We'd like to set facelets.development to false to suppress stack traces in non-development environments, but we'd like to have it set to true in dev for debugging.
Our deployment process dictates one CI build that is migrated through the environments up to production, so we can't use an approach that requires rebuilding the app / rewriting web.xml for each environment. We'd like to change the value from the application, based on a properties file setting. Is this possible? How can the application access facelets.development?
I think the simplest approach is to put the Context parameter in web.xml:
<context-param>
<param-name>facelets.DEVELOPMENT</param-name>
<param-value>false</param-value>
</context-param>
and override it in your Development deployments. This is usually possible without changing the WAR. In Tomcat, include META-INF/context.xml in your WAR with this line (within <Context> ... </Context>):
<Parameter name="facelets.DEVELOPMENT" value="true" override="false" />
Tomcat will copy this file on startup to $CATALINA_BASE/conf/[enginename]/[hostname]/[context-path-name].xml which can be used to configure the webapp outside the
WAR. This will happen in each of your environments and admins have to change it only once to:
<Parameter name="facelets.DEVELOPMENT" value="false" override="false" />
Afterwards, Tomcat will not overwrite it even if a new WAR with a newer /META-INF/context.xml is deployed. The names of context Parameters have to match the declarations in WEB-INF/web.xml.
See http://tomcat.apache.org/tomcat-6.0-doc/config/context.html for details ("Introduction" and "Context Parameters" section).
I can think of a few ways to do this, none of them very pleasant.
Decorate the FacesContext to control the init parameters programmatically. This is a lot of work for so little gain.
Patch the FaceletViewHandler class to get the behaviour you want. This may add maintenance overhead if you upgrade your Facelets libs. May make the folks who manage the app in production unhappy.
A variation on the patch approach is to just use the patched JARs in your dev/test machines and put them in the server libs - then use PARENT_FIRST classloading to load them over JARs in the apps (assuming your app server supports all that). The downside of this is that it imposes classloading policies and you have to manage JARs all over the place.
I would favour some other approach. If this setting is required on test machines, perhaps a deployment script could modify the app during the install for those servers. If you want it set to true in your source control, the build script could remove it as part of the build process. This approach would have no impact on your runtime code.
I've implemented a variation on option 1 above. eg
wrote a dynamic proxy for a ServletContext that intercepts the getInitParameter and getInitParameterNames methods returning appropriate values (sourced from environment-specific property files in this case)
wrote a very small subclass of FacesContextFactoryImpl that proxies the first/servletcontext parameter to getFacesContext and then delegates to the superclass.
added a faces-context-factory clause to my faces-config naming my FacesContextFactoryImpl class
I already had a mechanism in place to load environment-specific property files and make them available as a Properties object to the application (the factory in point 2 passes these properties to the proxy in point 1 to use as an alternate source of initParameter values)
The proxy looks like:
package zzzzz.framework.context;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import javax.servlet.ServletContext;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A proxy for ServletContext that intercepts accesses to the initParameters and
* returns values from the specified params instead. Generally useful if we have
* a set of properties (eg SystemContext.getInstance().getConfigProperties()) that
* we want to use in preference to the webapp initProperties.
*
*
*/
public class ServletContextProxy
implements InvocationHandler {
#SuppressWarnings("unused")
private static final Log log = LogFactory.getLog(ServletContextProxy.class);
#SuppressWarnings("unchecked")
public static ServletContext newInstance(ServletContext subject,
Map params) {
return newInstance(subject,
params,
true);
}
#SuppressWarnings("unchecked")
public static ServletContext newInstance(ServletContext subject,
Map params,
boolean overrideInitValues) {
return (ServletContext) Proxy.newProxyInstance(subject.getClass()
.getClassLoader(),
subject.getClass()
.getInterfaces(),
new ServletContextProxy(subject,
params,
overrideInitValues));
}
/**
* A convenience method to help extracting the initParameters from a
* ServletContext because it doesn't expose it's underlying Map
*
* #param config
* #return
*/
#SuppressWarnings("unchecked")
protected static Map copyInitParameters(Map parms,
ServletContext config) {
Enumeration names = config.getInitParameterNames();
// copy all the existing initParameters
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
parms.put(name,
config.getInitParameter(name));
}
return parms;
}
private boolean overrideInitValues = true;
#SuppressWarnings("unchecked")
private Map params;
private ServletContext subject;
#SuppressWarnings("unchecked")
public ServletContextProxy(ServletContext subject,
Map params,
boolean overrideInitValues) {
this.subject = subject;
this.overrideInitValues = overrideInitValues;
this.params = new Hashtable();
if (this.overrideInitValues) { // default behaviour... supplied parameters win
// start with initParameters
copyInitParameters(this.params,
subject);
// override and supplement with supplied params
if (params != null) {
this.params.putAll(params);
}
} else {
// start with supplied params
if (params != null) {
this.params.putAll(params);
}
// override and supplement with initParameters
copyInitParameters(this.params,
subject);
}
}
public Object invoke(Object proxy,
Method m,
Object[] args) throws Throwable {
Object result;
try {
if ("getInitParameter".equals(m.getName())) {
result = this.params.get(args[0]);
} else if ("getInitParameterNames".equals(m.getName())) {
result = IteratorUtils.asEnumeration(this.params.keySet()
.iterator());
} else {// else let it go through to the keeper
result = m.invoke(this.subject,
args);
}
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("unexpected invocation exception: "
+ e.getMessage());
}
return result;
}
}
The factory looks like:
package zzz.faces.context;
import javax.faces.FacesException;
import javax.faces.context.FacesContext;
import javax.faces.lifecycle.Lifecycle;
import javax.servlet.ServletContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import zzzzz.framework.context.ServletContextProxy;
import zzzzz.context.SystemContext;
/**
* A FacesContextFactory implementation that supplements/overrided the
* servletContext initParemeters with properties form
* SystemContext.configProperties
* <p>
* The point of this is that it allows us to substitute configuration in the
* web.xml like this (which requires rewriting web.xml to change)
* </p>
*
* <pre>
* <!-- Enables special Facelets debug output during development -->
* <context-param>
* <param-name>facelets.DEVELOPMENT</param-name>
* <param-value>true</param-value>
* </context-param>
* </pre>
*
* <p>
* with settings in the relevent application.properties file like this (which
* can be changed separately to the webapp)
* </p>
*
* <pre>
* # Enables special Facelets debug output during development
* facelets.DEVELOPMENT=true
* </pre>
*
* <p>
* usage: add a clause to faces-config like this:
*
* <pre>
* <factory>
* <faces-context-factory>zzzzz.faces.context.FacesContextFactoryImpl</faces-context-factory>
* </factory>
* </pre>
* <p>
*
*/
public class FacesContextFactoryImpl extends com.sun.faces.context.FacesContextFactoryImpl {
#SuppressWarnings("unused")
private static final Log log = LogFactory.getLog(FacesContextFactoryImpl.class);
public FacesContextFactoryImpl() {
super();
}
#Override
public FacesContext getFacesContext(Object sc,
Object request,
Object response,
Lifecycle lifecycle) throws FacesException {
if (sc instanceof ServletContext
&& !(sc instanceof ServletContextProxy)) {
// wrap the servlet context with a proxy to override/supplement initParameters
sc = ServletContextProxy.newInstance((ServletContext) sc,
SystemContext.getInstance()
.getConfigProperties(),
true);
}
return super.getFacesContext(sc,
request,
response,
lifecycle);
}
}
and the faces-config looks like
<faces-config>
blah waffle....
<factory>
<faces-context-factory>zzzz.faces.context.FacesContextFactoryImpl</faces-context-factory>
</factory>
</faces-config>
What SystemContext.getInstance().getConfigProperties() looks like is an excercise for another day but it just returns the Map of property values the app is supposed to use
Related
Can anyone please help me with this JAXB unmarshal issue?
So, this is the file employee.xml I am using:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<parent>
<anotherchild1>Another</anotherchild1>
</parent>
and this is the Parent.java class which has JAXB annotation:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package XMLToObject;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
*
* #author Gauravb
*/
#XmlRootElement
public class Parent {
private String child;
private String anotherchild1;
public String getAnotherChild1() {
return anotherchild1;
}
public void setAnotherChild1(String child) {
this.anotherchild1 = anotherchild1;
}
}
And this is XMLToObject.java file which I am using:
package XMLToObject;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author Gauravb
*/
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
public class XMLToObject {
public static void main(String[] args) {
try {
File file = new File("employee.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(Parent.class);
System.out.println("Reading....."+file.getAbsolutePath());
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Parent p = (Parent) jaxbUnmarshaller.unmarshal(file);
//System.out.println("Child:"+p.getChild());
System.out.println("Another Child:"+p.getAnotherChild1());
} catch (JAXBException e) {e.printStackTrace(); }
}
}
Now, my issue is when I run XMLToObject.java file, then I see following:
Reading.....C:\xxxxxx\employee.xml
Another Child:null
So, can anyone please let me know what can be the reason behind the value as null?
Please note:
1. I am a newbie in JAXB
2. I am using Netbeans editor while running the code
3. I do NOT have XSD for the XML and dont want to use any for my learning
4. I do NOT want to use MOXy for now, this is something I will learn next
You have forgot to add #XmlElement annotation in the binding class Parent.java as below
#XmlElement
private String anotherchild1;
I followed the https://learn.microsoft.com/en-us/azure/application-insights/app-insights-java-get-started, but still without success.
I have applicationinsights-web dependency in place via maven
I added ApplicationInsights.xml to main/resources with hardcoded instrumentation key and even with <SDKLogger /> inside
I added the scan path: #ComponentScan({...., "com.microsoft.applicationinsights.web.spring"})
Results:
I see no logs about looking up the configuration file, even if I make the syntax error in in or remove it completely
in debug I see that RequestNameHandlerInterceptorAdapter is instantiated via com.microsoft.applicationinsights.web.spring.internal.InterceptorRegistry, and during calls the preHandle method is called, but calls to ThreadContext.getRequestTelemetryContext() returns always null and nothing more happens
It looks like it is something obvious, but no idea what. What part/classes are responsible for loading the configuration file?
I was a little bit confused with documentation. As mentioned by yonisha, the filter does the whole magic. The following configuration class takes care of creating and adding the filter in Spring Boot application.
import com.microsoft.applicationinsights.web.internal.WebRequestTrackingFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
#Configuration
#ComponentScan("com.microsoft.applicationinsights.web.spring")
public class ApplicationInsightsConfiguration {
#Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(appInsightsWebRequestTrackingFilter());
registration.addUrlPatterns("/*");
registration.setName("webRequestTrackingFilter");
registration.setOrder(1);
return registration;
}
#Bean(name = "appInsightsWebRequestTrackingFilter")
public Filter appInsightsWebRequestTrackingFilter() {
return new WebRequestTrackingFilter();
}
Important: It will work nicely if you set the server.context-path property to some value. If not, AI initialization will fail with error
AI: ERROR 03-04-2017 14:11, 20: WebApp name is not found, unable to register WebApp
In order to keep servlet-context empty, I had to implement wrappers for the filter and 2 other classes to override it, but it was a very dirty fix... Would be great, if the name could be passed as a parameter to the filter, but that is not yet possible (https://github.com/Microsoft/ApplicationInsights-Java/issues/359)
In spring boot , We need to configure WebRequestTrackingFilter by extending WebSecurityConfigurerAdapter and overriding configure(HttpSecurity httpSecurity)
#Bean
public WebRequestTrackingFilter applicationInsightsFilterBean() throws Exception {
WebRequestTrackingFilter webRequestTrackingFilter = new WebRequestTrackingFilter();
return webRequestTrackingFilter;
}
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
//other stuff...
httpSecurity.addFilterBefore(applicationInsightsFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
you need to have below configuration also ..
applicationinsights-web dependency in place via maven
added ApplicationInsights.xml to main/resources
Here is newer guide for Spring Boot Application Insights integration that worked well for me just now:
https://github.com/AzureCAT-GSI/DevCamp/tree/master/HOL/java/06-appinsights
The idea is basically what Tomasz has above with some minor differences.
package devCamp.WebApp.configurations;
import javax.servlet.Filter;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.microsoft.applicationinsights.TelemetryConfiguration;
import com.microsoft.applicationinsights.web.internal.WebRequestTrackingFilter;
#Configuration
public class AppInsightsConfig {
#Bean
public String telemetryConfig() {
String telemetryKey = System.getenv("APPLICATION_INSIGHTS_IKEY");
if (telemetryKey != null) {
TelemetryConfiguration.getActive().setInstrumentationKey(telemetryKey);
}
return telemetryKey;
}
#Bean
public FilterRegistrationBean aiFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new WebRequestTrackingFilter());
registration.addUrlPatterns("/**");
registration.setOrder(1);
return registration;
}
#Bean(name = "WebRequestTrackingFilter")
public Filter WebRequestTrackingFilter() {
return new WebRequestTrackingFilter();
}
}
The guide at the link above has a full set of instructions and includes client side js and a java log appender example as well. Hope this helps.
The above all method works! However you can try the whole new seamless experience using Application Insights SpringBoot Starter.
https://github.com/Microsoft/ApplicationInsights-Java/blob/master/azure-application-insights-spring-boot-starter/README.md
This is currently in BETA
I'm running Spring Batch job on Linux (works fine on Windows). It's a simple file existence check but I am unable to get the system to find the path/file when it definitely exists. Does Spring's Resource class somehow automatically know when it's run on Linux or Windows? Is there something else I can do
My job definition
<batch:job id="vendorGoalsJob" restartable="true">
<batch:step id="checkStartFileExists" next="readHeaderFiles">
<batch:tasklet ref="startFileExistsTasklet" />
</batch:step>
more steps....
</batch:job>
<bean id="startFileExistsTasklet"
class="com.blah.blah.StartFileExistsTasklet"
scope="step">
<property name="resource" value="/di/global/Users/my filename with blanks.txt" />
</bean>
The tasklet class:
package com.blah.blah;
import static java.lang.String.format;
import org.apache.log4j.Logger;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.core.io.Resource;
public class StartFileExistsTasklet implements Tasklet {
private static final Logger logger = Logger.getLogger(StartFileExistsTasklet.class);
private Resource resource;
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
if (!resource.isReadable()) {
logger.info(format("Resource %s not found.", resource.getFilename()));
chunkContext.getStepContext().getStepExecution().setTerminateOnly();
}
return RepeatStatus.FINISHED;
}
public void setResource(Resource resource) {
this.resource = resource;
}
}
...And I always get the 'not found message':
2013-09-26 15:47:09,342 handleStep Executing step: [checkStartFileExists] (SimpleStepHandler.java:133)
2013-09-26 15:47:09,352 execute Resource vendor-goal-trigger.txt not found.
2013-09-26 15:47:09,361 isInterrupted Step interrupted through StepExecution
The default RessourceLoader used by Spring loads from the classpath if the protocol is not specified in the ressource URL.
/**
* Default implementation of the {#link ResourceLoader} interface.
* Used by {#link ResourceEditor}, and serves as base class for
* {#link org.springframework.context.support.AbstractApplicationContext}.
* Can also be used standalone.
*
* <p>Will return a {#link UrlResource} if the location value is a URL,
* and a {#link ClassPathResource} if it is a non-URL path or a
* "classpath:" pseudo-URL.
*
* #author Juergen Hoeller
* #since 10.03.2004
* #see FileSystemResourceLoader
* #see org.springframework.context.support.ClassPathXmlApplicationContext
*/
public class DefaultResourceLoader implements ResourceLoader { ... }
You may try using the file: prefix:
<property name="resource" value="file:/di/global/Users/my filename with blanks.txt"/>
By default the generated XText artifacts generate code from my DSL to the default outlet (which defaults to src-gen folder). I know that you can explicitly pass the outlet configuration name in fsa.generateFile("myfile.txt", "MY_OUTLET_NAME", "Some file content").
I it because I want to generate code with my XText DSL and want to use the generation gap pattern and generate code in a folder called "src-once".
I'am using XText 2.2.1.
My questions:
1) Where and how do I define my outlets like "MY_OUTLET_NAME"?
2) Is there a way to prevent overwriting existing files in a specific outlet?
The hint form Christian Dietrich pointed me in the right direction. Below is the code that I ended up with.
I have created a new class MyOutputConfigurationProvider that implements IOutputConfigurationProvider. The getOutputConfigurations method returns two output configurations, the default src-gen and a custom src-gen-once with the correct settings for generating sources only once.
package com.my.dsl;
import static com.google.common.collect.Sets.newHashSet;
import java.util.Set;
import org.eclipse.xtext.generator.IFileSystemAccess;
import org.eclipse.xtext.generator.IOutputConfigurationProvider;
import org.eclipse.xtext.generator.OutputConfiguration;
public class MyOutputConfigurationProvider implements
IOutputConfigurationProvider {
public final static String DEFAULT_OUTPUT_ONCE = "DEFAULT_OUTPUT_ONCE";
/**
* #return a set of {#link OutputConfiguration} available for the generator
*/
public Set<OutputConfiguration> getOutputConfigurations() {
OutputConfiguration defaultOutput = new OutputConfiguration(IFileSystemAccess.DEFAULT_OUTPUT);
defaultOutput.setDescription("Output Folder");
defaultOutput.setOutputDirectory("./src-gen");
defaultOutput.setOverrideExistingResources(true);
defaultOutput.setCreateOutputDirectory(true);
defaultOutput.setCleanUpDerivedResources(true);
defaultOutput.setSetDerivedProperty(true);
OutputConfiguration onceOutput = new OutputConfiguration(DEFAULT_OUTPUT_ONCE);
onceOutput.setDescription("Output Folder (once)");
onceOutput.setOutputDirectory("./src-gen-once");
onceOutput.setOverrideExistingResources(false);
onceOutput.setCreateOutputDirectory(true);
onceOutput.setCleanUpDerivedResources(false);
onceOutput.setSetDerivedProperty(true);
return newHashSet(defaultOutput, onceOutput);
}
}
To use the MyOutputConfigurationProvider implementation add a configure method to your module class:
/**
* Use this class to register components to be used within the IDE.
*/
public class MyDslUiModule extends com.my.dsl.ui.AbstractMyDslUiModule {
public MyDslUiModule(AbstractUIPlugin plugin) {
super(plugin);
}
#Override
public void configure(Binder binder) {
super.configure(binder);
binder.bind(IOutputConfigurationProvider.class).to(MyOutputConfigurationProvider.class).in(Singleton.class);
}
}
implement a custom IOutputConfigurationProvider should do the trick
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.