I have extended the default product type to also have a videos attribute that is identical to the identical to the images attribute, except for the name:
<itemtype code="Product" extends="GenericItem" autocreate="false" generate="false">
<description>Extend the product type to also hold links to videos.</description>
<attributes>
<attribute qualifier="videos" type="MediaContainerList">
<description>List of videos for a given product</description>
<modifiers/>
<persistence type="property"/>
</attribute>
</attributes>
</itemtype>
I am now trying to copy the ProductPrimaryImagePopulator to set the videos in the Data object, but I am missing something obvious. Here is the code and the error:
import de.hybris.platform.commercefacades.product.data.ImageData;
import de.hybris.platform.commercefacades.product.data.ImageDataType;
import de.hybris.platform.commercefacades.product.data.ProductData;
import de.hybris.platform.core.model.media.MediaContainerModel;
import de.hybris.platform.core.model.media.MediaModel;
import de.hybris.platform.core.model.product.ProductModel;
import de.hybris.platform.servicelayer.dto.converter.ConversionException;
import java.util.ArrayList;
import java.util.List;
public class ProductVideoPopulator<SOURCE extends ProductModel, TARGET extends ProductData>
extends AbstractProductImagePopulator<SOURCE, TARGET> {
#Override
public void populate(final SOURCE productModel, final TARGET productData) throws ConversionException {
final MediaContainerModel primaryImageMediaContainer = getPrimaryImageMediaContainer(productModel);
if (primaryImageMediaContainer != null) {
final List<ImageData> imageList = new ArrayList<ImageData>();
// Use the first container as the primary image
addImagesInFormats(primaryImageMediaContainer, ImageDataType.PRIMARY, 0, imageList);
for (final ImageData imageData : imageList) {
if (imageData.getAltText() == null) {
imageData.setAltText(productModel.getName());
}
}
productData.setVideos(imageList);
}
}
protected MediaContainerModel getPrimaryImageMediaContainer(final SOURCE productModel) {
final MediaModel picture = (MediaModel) getProductAttribute(productModel, ProductModel.PICTURE);
if (picture != null) {
return picture.getMediaContainer();
}
return null;
}
}
The error
[yjavac] Compiling 1 source file to /Users/riley/dev/work/hybrisdemo/core-customize/hybris/bin/custom/demo/demofacades/classes
[yjavac] ----------
[yjavac] 1. ERROR in /Users/riley/dev/work/hybrisdemo/core-customize/hybris/bin/custom/demo/demofacades/src/com/nobiz/demo/facades/populators/ProductVideoPopulator.java (at line 15)
[yjavac] extends AbstractProductImagePopulator<SOURCE, TARGET> {
[yjavac] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[yjavac] AbstractProductImagePopulator cannot be resolved to a type
[yjavac] ----------
[yjavac] 2. ERROR in /Users/riley/dev/work/hybrisdemo/core-customize/hybris/bin/custom/demo/demofacades/src/com/nobiz/demo/facades/populators/ProductVideoPopulator.java (at line 17)
[yjavac] public void populate(final SOURCE productModel, final TARGET productData) throws ConversionException {
[yjavac] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[yjavac] The method populate(SOURCE, TARGET) of type ProductVideoPopulator<SOURCE,TARGET> must override or implement a supertype method
[yjavac] ----------
[yjavac] 2 problems (2 errors)
Another issue I know I will need to resolve is in the ProductModel.PICTURE, Eclipse is not showing me that value, what should I be using to get the videos?
AbstractProductImagePopulator cannot be resolved to a type
I'm guessing that you have not defined the dependencies.
AbstractProductImagePopulator exists in commercefacades. Make sure demofacades defines commercefacades as a dependency in extensioninfo.xml.
Another issue I know I will need to resolve is in the
ProductModel.PICTURE, Eclipse is not showing me that value, what
should I be using to get the videos?
Since you added Product.videos in items.xml, something like ProductModel.VIDEOS should be available in ProductModel.
Make sure to resolve all build / compilation errors first. Then, check if it's all good. Additionally, you will also need to add the populator to the converter that will convert the ProductModel.
Related
What I am trying to do:
I want to use the MapboxSearchSdk that mapbox has, however, I get the following error when trying to use it.
Error:
Occurs at the following line of code:
MapboxSearchSdk.initialize(this, getResources().getString(R.string.mapbox_access_token), LocationEngineProvider.getBestLocationEngine(this));
"Wrong 3rd argument type. Found: 'com.mapbox.android.core.location.LocationEngine', required: 'com.mapbox.search.location.LocationProvider'"
My Code:
package com.android.takemehome;
import android.app.Application;
import android.util.Log;
import com.mapbox.android.core.location.LocationEngineProvider;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.search.MapboxSearchSdk;
import com.mapbox.vision.VisionManager;
public class BaseARActivity extends Application {
#Override
public void onCreate() {
super.onCreate();
Mapbox.getInstance(this, getString(R.string.mapbox_access_token));
VisionManager.init(this, getResources().getString(R.string.mapbox_access_token));
Log.e("Map Box ", "Token initialized");
MapboxSearchSdk.initialize(this, getResources().getString(R.string.mapbox_access_token), LocationEngineProvider.getBestLocationEngine(this));
}
}
Guide that I am following:
I followed the guide on the mapbox website https://docs.mapbox.com/android/search/guides/install/.
For some reason however, I am getting this error. Could someone please help me fix the issue?
Thank you.
I have found the fix.
I needed to create a new LocationProvider to put as the 3rd argument.
So, I just added the following, and now it works:
LocationProvider locationProvider = new LocationProvider() {
#Nullable
#Override
public Point getLocation() {
return null;
}
};
MapboxSearchSdk.initialize(this, getResources().getString(R.string.mapbox_access_token), locationProvider);
I'm running into a problem with GroovyScriptEngine - it seems not to be able to work with inner classes. Anyone know whether there's some limitation in GroovyScriptEngine or a workaround?
I have a directory with these two files:
// MyClass.groovy
public class MyClass {
MyOuter m1;
MyOuter.MyInner m2;
}
and
// MyOuter.groovy
public class MyOuter {
public static class MyInner {}
}
I have a following test class:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
When I run it I get the following compilation error:
Exception in thread "main" org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
C:\MyGroovySourceDir\MyClass.groovy: 3: unable to resolve class MyOuter.MyInner
# line 3, column 2.
MyOuter.MyInner m2;
^
1 error
at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:311)
at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:983)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:633)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:582)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:354)
at groovy.lang.GroovyClassLoader.access$300(GroovyClassLoader.java:87)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:323)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:320)
at org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache.getAndPut(ConcurrentCommonCache.java:147)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:318)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.doParseClass(GroovyScriptEngine.java:248)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.parseClass(GroovyScriptEngine.java:235)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:307)
at groovy.lang.GroovyClassLoader.recompile(GroovyClassLoader.java:811)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:767)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:836)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:824)
I would have expected a "clean compile", but the inner class seems to be causing problems.
My groovy classes compile fine at the command line using groovyc, or in Eclipse.
You have faced an edge case here. To clarify what happens let's define the initial conditions:
you have a Java (or Groovy) class that gets executed inside JVM
you have two Groovy classes that get loaded outside of the JVM
The problem you have described does not exist if you put these two Groovy classes inside the same path you execute your Java class from - in this case IDE takes care to compile these Groovy classes and put them to the classpath of a JVM that gets started to run your Java test class.
But this is not your case and you are trying to load these two Groovy classes outside the running JVM using GroovyClassLoader (which extends URLClassLoader btw). I will try to explain in the simplest possible words what happened that adding field of type MyOuter does not throw any compilation error, but MyOuter.MyInner does.
When you execute:
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
Groovy class loader goes to script file lookup part, because it was not able to find MyClass in the current classpath. This is the part responsible for it:
// at this point the loading from a parent loader failed
// and we want to recompile if needed.
if (lookupScriptFiles) {
// try groovy file
try {
// check if recompilation already happened.
final Class classCacheEntry = getClassCacheEntry(name);
if (classCacheEntry != cls) return classCacheEntry;
URL source = resourceLoader.loadGroovySource(name);
// if recompilation fails, we want cls==null
Class oldClass = cls;
cls = null;
cls = recompile(source, name, oldClass);
} catch (IOException ioe) {
last = new ClassNotFoundException("IOException while opening groovy source: " + name, ioe);
} finally {
if (cls == null) {
removeClassCacheEntry(name);
} else {
setClassCacheEntry(cls);
}
}
}
Source: src/main/groovy/lang/GroovyClassLoader.java#L733-L753
Here URL source = resourceLoader.loadGroovySource(name); it loads the full file URL to the source file and here cls = recompile(source, name, oldClass); it executes class compilation.
There are several phases involved in Groovy class compilation. One of them is Phase.SEMANTIC_ANALYSIS which analyses class fields and their types for instance. At this point ClassCodeVisitorSupport executes visitClass(ClassNode node) for MyClass class and following line
node.visitContents(this);
starts class contents processing. If we take a look at the source code of this method:
public void visitContents(GroovyClassVisitor visitor) {
// now let's visit the contents of the class
for (PropertyNode pn : getProperties()) {
visitor.visitProperty(pn);
}
for (FieldNode fn : getFields()) {
visitor.visitField(fn);
}
for (ConstructorNode cn : getDeclaredConstructors()) {
visitor.visitConstructor(cn);
}
for (MethodNode mn : getMethods()) {
visitor.visitMethod(mn);
}
}
Source: src/main/org/codehaus/groovy/ast/ClassNode.java#L1066-L108
we will see that it analyses and process class properties, fields, constructors and methods. At this phase it resolves all types defined for these elements. It sees that there are two properties m1 and m2 with types MyOuter and MyOuter.MyInner accordingly, and it executes visitor.visitProperty(pn); for them. This method executes the one we are looking for - resolve()
private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
resolveGenericsTypes(type.getGenericsTypes());
if (type.isResolved() || type.isPrimaryClassNode()) return true;
if (type.isArray()) {
ClassNode element = type.getComponentType();
boolean resolved = resolve(element, testModuleImports, testDefaultImports, testStaticInnerClasses);
if (resolved) {
ClassNode cn = element.makeArray();
type.setRedirect(cn);
}
return resolved;
}
// test if vanilla name is current class name
if (currentClass == type) return true;
String typeName = type.getName();
if (genericParameterNames.get(typeName) != null) {
GenericsType gt = genericParameterNames.get(typeName);
type.setRedirect(gt.getType());
type.setGenericsTypes(new GenericsType[]{ gt });
type.setGenericsPlaceHolder(true);
return true;
}
if (currentClass.getNameWithoutPackage().equals(typeName)) {
type.setRedirect(currentClass);
return true;
}
return resolveNestedClass(type) ||
resolveFromModule(type, testModuleImports) ||
resolveFromCompileUnit(type) ||
resolveFromDefaultImports(type, testDefaultImports) ||
resolveFromStaticInnerClasses(type, testStaticInnerClasses) ||
resolveToOuter(type);
}
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L343-L378
This method gets executed for both MyOuter and MyOuter.MyInner classes. It is worth mentioning that class resolving mechanism only checks if given class is available in the classpath and it does not load or parse any classes. That is why MyOuter gets recognized when this method reaches resolveToOuter(type). If we take a quick look at its source code we will understand why it works for this class:
private boolean resolveToOuter(ClassNode type) {
String name = type.getName();
// We do not need to check instances of LowerCaseClass
// to be a Class, because unless there was an import for
// for this we do not lookup these cases. This was a decision
// made on the mailing list. To ensure we will not visit this
// method again we set a NO_CLASS for this name
if (type instanceof LowerCaseClass) {
classNodeResolver.cacheClass(name, ClassNodeResolver.NO_CLASS);
return false;
}
if (currentClass.getModule().hasPackageName() && name.indexOf('.') == -1) return false;
LookupResult lr = null;
lr = classNodeResolver.resolveName(name, compilationUnit);
if (lr!=null) {
if (lr.isSourceUnit()) {
SourceUnit su = lr.getSourceUnit();
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
} else {
type.setRedirect(lr.getClassNode());
}
return true;
}
return false;
}
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L725-L751
When Groovy class loader tries to resolve MyOuter type name it reaches
lr = classNodeResolver.resolveName(name, compilationUnit);
which locates script with a name MyOuter.groovy and it creates a SourceUnit object associated with this script file name. It is simply something like saying "OK, this class is not in my classpath at the moment, but there is a source file I can see that once compiled it will provide a valid type of name MyOuter". This is why it finally reaches:
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
where currentClass is an object associated with MyClass type - it adds this source unit to MyClass compilation unit, so it gets compiled with the MyClass class. And this is the point where resolving
MyOuter m1
class property ends.
In the next step it picks MyOuter.MyInner m2 property and it tries to resolve its type. Keep in mind - MyOuter got resolved correctly, but it didn't get loaded to the classpath, so it's static inner class does not exist in any scope, yet. It goes through the same resolving strategies as MyOuter, but any of them works for MyOuter.MyInner class. And this is why ResolveVisitor.resolveOrFail() eventually throws this compilation exception.
Workaround
OK, so we know what happens, but is there anything we can do about it? Luckily, there is a workaround for this problem. You can run your program and load MyClass successfully only if you load MyOuter class to Groovy script engine first:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
groovyScriptEngine.getGroovyClassLoader().loadClass("MyOuter");
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
Why does it work? Well, semantic analysis of MyOuter class does not cause any problems, because all types are known at this stage. This is why loading MyOuter class succeeds and it results in Groovy script engine instance knows what MyOuter and MyOuter.MyInner types are. So when you next load MyClass from the same Groovy script engine it will apply different resolving strategy - it will find both classes available to the current compilation unit and it wont have to resolve MyOuter class based on its Groovy script file.
Debugging
If you want to examine this use case better it is worth to run a debugger and see analyze what happens at the runtime. You can create a breakpoint at line 357 of ResolveVisitor.java file for instance, to see described scenario in action. Keep in mind one thing though - resolveFromDefaultImports(type, testDefaultImports) will try to lookup MyClass and MyOuter classes by applying default packages like java.util, java.io, groovy.lang etc. This resolve strategy kicks in before resolveToOuter(type) so you have to patiently jump through them. But it is worth it to see and get a better understanding about how things work. Hope it helps!
I'm getting an unresolved compilation error. Getting these errors:
The method addActionListener(ActionListener) in the type
AbstractButton is not applicable for the arguments (Gui.HandlerClass)
The method addActionListener(ActionListener) in the type
AbstractButton is not applicable for the arguments (Gui.HandlerClass)
ActionListener cannot be resolved to a type ActionEvent cannot be
resolved to a type
import java.awt.*;
import javax.swing.*;
public class Gui extends JFrame{
private JButton reg;
private JButton custom;
public Gui(){
super("The title");
setLayout(new FlowLayout());
reg = new JButton("Regular Button");
add(reg);
Icon b = new ImageIcon(getClass().getResource("foto 1.png"));
Icon c = new ImageIcon(getClass().getResource("foto 2.png"));
custom = new JButton("Custom", b);
custom.setRolloverIcon(c);
add(custom);
HandlerClass handler = new HandlerClass();
reg.addActionListener(handler);
custom.addActionListener(handler);
}
private class HandlerClass implements ActionListener{
public void actionPerformed(ActionEvent event) {
JOptionPane.showMessageDialog(null, String.format("%s", event.getActionCommand()));
}
}
}
Avoid using * in imports. Your problem here is that ActionListener and ActionEvent classes are not imported. Therefore, you must import them:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
Also, what IDE are you using? Most IDEs would spot the problem and recommend you something in order to be fixed.
I was trying to develop an app in Android Studio 2.1.2 for Sony Smart Glass. I wrote the coding and now I have to register the app so that the Smart Connect can recognize the app, so that it can be used for Sony Smart Glass.
Sony has given few set of instructions to register but I couldn't understand it. Nevertheless I tried my best to register it. I am getting around 13 errors. I have posted my coding below.
package com.example.balakrishnan.newapp;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements RegistrationInformation {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onButonTap(View v) {
Toast myToast = Toast.makeText(getApplicationContext(), "sony smart glass", Toast.LENGTH_LONG);
myToast.show();
}
public void browserapp(View view) {
Intent browserIntent=new Intent(Intent.ACTION_VIEW, Uri.parse("http://192.168.72.101/smartglass/datetime.php"));
startActivity(browserIntent);
}
#Override
public int getRequiredControlApiVersion() {
return 4;
}
#Override
public int getTargetControlApiVersion() {
return 4;
}
#Override
public int getRequiredSensorApiVersion() {
// Return 0 if the API is not required for your app
return 0;
}
#Override
public boolean isDisplaySizeSupported(int width, int height) {
boolean isSEG =
(width == HelloLayoutsSEGControl.getSupportedControlWidth(mContext) &&
height == HelloLayoutsSEGControl.getSupportedControlHeight(mContext));
return isSW2 || isSEG;
}
#Override
protected RegistrationInformation getRegistrationInformation() {
return new SampleRegistrationInformation(this);
}
}
Errors:
Error:(13, 64) error: cannot find symbol class RegistrationInformation
Error:(60, 15) error: cannot find symbol class RegistrationInformation
Error:(37, 5) error: method does not override or implement a method from a supertype
Error:(31, 5) error: method does not override or implement a method from a supertype
Error:(43, 5) error: method does not override or implement a method from a supertype
Error:(49, 5) error: method does not override or implement a method from a supertype
Error:(52, 75) error: cannot find symbol variable mContext
Error:(52, 27) error: cannot find symbol variable HelloLayoutsSEGControl
Error:(53, 84) error: cannot find symbol variable mContext
Error:(53, 35) error: cannot find symbol variable HelloLayoutsSEGControl
Error:(55, 16) error: cannot find symbol variable isSW2
Error:(59, 5) error: method does not override or implement a method from a supertype
Error:(61, 20) error: cannot find symbol class SampleRegistrationInformation
:app:compileDebugJavaWithJavac FAILED
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.
Follow these steps for registration process:
Create a class that extends RegistrationInformation.
Override the methods to define the API versions used.
Override the getExtensionRegistrationConfiguration() method to define your app’s registration info.
Override the isDisplaySizeSupported() method to define which accessories your app supports.
Return an instance of RegistrationInformation in your ExtensionService class.
I use Tapestry 4, and whenever we push a release that changes any assets (image, style sheet, JS library), we get problems because users still have the old version of the asset in their browser cache. I'd like to set up some easy way to allow caching, but force a new asset download when we update the application. Simply disallowing caching entirely for assets is not an acceptable solution.
I couldn't see any existing mechanism for doing this, but I was figuring that there might be some way to tell Tapestry to add the build number to the URL, something like this:
http://www.test.com/path/to/the/asset/asset.jpg?12345
That way, every new build would make it look like a different asset to the end user.
Does Tapestry provide an easy way to solve the cache problem that I'm not aware of? If not, how would one go about modifying the URL generated by Tapestry? And how would the code responsible for doing that get the build number? (I could get the build number into a Spring bean, for example, but how would the new URL building mechanism get at it?)
After stewing about this problem for a long time, I eventually solved it myself. This solution assumes you have the tapestry-spring library in your project.
In my case, I have a Spring bean that contains some of my application's global properties:
package myapp;
public class AppProperties {
private String build;
public String getBuild() {
return build;
}
public void setBuild(String build) {
this.build = build;
}
// other properties
}
Declare this bean in your Spring configuration:
<bean id="appProperties" class="myapp.AppProperties">
<property name="build" value="#BUILD_NUMBER#"/>
</bean>
You can set up your Ant build script to replace #BUILD_NUMBER# with the actual number (see the Copy task in the Ant manual for details).
Now create a class that will wrap IAssets and tack the build number onto the URL:
package myapp;
import java.io.InputStream;
import org.apache.hivemind.Location;
import org.apache.hivemind.Resource;
import org.apache.tapestry.IAsset;
public class BuildAwareAssetWrapper implements IAsset {
private IAsset wrapped;
private String build;
public BuildAwareAssetWrapper(IAsset wrapped, String build) {
this.wrapped = wrapped;
this.build = build;
}
public String buildURL() {
return addParam(wrapped.buildURL(), "build", build);
}
public InputStream getResourceAsStream() {
return wrapped.getResourceAsStream();
}
public Resource getResourceLocation() {
return wrapped.getResourceLocation();
}
public Location getLocation() {
return wrapped.getLocation();
}
private static String addParam(String url, String name, String value) {
if (url == null) url = "";
char sep = url.contains("?") ? '&' : '?';
return url + sep + name + '=' + value;
}
}
Next, we need to make Tapestry wrap all assets with our wrapper. The AssetSourceImpl class is responsible for providing IAsset instances to Tapestry. We'll extend this class and override the findAsset() method so that we can wrap the created assets with the wrapper class:
package myapp;
import java.util.Locale;
import org.apache.hivemind.Location;
import org.apache.hivemind.Resource;
import org.apache.tapestry.IAsset;
import org.apache.tapestry.asset.AssetSourceImpl;
public class BuildAwareAssetSourceImpl extends AssetSourceImpl {
private AppProperties props;
#Override
public IAsset findAsset(Resource base, String path, Locale locale, Location location) {
IAsset asset = super.findAsset(base, path, locale, location);
return new BuildAwareAssetWrapper(asset, props.getBuild());
}
public void setAppProperties(AppProperties props) {
this.props = props;
}
}
Notice that the implementation has a setter which can accept our Spring bean. The last step is to get Tapestry to use BuildAwareAssetSourceImpl to create assets instead of AssetSourceImpl. We do this by overriding the corresponding service point in hivemodule.xml:
<!-- Custom asset source -->
<implementation service-id="tapestry.asset.AssetSource">
<invoke-factory service-id="hivemind.BuilderFactory" model="singleton">
<construct class="myapp.BuildAwareAssetSourceImpl">
<set-object property="appProperties" value="spring:appProperties"/>
<set-configuration property="contributions" configuration-id="tapestry.asset.AssetFactories"/>
<set-service property="lookupAssetFactory" service-id="tapestry.asset.LookupAssetFactory"/>
<set-service property="defaultAssetFactory" service-id="tapestry.asset.DefaultAssetFactory"/>
</construct>
</invoke-factory>
</implementation>
That's it. If you run your application and view the source for any page that uses an asset, you will see that the URL will have the new build parameter on it.