package ro.isdc.wro.extensions.processor.js;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.commons.io.output.WriterOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.SupportedResourceType;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.processor.SupportAware;
/**
* Uses ngmin node utility.
* <p/>
* Ngmin is a "pre-minifier" for AngularJS. It modifies Angular code to prevent errors that may arise from minification.
* <p/>
* More details can be found here: http://www.thinkster.io/pick/XlWneEZCqY/angularjs-ngmin
*
* @author Janek L.B
* @since 1.7.4
* @created 26 Jan 2014
*/
@SupportedResourceType(ResourceType.JS)
public class NgMinProcessor
implements ResourcePreProcessor, ResourcePostProcessor, SupportAware {
public static final String ALIAS = "ngMin";
private static final Logger LOG = LoggerFactory.getLogger(NgMinProcessor.class);
/**
* 'ngmin' must be available on the $PATH, and can be installed via npm: 'npm install -g ngmin'
*/
private static final String NGMIN_COMMAND = "ngmin";
@Override
public void process(final Resource resource, final Reader reader, final Writer writer)
throws IOException {
final OutputStream out = new WriterOutputStream(writer);
final InputStream in = new ReaderInputStream(reader);
try {
doProcess(in, out);
} catch (final IOException ex) {
final String resourceUri = resource == null ? StringUtils.EMPTY : "[" + resource.getUri() + "]";
LOG.warn("Exception while applying " + getClass().getSimpleName() + " processor on the " + resourceUri
+ " resource, no processing applied...", ex);
throw ex;
}
}
@Override
public void process(final Reader reader, final Writer writer)
throws IOException {
process(null, reader, writer);
}
/**
* @VisibleForTesting
*/
void doProcess(final InputStream in, final OutputStream out)
throws ExecuteException, IOException {
final ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
final Executor executor = new DefaultExecutor();
executor.setStreamHandler(new PumpStreamHandler(out, errorStream, in));
final int result = executor.execute(CommandLine.parse(NGMIN_COMMAND));
LOG.debug("result={}", result);
if (result != 0) {
throw new ExecuteException("Processing failed: " + new String(errorStream.toByteArray()), result);
}
}
@Override
public boolean isSupported() {
boolean supported = true;
try {
final InputStream input = new ByteArrayInputStream("".getBytes("UTF-8"));
doProcess(input, null);
} catch (final Exception e) {
LOG.debug("The {} processor is not supported. Because: {}", getClass().getName(), e.getMessage());
supported = false;
}
return supported;
}
}