com.github.edgarespina.mwa.wro4j.WebResourceOptimizer Maven / Gradle / Ivy
package com.github.edgarespina.mwa.wro4j;
import static ro.isdc.wro.model.resource.processor.factory.ConfigurableProcessorsFactory.PARAM_POST_PROCESSORS;
import static ro.isdc.wro.model.resource.processor.factory.ConfigurableProcessorsFactory.PARAM_PRE_PROCESSORS;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.core.env.Environment;
import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.config.Context;
import ro.isdc.wro.config.jmx.ConfigConstants;
import ro.isdc.wro.extensions.manager.ExtensionsConfigurableWroManagerFactory;
import ro.isdc.wro.extensions.processor.css.LessCssProcessor;
import ro.isdc.wro.extensions.processor.js.JsHintProcessor;
import ro.isdc.wro.http.ConfigurableWroFilter;
import ro.isdc.wro.http.WroFilter;
import ro.isdc.wro.model.WroModel;
import ro.isdc.wro.model.factory.ModelTransformerFactory;
import ro.isdc.wro.model.factory.WroModelFactory;
import ro.isdc.wro.model.factory.XmlModelFactory;
import ro.isdc.wro.model.group.Group;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.locator.ClasspathUriLocator;
import ro.isdc.wro.model.resource.locator.UriLocator;
import ro.isdc.wro.model.resource.locator.UrlUriLocator;
import ro.isdc.wro.model.resource.locator.factory.SimpleUriLocatorFactory;
import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.processor.impl.PlaceholderProcessor;
import ro.isdc.wro.util.ObjectFactory;
import ro.isdc.wro.util.Transformer;
import com.github.edgarespina.mwa.Application;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Sets;
/**
* Build a {@link WroFilter} who takes sensible defaults for dev or production
* like environments. It publish the 'contextPath' as a variable for JS/CSS
* resources.
*
* @author edgar.espina
* @since 0.1
*/
public class WebResourceOptimizer {
/**
* Create a group for each resource found.
*
* @author Edgar Espina<[email protected]>
* @since 0.6
*/
private static class DevModelTransformer implements Transformer {
@Override
public WroModel transform(final WroModel input) throws Exception {
Collection groups = input.getGroups();
Map> map = new HashMap>();
for (Group group : groups) {
List resources = group.getResources();
for (Resource resource : resources) {
String groupName = FilenameUtils.getBaseName(resource.getUri());
Set newGroup = map.get(groupName);
if (newGroup == null) {
newGroup = new HashSet();
map.put(groupName, newGroup);
}
newGroup.add(resource);
}
}
WroModel output = new WroModel();
for (Entry> g : map.entrySet()) {
Group newGroup = new Group(g.getKey());
Set resources = g.getValue();
if (resources.size() > 2) {
throw new IllegalStateException("Multiples resource " + resources
+ " for group: " + g.getKey());
}
for (Resource resource : resources) {
newGroup.addResource(resource);
}
output.addGroup(newGroup);
}
return output;
}
}
/**
* The global Wro4j properties.
*/
private final Properties configProperties = new Properties();
/**
* The Wro4j factory properties.
*/
private final Properties factoryProperties = new Properties();
/**
* The model factory to use.
*/
private WroModelFactory wroModelFactory;
/**
* Custom pre-processors.
*/
private final Map preProcessors =
new HashMap();
/**
* Custom post-processors.
*/
private final Map postProcessors =
new HashMap();
/**
* The placeholders.
*/
private final Properties placeholders = new Properties();
/**
* Are we in dev?
*/
private boolean dev;
/**
* This proccessors are off in dev.
*/
private final Set noDevProccessors = Sets.newHashSet("cssCompressor",
"yuiCssMin", "yuiJsMin", "yuiJsMinAdvanced", "dojoShrinksafe",
"uglifyJs", "googleClosureSimple", "googleClosureAdvanced");
/**
* Creates a new {@link WebResourceOptimizer}.
*
* @param environment The environment object. Required.
*/
public WebResourceOptimizer(final Environment environment) {
Validate.notNull(environment, "The environmnt is required.");
dev =
Application.DEV.matches(environment.getProperty("application.mode"));
configProperties.setProperty("debug", String.valueOf(dev).toString());
boolean gzipEnabled;
boolean disableCache;
long cacheUpdatePeriod = 0;
long modelUpdatePeriod;
if (dev) {
gzipEnabled = Boolean.FALSE;
disableCache = Boolean.TRUE;
// Update wro.xml every second
modelUpdatePeriod = 1;
ModelTransformerFactory modelTransformerFactory =
new ModelTransformerFactory(new XmlModelFactory());
modelTransformerFactory.setTransformers(Arrays
.asList(new DevModelTransformer()));
this.wroModelFactory = modelTransformerFactory;
} else {
gzipEnabled = Boolean.TRUE;
disableCache = Boolean.FALSE;
modelUpdatePeriod = 0;
this.wroModelFactory = new XmlModelFactory();
}
// Publish context path
withPreProcessor("commonVars", new PlaceholderProcessor()
.setPropertiesFactory(new ObjectFactory() {
@Override
public Properties create() {
Context context = Context.get();
placeholders
.put("contextPath", context.getRequest().getContextPath());
return placeholders;
}
})
.setIgnoreMissingVariables(false));
configProperties.setProperty(ConfigConstants.debug.name(),
String.valueOf(dev));
configProperties.setProperty(ConfigConstants.gzipResources.name(),
String.valueOf(gzipEnabled));
configProperties.setProperty(ConfigConstants.cacheUpdatePeriod.name(),
String.valueOf(cacheUpdatePeriod));
configProperties.setProperty(ConfigConstants.modelUpdatePeriod.name(),
String.valueOf(modelUpdatePeriod));
configProperties.setProperty(ConfigConstants.disableCache.name(),
String.valueOf(disableCache));
configProperties.setProperty(ConfigConstants.encoding.name(),
"UTF-8");
configProperties.setProperty(ConfigConstants.jmxEnabled.name(), "false");
configProperties.setProperty("ignoreMissingResources", "false");
}
/**
* Creates a new {@link JsHintProcessor} with HTML error reporting.
*
* @param options The lint options. Required.
* @param excludePaths The paths to exclude. Optional.
* @return A new {@link JsHintProcessor} with HTML error reporting.
*/
public JsHintProcessor newJsHint(final LintOptions options,
final String... excludePaths) {
return new ExtendedJsHintProcessor(options, excludePaths);
}
/**
* Creates a new {@link LessCssProcessor} with HTML error reporting.
*
* @return A new {@link LessCssProcessor} with HTML error reporting.
*/
public LessCssProcessor newLessCss() {
return new LessCssProcessor() {
@Override
protected void onException(final WroRuntimeException ex) {
// Handle by problem reporter.
throw ex;
}
};
}
/**
* Publish a placeholder and later usage, usually from css or js files.
*
* @param name The placeholder's name. Required.
* @param value The placeholder's value. Required.
* @return This builder.
* @see
* PlaceholderProcessor
*/
public WebResourceOptimizer withPlaceholder(final String name,
final String value) {
Validate.notEmpty(name, "The placeholder's name is required.");
Validate.notEmpty(value, "The placeholder's value is required.");
placeholders.setProperty(name, value);
return this;
}
/**
* Publish a placeholder for later usage, usually from css or js files.
*
* @param name The placeholder's name. Required.
* @param value The placeholder's value. Required.
* @return This builder.
* @see
* PlaceholderProcessor
*/
public WebResourceOptimizer withPlaceholder(final String name,
final Boolean value) {
Validate.notEmpty(name, "The placeholder's name is required.");
Validate.notNull(value, "The placeholder's value is required.");
placeholders.setProperty(name, value.toString());
return this;
}
/**
* Publish a placeholder for later usage, usually from css or js files.
*
* @param name The placeholder's name. Required.
* @param value The placeholder's value. Required.
* @return This builder.
* @see
* PlaceholderProcessor
*/
public WebResourceOptimizer withPlaceholder(final String name,
final Integer value) {
Validate.notEmpty(name, "The placeholder's name is required.");
Validate.notNull(value, "The placeholder's value is required.");
placeholders.setProperty(name, value.toString());
return this;
}
/**
* Publish a placeholder for later usage, usually from css or js files.
*
* @param name The placeholder's name. Required.
* @param value The placeholder's value. Required.
* @return This builder.
* @see
* PlaceholderProcessor
*/
public WebResourceOptimizer withPlaceholder(final String name,
final Long value) {
Validate.notEmpty(name, "The placeholder's name is required.");
Validate.notNull(value, "The placeholder's value is required.");
placeholders.setProperty(name, value.toString());
return this;
}
/**
* Publish a placeholder for later usage, usually from css or js files.
*
* @param name The placeholder's name. Required.
* @param value The placeholder's value. Required.
* @return This builder.
* @see
* PlaceholderProcessor
*/
public WebResourceOptimizer withPlaceholder(final String name,
final Float value) {
Validate.notEmpty(name, "The placeholder's name is required.");
Validate.notNull(value, "The placeholder's value is required.");
placeholders.setProperty(name, value.toString());
return this;
}
/**
* Publish a placeholder for later usage, usually from css or js files.
*
* @param name The placeholder's name. Required.
* @param value The placeholder's value. Required.
* @return This builder.
* @see
* PlaceholderProcessor
*/
public WebResourceOptimizer withPlaceholder(final String name,
final Double value) {
Validate.notEmpty(name, "The placeholder's name is required.");
Validate.notNull(value, "The placeholder's value is required.");
placeholders.setProperty(name, value.toString());
return this;
}
/**
* Append a built-in pre-processor to the list.
*
* @param name The pre-processor's name. Required.
* @return This builder.
* @see
* AvailableProcessors
*/
public WebResourceOptimizer withPreProcessor(final String name) {
Validate.notEmpty(name, "The preprocessor's name is required.");
append(PARAM_PRE_PROCESSORS, name);
return this;
}
/**
* Append a custom pre-processor to the list.
*
* @param name The pre-processor's name. Required.
* @param processor The custom pre-processor. Required.
* @return This builder.
*/
public WebResourceOptimizer withPreProcessor(final String name,
final ResourcePreProcessor processor) {
Validate.notEmpty(name, "The preprocessor's name is required.");
Validate.notNull(processor, "The preprocessor's is required.");
preProcessors.put(name, processor);
withPreProcessor(name);
return this;
}
/**
* Append a post-processor to the list.
*
* @param name The preprocessor's name. Required.
* @return This builder.
* @see
* AvailableProcessors
*/
public WebResourceOptimizer withPostProcessor(final String name) {
Validate.notEmpty(name, "The preprocessor's name is required.");
append(PARAM_POST_PROCESSORS, name);
return this;
}
/**
* Append a custom post-processor to the list.
*
* @param name The post-processor's name. Required.
* @param processor The custom post-processor. Required.
* @return This builder.
*/
public WebResourceOptimizer withPostProcessor(final String name,
final ResourcePostProcessor processor) {
Validate.notEmpty(name, "The preprocessor's name is required.");
Validate.notNull(processor, "The preprocessor's is required.");
postProcessors.put(name, processor);
withPostProcessor(name);
return this;
}
/**
* Append a value and separate multiples values by comma.
*
* @param name The property's name.
* @param value The property's value.
*/
private void append(final String name, final String value) {
if (dev && noDevProccessors.contains(value)) {
return;
}
Iterable existingValues = Splitter.on(",")
.omitEmptyStrings()
.trimResults()
.split(factoryProperties.getProperty(name, ""));
Collection newValues = new LinkedHashSet();
for (String existingValue : existingValues) {
newValues.add(existingValue);
}
newValues.add(value);
factoryProperties.put(name, Joiner.on(",").join(newValues));
}
/**
* Build a new {@link WroFilter} ready for development or production
* environments.
*
* @return A new {@link WroFilter} ready for development or production
* environments.
*/
public WroFilter build() {
// Let's create a factory.
ExtensionsConfigurableWroManagerFactory factory =
new ExtensionsConfigurableWroManagerFactory() {
@Override
protected UriLocatorFactory newUriLocatorFactory() {
final SimpleUriLocatorFactory factory =
new SimpleUriLocatorFactory();
factory.addUriLocator(new UriLocator() {
@Override
public boolean accept(final String uri) {
return uri.trim().startsWith("/");
}
@Override
public InputStream locate(final String uri) throws IOException {
final ServletContext servletContext =
Context.get().getServletContext();
InputStream stream = servletContext.getResourceAsStream(uri);
if (stream == null) {
throw new IOException(
"Exception while reading resource from " + uri);
}
return stream;
}
});
factory.addUriLocator(new ClasspathUriLocator());
factory.addUriLocator(new UrlUriLocator());
return factory;
}
@Override
protected Properties newConfigProperties() {
return factoryProperties;
}
@Override
protected void contributePostProcessors(
final Map map) {
super.contributePostProcessors(map);
map.putAll(postProcessors);
}
@Override
protected void contributePreProcessors(
final Map map) {
super.contributePreProcessors(map);
map.putAll(preProcessors);
}
};
factory.setModelFactory(wroModelFactory);
// Now the filter.
ConfigurableWroFilter filter = new ConfigurableWroFilter() {
@Override
protected void onRuntimeException(final RuntimeException e,
final HttpServletResponse response, final FilterChain chain) {
try {
HttpServletRequest request = Context.get().getRequest();
WroProblemReporter reporter = WroProblemReporter
.bestFor(request.getRequestURI(), e);
if (reporter != null) {
reporter.report((WroRuntimeException) e, request, response);
} else {
// go to the default behavior.
super.onRuntimeException(e, response, chain);
}
} catch (IOException ex) {
// go to the default behavior.
super.onRuntimeException(e, response, chain);
}
}
};
filter.setProperties(configProperties);
filter.setWroManagerFactory(factory);
return filter;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy