/** * Copyright 2011 meltmedia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xchain.framework.javascript; import static org.xchain.framework.util.IoUtil.close; import static org.xchain.framework.util.IoUtil.copyStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.dojotoolkit.shrinksafe.Compressor; import org.mozilla.javascript.Context; import org.mozilla.javascript.ContextAction; import org.mozilla.javascript.tools.ToolErrorReporter; import org.mozilla.javascript.tools.shell.Global; import org.mozilla.javascript.tools.shell.ShellContextFactory; /** * This class is essentially a reimplementation of <code>org.dojotoolkit.shrinksafe.Main</code> for using * <code>org.dojotoolkit.shrinksafe.Compressor</code>. * * @author John Trimble * @author Josh Kennedy */ public class JsCompressor { private static Logger log = LoggerFactory.getLogger(JsCompressor.class); private static int DEFAULT_OPTIMIZATION_LEVEL = 0; private boolean respectNoCompressFlag = true; private int optimizationLevel; public JsCompressor() { this(DEFAULT_OPTIMIZATION_LEVEL); } public JsCompressor(int optimizationLevel) { this.optimizationLevel = optimizationLevel; } /** * Reads each URL in <code>javaScriptUrls</code>, in order, compresses the data and streams the result to the * <code>output</code> stream. Does not close <code>output</code>. * * @param javaScriptUrls - An array of URLs pointing to valid Java Script content. * @param output - The stream to which the compressed content is written. * @throws IOException */ public void compress(URL[] javaScriptUrls, OutputStream output) throws IOException { // Copied from org.dojotoolkit.shrinksafe.Main Global global = new Global(); ShellContextFactory shellContextFactory = new ShellContextFactory(); shellContextFactory.setOptimizationLevel(optimizationLevel); ToolErrorReporter errorReporter = new ToolErrorReporter(false, global.getErr()); shellContextFactory.setErrorReporter(errorReporter); IProxy iproxy = createCompressProxy(javaScriptUrls, output); global.init(shellContextFactory); shellContextFactory.call(iproxy); if( iproxy.hasException() ) { if( iproxy.getException() instanceof IOException ) throw (IOException) iproxy.getException(); else if( iproxy.getException() instanceof RuntimeException ) throw (RuntimeException) iproxy.getException(); else throw new RuntimeException(iproxy.getException()); } } /** * Creates an IProxy object for calling <code>compressJsUrlArray</code> inside the Rhino Java Script engine. * @param javaScriptUrls * @param output * @return */ private IProxy createCompressProxy(final URL[] javaScriptUrls, final OutputStream output) { IProxy iproxy = new IProxy() { public Object execute(Context cx) throws Exception { compressJsUrlArray(cx, javaScriptUrls, output); return null; } }; return iproxy; } /** * Reads the content of <code>url</code> and stores it into a returned string. * @param url * @return * @throws IOException */ private static String readContent(URL url) throws IOException { InputStream in = null; ByteArrayOutputStream out = null; try { in = url.openConnection().getInputStream(); out = new ByteArrayOutputStream(); copyStream(in, out, 2048); return out.toString(); } finally { close(in, log); close(out, log); } } /** * Returns whether or not the given URL should be compressed. This returns true if the query portion of the url does * not contain "compress=false" or the <code>respectNoCompressFlag</code> is false; otherwise, false is returned. * @param url * @return */ private boolean shouldCompress(URL url) { String query = url.getQuery(); // This is not the best way to check a parameter, but if it works... return query == null || !respectNoCompressFlag || !(query.toLowerCase().contains("compress=false")); } /** * Compresses each URL in <code>jsUrls</code> by using <code>org.dojotoolkit.shrinksafe.Compressor</code>. Streams * result to <code>output</code>. Must be called from within the Rhino Java Script Engine. * * @param cx * @param jsUrls * @param output * @throws IOException */ private void compressJsUrlArray(Context cx, URL[] jsUrls, OutputStream output) throws IOException { for( int i = 0; i < jsUrls.length; i++ ) { URL jsUrl = jsUrls[i]; String data = readContent(jsUrl); String compressedData = data; if( shouldCompress(jsUrl) ) { if( log.isDebugEnabled() ) log.debug("Compressing URL: "+jsUrl); compressedData = Compressor.compressScript(data, 0, 1, false, "normal"); } else if( log.isDebugEnabled() ) log.debug("Not compressing URL: "+jsUrl); ByteArrayInputStream sin = null; try { sin = new ByteArrayInputStream(compressedData.getBytes()); copyStream(sin, output, 2048); } finally { close(sin, log); } } } /** * Proxy class for calling a java method from within the Rhino JavaScript engine. */ private static abstract class IProxy implements ContextAction { private Exception exception; protected abstract Object execute(Context cx) throws Exception; public Exception getException() { return this.exception; } public boolean hasException() { return this.exception != null; } public Object run(Context cx) { this.exception = null; Object result; try { result = this.execute(cx); return result; } catch (Exception e) { this.exception = e; return null; } } } }