001/* 002 * Copyright 2018 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.core.ui.resources.css.sass; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.io.OutputStream; 021import java.io.Serializable; 022import java.net.URISyntaxException; 023import java.nio.charset.StandardCharsets; 024import java.util.Map; 025 026import org.apache.avalon.framework.context.Context; 027import org.apache.avalon.framework.context.ContextException; 028import org.apache.avalon.framework.context.Contextualizable; 029import org.apache.avalon.framework.parameters.Parameters; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.cocoon.ProcessingException; 033import org.apache.cocoon.components.ContextHelper; 034import org.apache.commons.io.FilenameUtils; 035import org.apache.commons.io.IOUtils; 036import org.apache.commons.lang3.StringUtils; 037import org.apache.excalibur.source.Source; 038 039import org.ametys.core.minimize.SourceMapCache; 040import org.ametys.core.minimize.css.sass.MinimizeSassManager; 041import org.ametys.core.resources.ProxiedContextPathProvider; 042import org.ametys.core.resources.ResourceHandlerProvider; 043import org.ametys.core.resources.SimpleResourceHandler; 044import org.ametys.plugins.core.ui.resources.css.JSASSResourceURIExtensionPoint; 045 046import io.bit3.jsass.CompilationException; 047 048/** 049 * Resource handler for source map of compiled sass files 050 */ 051public class SassSourceMapResourceHandler extends SimpleResourceHandler implements Contextualizable 052{ 053 /** sassMinimizeManager */ 054 protected MinimizeSassManager _sassMinimizeManager; 055 056 private boolean _minimize; 057 private String _sourceMapLocation; 058 059 private SourceMapCache _sourceMapCache; 060 private JSASSResourceURIExtensionPoint _jsassResourceURIExtensionPoint; 061 private ProxiedContextPathProvider _proxiedContextPathProvider; 062 063 private Context _context; 064 065 /** 066 * Called 067 */ 068 public SassSourceMapResourceHandler() 069 { 070 super(); 071 } 072 073 /** 074 * If the {@link Source} is already resolved by the {@link ResourceHandlerProvider}, 075 * it may provide it through the constructor to avoid resolving it again. 076 * @param source the source. 077 */ 078 public SassSourceMapResourceHandler(Source source) 079 { 080 super(source); 081 } 082 083 public void contextualize(Context context) throws ContextException 084 { 085 _context = context; 086 } 087 088 @Override 089 public void service(ServiceManager manager) throws ServiceException 090 { 091 super.service(manager); 092 093 _sourceMapCache = (SourceMapCache) manager.lookup(SourceMapCache.ROLE); 094 _sassMinimizeManager = (MinimizeSassManager) manager.lookup(MinimizeSassManager.ROLE); 095 _jsassResourceURIExtensionPoint = (JSASSResourceURIExtensionPoint) manager.lookup(JSASSResourceURIExtensionPoint.ROLE); 096 _proxiedContextPathProvider = (ProxiedContextPathProvider) manager.lookup(ProxiedContextPathProvider.ROLE); 097 } 098 099 @Override 100 public Source setup(String location, Map objectModel, Parameters parameters, boolean readForDownload) throws IOException, ProcessingException 101 { 102 _sourceMapLocation = location; 103 104 // example: plugin:core-ui://resources/example/test.scss or plugin:core-ui://resources/example/test.css 105 String originalSassLocation = StringUtils.removeEnd(location, ".map"); 106 107 return super.setup(originalSassLocation, objectModel, parameters, readForDownload); 108 } 109 110 @Override 111 public Serializable getKey() 112 { 113 return super.getKey() + ".map" + (_minimize ? "*min" : ""); 114 } 115 116 @Override 117 public void generate(OutputStream out) throws IOException, ProcessingException 118 { 119 // Check if the source map is in cache 120 121 // example: plugin:core-ui://resources/example/test.scss.map or plugin:core-ui://resources/example/test.css.map 122 Source cachedSourceMap = null; 123 try 124 { 125 // example: /plugins/core-ui/resources/example/test.scss.map or /plugins/core-ui/resources/example/test.css.map 126 String sourceMapCacheKey = _jsassResourceURIExtensionPoint.resolve(_sourceMapLocation); 127 cachedSourceMap = _sourceMapCache.get(sourceMapCacheKey); 128 } 129 catch (URISyntaxException e) 130 { 131 getLogger().error("Unable to resolve URI location because URI syntax '" + _sourceMapLocation + "' is not supported", e); 132 } 133 134 if (cachedSourceMap != null) 135 { 136 try (InputStream is = cachedSourceMap.getInputStream()) 137 { 138 IOUtils.copy(is, out); 139 return; 140 } 141 } 142 143 // Generate the source map 144 try (InputStream is = _source.getInputStream()) 145 { 146 String sassContent = IOUtils.toString(is, StandardCharsets.UTF_8); 147 String extension = FilenameUtils.getExtension(_source.getURI()); 148 String sourceMap = _sassMinimizeManager.generateCssSourceMap(sassContent, _sourceMapLocation, extension, ContextHelper.getRequest(_context).getContextPath(), _proxiedContextPathProvider.getContextPath(), _minimize, _source.getLastModified()); 149 IOUtils.write(sourceMap, out, "UTF-8"); 150 } 151 catch (CompilationException | URISyntaxException e) 152 { 153 throw new ProcessingException("Unable to compile the SASS file: " + _source.getURI(), e); 154 } 155 } 156}