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