001
014
015 package com.liferay.portal.servlet;
016
017 import com.liferay.portal.kernel.cache.PortalCache;
018 import com.liferay.portal.kernel.cache.SingleVMPoolUtil;
019 import com.liferay.portal.kernel.log.Log;
020 import com.liferay.portal.kernel.log.LogFactoryUtil;
021 import com.liferay.portal.kernel.servlet.HttpHeaders;
022 import com.liferay.portal.kernel.servlet.ServletContextUtil;
023 import com.liferay.portal.kernel.servlet.ServletResponseUtil;
024 import com.liferay.portal.kernel.util.CharPool;
025 import com.liferay.portal.kernel.util.ContentTypes;
026 import com.liferay.portal.kernel.util.FileUtil;
027 import com.liferay.portal.kernel.util.ParamUtil;
028 import com.liferay.portal.kernel.util.PropsKeys;
029 import com.liferay.portal.kernel.util.StringPool;
030 import com.liferay.portal.kernel.util.StringUtil;
031 import com.liferay.portal.kernel.util.Time;
032 import com.liferay.portal.kernel.util.Validator;
033 import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
034 import com.liferay.portal.util.MinifierUtil;
035 import com.liferay.portal.util.PortalUtil;
036 import com.liferay.portal.util.PrefsPropsUtil;
037 import com.liferay.portal.util.PropsValues;
038
039 import java.io.File;
040 import java.io.IOException;
041 import java.io.Serializable;
042
043 import java.util.Arrays;
044 import java.util.LinkedHashSet;
045 import java.util.Set;
046
047 import javax.servlet.ServletContext;
048 import javax.servlet.ServletException;
049 import javax.servlet.http.HttpServlet;
050 import javax.servlet.http.HttpServletRequest;
051 import javax.servlet.http.HttpServletResponse;
052
053
058 public class ComboServlet extends HttpServlet {
059
060 @Override
061 public void service(
062 HttpServletRequest request, HttpServletResponse response)
063 throws IOException, ServletException {
064
065 try {
066 doService(request, response);
067 }
068 catch (Exception e) {
069 _log.error(e, e);
070
071 PortalUtil.sendError(
072 HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e, request,
073 response);
074 }
075 }
076
077 protected void doService(
078 HttpServletRequest request, HttpServletResponse response)
079 throws Exception {
080
081 String contextPath = PortalUtil.getPathContext();
082
083 String[] modulePaths = request.getParameterValues("m");
084
085 if ((modulePaths == null) || (modulePaths.length == 0)) {
086 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
087
088 return;
089 }
090
091 Set<String> modulePathsSet = new LinkedHashSet<String>(
092 modulePaths.length);
093
094 for (String path : modulePaths) {
095 modulePathsSet.add(path);
096 }
097
098 modulePaths = modulePathsSet.toArray(new String[modulePathsSet.size()]);
099
100 String modulePathsString = null;
101
102 byte[][] bytesArray = null;
103
104 if (!PropsValues.COMBO_CHECK_TIMESTAMP) {
105 modulePathsString = Arrays.toString(modulePaths);
106
107 bytesArray = (byte[][])_bytesArrayPortalCache.get(
108 modulePathsString);
109 }
110
111 String firstModulePath = modulePaths[0];
112
113 String extension = FileUtil.getExtension(firstModulePath);
114
115 if (bytesArray == null) {
116 String p = ParamUtil.getString(request, "p");
117
118 String minifierType = ParamUtil.getString(request, "minifierType");
119
120 if (Validator.isNull(minifierType)) {
121 minifierType = "js";
122
123 if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
124 minifierType = "css";
125 }
126 }
127
128 if (!minifierType.equals("css") && !minifierType.equals("js")) {
129 minifierType = "js";
130 }
131
132 bytesArray = new byte[modulePaths.length][];
133
134 for (int i = 0; i < modulePaths.length; i++) {
135 String modulePath = modulePaths[i];
136
137 if (!validateModuleExtension(modulePath)) {
138 response.setHeader(
139 HttpHeaders.CACHE_CONTROL,
140 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
141 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
142
143 return;
144 }
145
146 byte[] bytes = new byte[0];
147
148 if (Validator.isNotNull(modulePath)) {
149 modulePath = StringUtil.replaceFirst(
150 p.concat(modulePath), contextPath, StringPool.BLANK);
151
152 if (getFile(modulePath) == null) {
153 response.setHeader(
154 HttpHeaders.CACHE_CONTROL,
155 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
156 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
157
158 return;
159 }
160
161 bytes = getFileContent(
162 request, response, modulePath, minifierType);
163 }
164
165 bytesArray[i] = bytes;
166 }
167
168 if ((modulePathsString != null) &&
169 !PropsValues.COMBO_CHECK_TIMESTAMP) {
170
171 _bytesArrayPortalCache.put(modulePathsString, bytesArray);
172 }
173 }
174
175 String contentType = ContentTypes.TEXT_JAVASCRIPT;
176
177 if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
178 contentType = ContentTypes.TEXT_CSS;
179 }
180
181 response.setContentType(contentType);
182
183 ServletResponseUtil.write(response, bytesArray);
184 }
185
186 protected File getFile(String path) throws IOException {
187 ServletContext servletContext = getServletContext();
188
189 String basePath = ServletContextUtil.getRealPath(
190 servletContext, _JAVASCRIPT_DIR);
191
192 if (basePath == null) {
193 return null;
194 }
195
196 basePath = StringUtil.replace(
197 basePath, CharPool.BACK_SLASH, CharPool.SLASH);
198
199 File baseDir = new File(basePath);
200
201 if (!baseDir.exists()) {
202 return null;
203 }
204
205 String filePath = ServletContextUtil.getRealPath(servletContext, path);
206
207 if (filePath == null) {
208 return null;
209 }
210
211 filePath = StringUtil.replace(
212 filePath, CharPool.BACK_SLASH, CharPool.SLASH);
213
214 File file = new File(filePath);
215
216 if (!file.exists()) {
217 return null;
218 }
219
220 String baseCanonicalPath = baseDir.getCanonicalPath();
221 String fileCanonicalPath = file.getCanonicalPath();
222
223 if (fileCanonicalPath.indexOf(baseCanonicalPath) == 0) {
224 return file;
225 }
226
227 return null;
228 }
229
230 protected byte[] getFileContent(
231 HttpServletRequest request, HttpServletResponse response,
232 String path, String minifierType)
233 throws IOException {
234
235 String fileContentKey = path.concat(StringPool.QUESTION).concat(
236 minifierType);
237
238 FileContentBag fileContentBag =
239 (FileContentBag)_fileContentBagPortalCache.get(fileContentKey);
240
241 if ((fileContentBag != null) && !PropsValues.COMBO_CHECK_TIMESTAMP) {
242 return fileContentBag._fileContent;
243 }
244
245 File file = getFile(path);
246
247 if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
248 long elapsedTime =
249 System.currentTimeMillis() - fileContentBag._lastModified;
250
251 if ((file != null) &&
252 (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
253 (file.lastModified() == fileContentBag._lastModified)) {
254
255 return fileContentBag._fileContent;
256 }
257 else {
258 _fileContentBagPortalCache.remove(fileContentKey);
259 }
260 }
261
262 if (file == null) {
263 fileContentBag = _EMPTY_FILE_CONTENT_BAG;
264 }
265 else {
266 String stringFileContent = FileUtil.read(file);
267
268 if (!StringUtil.endsWith(path, _CSS_MINIFIED_SUFFIX) &&
269 !StringUtil.endsWith(path, _JAVASCRIPT_MINIFIED_SUFFIX)) {
270
271 if (minifierType.equals("css")) {
272 String cssRealPath = file.getAbsolutePath();
273
274 try {
275 stringFileContent = DynamicCSSUtil.parseSass(
276 request, cssRealPath, stringFileContent);
277 }
278 catch (Exception e) {
279 _log.error(
280 "Unable to parse SASS on CSS " + cssRealPath, e);
281
282 if (_log.isDebugEnabled()) {
283 _log.debug(stringFileContent);
284 }
285
286 response.setHeader(
287 HttpHeaders.CACHE_CONTROL,
288 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
289 }
290
291 stringFileContent = MinifierUtil.minifyCss(
292 stringFileContent);
293 }
294 else if (minifierType.equals("js")) {
295 stringFileContent = MinifierUtil.minifyJavaScript(
296 stringFileContent);
297 }
298 }
299
300 fileContentBag = new FileContentBag(
301 stringFileContent.getBytes(StringPool.UTF8),
302 file.lastModified());
303 }
304
305 if (PropsValues.COMBO_CHECK_TIMESTAMP) {
306 int timeToLive =
307 (int)(PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL / Time.SECOND);
308
309 _fileContentBagPortalCache.put(
310 fileContentKey, fileContentBag, timeToLive);
311 }
312
313 return fileContentBag._fileContent;
314 }
315
316 protected boolean validateModuleExtension(String moduleName)
317 throws Exception {
318
319 boolean validModuleExtension = false;
320
321 String[] fileExtensions = PrefsPropsUtil.getStringArray(
322 PropsKeys.COMBO_ALLOWED_FILE_EXTENSIONS, StringPool.COMMA);
323
324 for (int i = 0; i < fileExtensions.length; i++) {
325 if (StringPool.STAR.equals(fileExtensions[i]) ||
326 StringUtil.endsWith(moduleName, fileExtensions[i])) {
327
328 validModuleExtension = true;
329
330 break;
331 }
332 }
333
334 return validModuleExtension;
335 }
336
337 private static final String _CSS_EXTENSION = "css";
338
339 private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
340
341 private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
342 new FileContentBag(new byte[0], 0);
343
344 private static final String _JAVASCRIPT_DIR = "html/js";
345
346 private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
347
348 private static Log _log = LogFactoryUtil.getLog(ComboServlet.class);
349
350 private PortalCache _bytesArrayPortalCache = SingleVMPoolUtil.getCache(
351 ComboServlet.class.getName());
352 private PortalCache _fileContentBagPortalCache = SingleVMPoolUtil.getCache(
353 FileContentBag.class.getName());
354
355 private static class FileContentBag implements Serializable {
356
357 public FileContentBag(byte[] fileContent, long lastModifiedTime) {
358 _fileContent = fileContent;
359 _lastModified = lastModifiedTime;
360 }
361
362 private byte[] _fileContent;
363 private long _lastModified;
364
365 }
366
367 }