001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.internal.webresources; 014 015import com.google.javascript.jscomp.*; 016import com.google.javascript.jscomp.Compiler; 017import org.apache.commons.io.IOUtils; 018import org.apache.tapestry5.TapestryConstants; 019import org.apache.tapestry5.ioc.OperationTracker; 020import org.apache.tapestry5.ioc.annotations.Symbol; 021import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 022import org.apache.tapestry5.ioc.internal.util.InternalUtils; 023import org.apache.tapestry5.services.Request; 024import org.apache.tapestry5.services.assets.AssetChecksumGenerator; 025import org.apache.tapestry5.services.assets.StreamableResource; 026import org.apache.tapestry5.webresources.WebResourcesSymbols; 027import org.slf4j.Logger; 028 029import java.io.IOException; 030import java.io.InputStream; 031import java.util.Collections; 032import java.util.List; 033import java.util.logging.Level; 034 035/** 036 * A wrapper around the Google Closure {@link Compiler} used to minimize 037 * a JavaScript resource. 038 */ 039public class GoogleClosureMinimizer extends AbstractMinimizer 040{ 041 042 private final static String OUTPUT_CHARSET = "utf-8"; 043 044 private final List<SourceFile> EXTERNS = Collections.emptyList(); 045 046 private final Request request; 047 private final CompilationLevel compilationLevel; 048 049 static 050 { 051 Compiler.setLoggingLevel(Level.SEVERE); 052 } 053 054 public GoogleClosureMinimizer(Logger logger, OperationTracker tracker, AssetChecksumGenerator checksumGenerator, Request request, 055 @Symbol(WebResourcesSymbols.COMPILATION_LEVEL) 056 CompilationLevel compilationLevel) 057 { 058 super(logger, tracker, checksumGenerator, "text/javascript"); 059 this.request = request; 060 this.compilationLevel = compilationLevel; 061 } 062 063 @Override 064 protected boolean isEnabled(StreamableResource resource) 065 { 066 return request.getAttribute(TapestryConstants.DISABLE_JAVASCRIPT_MINIMIZATION) == null; 067 } 068 069 @Override 070 protected InputStream doMinimize(StreamableResource resource) throws IOException 071 { 072 // Don't bother to pool the Compiler 073 074 CompilerOptions options = new CompilerOptions(); 075 076 compilationLevel.setOptionsForCompilationLevel(options); 077 078 options.setCodingConvention(new ClosureCodingConvention()); 079 options.setOutputCharset(OUTPUT_CHARSET); 080 options.setWarningLevel(DiagnosticGroups.CHECK_VARIABLES, CheckLevel.WARNING); 081 082 Compiler compiler = new Compiler(); 083 084 compiler.disableThreads(); 085 086 SourceFile input = SourceFile.fromInputStream(resource.toString(), resource.openStream()); 087 088 List<SourceFile> inputs = Collections.singletonList(input); 089 090 Result result = compiler.compile(EXTERNS, inputs, options); 091 092 if (result.success) 093 { 094 return IOUtils.toInputStream(compiler.toSource(), OUTPUT_CHARSET); 095 } 096 097 throw new RuntimeException(String.format("Compilation failed: %s.", 098 InternalUtils.join(CollectionFactory.newList(result.errors), ";"))); 099 } 100}