package com.dragome.web.helpers.serverside; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Properties; import javax.servlet.FilterConfig; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.dragome.web.serverside.servlets.DragomeWroModelFactory; import ro.isdc.wro.config.Context; import ro.isdc.wro.config.jmx.WroConfiguration; import ro.isdc.wro.http.support.DelegatingServletOutputStream; import ro.isdc.wro.manager.WroManager; import ro.isdc.wro.manager.factory.WroManagerFactory; import ro.isdc.wro.manager.factory.standalone.StandaloneContext; import ro.isdc.wro.manager.factory.standalone.StandaloneContextAware; import ro.isdc.wro.model.WroModel; import ro.isdc.wro.model.WroModelInspector; import ro.isdc.wro.model.factory.WroModelFactory; import ro.isdc.wro.model.resource.ResourceType; import ro.isdc.wro.model.resource.processor.factory.ProcessorsFactory; import ro.isdc.wro.util.io.UnclosableBufferedInputStream; public class Wro4jStandaloneRunner { private static final Logger LOG= LoggerFactory.getLogger(Wro4jStandaloneRunner.class); private static String userDirectory= System.getProperty("user.dir"); private final File defaultWroFile= newDefaultWroFile(); private Properties wroConfigurationAsProperties; private String targetGroups; private File contextFolder= new File("."); private File destinationFolder= new File("."); private boolean parallelPreprocessing; private ProcessorsFactory processorsFactory; private boolean minimize; private WroModelFactory wroModelFactory; private File wroFile; private WroConfiguration config; private WroManagerFactory managerFactory; private boolean ignoreMissingResources; public Wro4jStandaloneRunner(WroConfiguration config, WroManagerFactory managerFactory, File destinationDirectory) { super(); this.config= config; this.managerFactory= managerFactory; this.destinationFolder= destinationDirectory; } protected File newDefaultWroFile() { return new File(userDirectory, "wro.xml"); } protected File newWroConfigurationFile() { return new File(userDirectory, "wro.properties"); } protected File getContextFolder() { return contextFolder; } protected File getDestinationFolder() { return contextFolder; } protected void onRunnerException(final Exception e) { System.out.println(e.getMessage()); System.exit(1); // non-zero exit code indicates there was an error } public void process() { try { Context.set(Context.standaloneContext()); // create destinationFolder if needed if (!destinationFolder.exists()) { destinationFolder.mkdirs(); } final Collection<String> groupsAsList= getTargetGroupsAsList(); for (final String group : groupsAsList) { for (final ResourceType resourceType : ResourceType.values()) { final String groupWithExtension= group + "." + resourceType.name().toLowerCase(); processGroup(groupWithExtension, destinationFolder); } } } catch (final IOException e) { System.err.println(e.getMessage()); } } /** * @return a list containing all groups needs to be processed. */ private List<String> getTargetGroupsAsList() throws IOException { if (targetGroups == null) { final WroModel model= managerFactory.create().getModelFactory().create(); return new WroModelInspector(model).getGroupNames(); } return Arrays.asList(targetGroups.split(",")); } /** * Process a single group. * * @throws IOException * if any IO related exception occurs. */ private void processGroup(final String group, final File parentFoder) throws IOException { final ByteArrayOutputStream resultOutputStream= new ByteArrayOutputStream(); InputStream resultInputStream= null; try { LOG.info("processing group: " + group); initContext(group, resultOutputStream); doProcess(); // encode version & write result to file resultInputStream= new UnclosableBufferedInputStream(resultOutputStream.toByteArray()); final File destinationFile= new File(parentFoder, rename(group, resultInputStream)); destinationFile.createNewFile(); // allow the same stream to be read again resultInputStream.reset(); LOG.debug("Created file: {}", destinationFile.getName()); final OutputStream fos= new FileOutputStream(destinationFile); // use reader to detect encoding IOUtils.copy(resultInputStream, fos); fos.close(); // delete empty files if (destinationFile.length() == 0) { LOG.debug("No content found for group: {}", group); destinationFile.delete(); } else { LOG.info("file size: {} -> {}bytes", destinationFile.getName(), destinationFile.length()); LOG.info("{} ({}bytes) has been created!", destinationFile.getAbsolutePath(), destinationFile.length()); } } finally { if (resultOutputStream != null) { resultOutputStream.close(); } if (resultInputStream != null) { resultInputStream.close(); } } } /** * Initialize the context for standalone execution. */ private void initContext(final String group, final ByteArrayOutputStream resultOutputStream) throws IOException { final HttpServletRequest request= (HttpServletRequest) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { HttpServletRequest.class }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("getRequestURI")) return group; return null; } }); final HttpServletResponse response= (HttpServletResponse) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { HttpServletResponse.class }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("getOutputStream")) return new DelegatingServletOutputStream(resultOutputStream); return null; } }); final FilterConfig filterConfig= (FilterConfig) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { FilterConfig.class }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }); Context.set(Context.webContext(request, response, filterConfig), initWroConfiguration()); Context.get().setAggregatedFolderPath(computeAggregatedFolderPath()); } /** * Perform actual processing by delegating the process call to {@link WroManager}. * * @throws IOException * @VisibleForTesting */ void doProcess() throws IOException { // perform processing getManagerFactory().create().process(); } private WroConfiguration initWroConfiguration() throws IOException { return config; } /** * This implementation is similar to the one from Wro4jMojo. TODO: reuse if possible. */ private String computeAggregatedFolderPath() { Validate.notNull(destinationFolder, "DestinationFolder cannot be null!"); Validate.notNull(getContextFolder(), "ContextFolder cannot be null!"); final File cssTargetFolder= destinationFolder; File rootFolder= null; if (cssTargetFolder.getPath().startsWith(getContextFolder().getPath())) { rootFolder= getContextFolder(); } // compute aggregatedFolderPath String aggregatedFolderPath= null; if (rootFolder != null) { aggregatedFolderPath= StringUtils.removeStart(cssTargetFolder.getPath(), rootFolder.getPath()); } LOG.debug("aggregatedFolderPath: {}", aggregatedFolderPath); return aggregatedFolderPath; } /** * Encodes a version using some logic. * * @param group * the name of the resource to encode. * @param input * the stream of the result content. * @return the name of the resource with the version encoded. */ private String rename(final String group, final InputStream input) throws IOException { return getManagerFactory().create().getNamingStrategy().rename(group, input); } /** * This method will ensure that you have a right and initialized instance of {@link StandaloneContextAware}. */ private WroManagerFactory getManagerFactory() throws IOException { return managerFactory; } /** * Creates a {@link StandaloneContext} by setting properties passed after mojo is initialized. */ private StandaloneContext createStandaloneContext() { final StandaloneContext runContext= new StandaloneContext(); runContext.setContextFoldersAsCSV(getContextFolder().getPath()); runContext.setMinimize(minimize); runContext.setWroFile(wroFile); runContext.setIgnoreMissingResourcesAsString(Boolean.toString(ignoreMissingResources)); return runContext; } /** * @param destinationFolder * the destinationFolder to set * @VisibleForTestOnly */ void setDestinationFolder(final File destinationFolder) { this.destinationFolder= destinationFolder; } }