001
014
015 package com.liferay.portal.servlet;
016
017 import com.liferay.portal.kernel.log.Log;
018 import com.liferay.portal.kernel.log.LogFactoryUtil;
019 import com.liferay.portal.kernel.servlet.HttpHeaders;
020 import com.liferay.portal.kernel.servlet.ServletContextUtil;
021 import com.liferay.portal.kernel.servlet.ServletResponseUtil;
022 import com.liferay.portal.kernel.util.CharPool;
023 import com.liferay.portal.kernel.util.ContentTypes;
024 import com.liferay.portal.kernel.util.FileUtil;
025 import com.liferay.portal.kernel.util.ParamUtil;
026 import com.liferay.portal.kernel.util.StringPool;
027 import com.liferay.portal.kernel.util.StringUtil;
028 import com.liferay.portal.kernel.util.Validator;
029 import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
030 import com.liferay.portal.util.MinifierUtil;
031 import com.liferay.portal.util.PortalUtil;
032 import com.liferay.portal.util.PropsValues;
033
034 import java.io.File;
035 import java.io.IOException;
036
037 import java.util.Arrays;
038 import java.util.concurrent.ConcurrentHashMap;
039 import java.util.concurrent.ConcurrentMap;
040
041 import javax.servlet.ServletContext;
042 import javax.servlet.http.HttpServlet;
043 import javax.servlet.http.HttpServletRequest;
044 import javax.servlet.http.HttpServletResponse;
045
046
050 public class ComboServlet extends HttpServlet {
051
052 @Override
053 public void service(
054 HttpServletRequest request, HttpServletResponse response)
055 throws IOException {
056
057 String contextPath = PortalUtil.getPathContext();
058
059 String[] modulePaths = request.getParameterValues("m");
060
061 if ((modulePaths == null) || (modulePaths.length == 0)) {
062 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
063
064 return;
065 }
066
067 Arrays.sort(modulePaths);
068
069 String modulePathsString = null;
070
071 byte[][] bytesArray = null;
072
073 if (!PropsValues.COMBO_CHECK_TIMESTAMP) {
074 modulePathsString = Arrays.toString(modulePaths);
075
076 bytesArray = _byteArrays.get(modulePathsString);
077 }
078
079 String firstModulePath = modulePaths[0];
080
081 String extension = FileUtil.getExtension(firstModulePath);
082
083 if (bytesArray == null) {
084 String p = ParamUtil.getString(request, "p");
085
086 String minifierType = ParamUtil.getString(request, "minifierType");
087
088 if (Validator.isNull(minifierType)) {
089 minifierType = "js";
090
091 if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
092 minifierType = "css";
093 }
094 }
095
096 int length = modulePaths.length;
097
098 bytesArray = new byte[length][];
099
100 for (String modulePath : modulePaths) {
101 byte[] bytes = new byte[0];
102
103 if (Validator.isNotNull(modulePath)) {
104 modulePath = StringUtil.replaceFirst(
105 p.concat(modulePath), contextPath, StringPool.BLANK);
106
107 bytes = getFileContent(
108 request, response, modulePath, minifierType);
109 }
110
111 bytesArray[--length] = bytes;
112 }
113
114 if (modulePathsString != null) {
115 _byteArrays.put(modulePathsString, bytesArray);
116 }
117 }
118
119 String contentType = ContentTypes.TEXT_JAVASCRIPT;
120
121 if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
122 contentType = ContentTypes.TEXT_CSS;
123 }
124
125 response.setContentType(contentType);
126
127 ServletResponseUtil.write(response, bytesArray);
128 }
129
130 protected File getFile(String path) throws IOException {
131 ServletContext servletContext = getServletContext();
132
133 String basePath = ServletContextUtil.getRealPath(
134 servletContext, _JAVASCRIPT_DIR);
135
136 if (basePath == null) {
137 return null;
138 }
139
140 basePath = StringUtil.replace(
141 basePath, CharPool.BACK_SLASH, CharPool.SLASH);
142
143 File baseDir = new File(basePath);
144
145 if (!baseDir.exists()) {
146 return null;
147 }
148
149 String filePath = ServletContextUtil.getRealPath(servletContext, path);
150
151 if (filePath == null) {
152 return null;
153 }
154
155 filePath = StringUtil.replace(
156 filePath, CharPool.BACK_SLASH, CharPool.SLASH);
157
158 File file = new File(filePath);
159
160 if (!file.exists()) {
161 return null;
162 }
163
164 String baseCanonicalPath = baseDir.getCanonicalPath();
165 String fileCanonicalPath = file.getCanonicalPath();
166
167 if (fileCanonicalPath.indexOf(baseCanonicalPath) == 0) {
168 return file;
169 }
170
171 return null;
172 }
173
174 protected byte[] getFileContent(
175 HttpServletRequest request, HttpServletResponse response,
176 String path, String minifierType)
177 throws IOException {
178
179 String fileContentKey = path.concat(StringPool.QUESTION).concat(
180 minifierType);
181
182 FileContentBag fileContentBag = _fileContentBags.get(fileContentKey);
183
184 if ((fileContentBag != null) &&
185 !PropsValues.COMBO_CHECK_TIMESTAMP) {
186
187 return fileContentBag._fileContent;
188 }
189
190 File file = getFile(path);
191
192 if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
193 long elapsedTime =
194 System.currentTimeMillis() - fileContentBag._lastModified;
195
196 if ((file != null) &&
197 (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
198 (file.lastModified() == fileContentBag._lastModified)) {
199
200 return fileContentBag._fileContent;
201 }
202 else {
203 _fileContentBags.remove(fileContentKey, fileContentBag);
204 }
205 }
206
207 if (file == null) {
208 fileContentBag = _EMPTY_FILE_CONTENT_BAG;
209 }
210 else {
211 String stringFileContent = FileUtil.read(file);
212
213 if (!StringUtil.endsWith(path, _CSS_MINIFIED_SUFFIX) &&
214 !StringUtil.endsWith(path, _JAVASCRIPT_MINIFIED_SUFFIX)) {
215
216 if (minifierType.equals("css")) {
217 String cssRealPath = file.getAbsolutePath();
218
219 try {
220 stringFileContent = DynamicCSSUtil.parseSass(
221 request, cssRealPath, stringFileContent);
222 }
223 catch (Exception e) {
224 _log.error(
225 "Unable to parse SASS on CSS " + cssRealPath, e);
226
227 if (_log.isDebugEnabled()) {
228 _log.debug(stringFileContent);
229 }
230
231 response.setHeader(
232 HttpHeaders.CACHE_CONTROL,
233 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
234 }
235
236 stringFileContent = MinifierUtil.minifyCss(
237 stringFileContent);
238 }
239 else if (minifierType.equals("js")) {
240 stringFileContent = MinifierUtil.minifyJavaScript(
241 stringFileContent);
242 }
243 }
244
245 fileContentBag = new FileContentBag(
246 stringFileContent.getBytes(StringPool.UTF8),
247 file.lastModified());
248 }
249
250 FileContentBag oldFileContentBag = _fileContentBags.putIfAbsent(
251 fileContentKey, fileContentBag);
252
253 if (oldFileContentBag != null) {
254 fileContentBag = oldFileContentBag;
255 }
256
257 return fileContentBag._fileContent;
258 }
259
260 private static final String _CSS_EXTENSION = "css";
261
262 private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
263
264 private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
265 new FileContentBag(new byte[0], 0);
266
267 private static final String _JAVASCRIPT_DIR = "html/js";
268
269 private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
270
271 private static Log _log = LogFactoryUtil.getLog(ComboServlet.class);
272
273 private static class FileContentBag {
274
275 public FileContentBag(byte[] fileContent, long lastModifiedTime) {
276 _fileContent = fileContent;
277 _lastModified = lastModifiedTime;
278 }
279
280 private byte[] _fileContent;
281 private long _lastModified;
282
283 }
284
285 private ConcurrentMap<String, byte[][]> _byteArrays =
286 new ConcurrentHashMap<String, byte[][]>();
287 private ConcurrentMap<String, FileContentBag> _fileContentBags =
288 new ConcurrentHashMap<String, FileContentBag>();
289
290 }