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 bytesArray = new byte[modulePaths.length][];
138
139 for (int i = 0; i < modulePaths.length; i++) {
140 String modulePath = modulePaths[i];
141
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 url = getResourceURL(
158 servletContext, rootPath, modulePath);
159
160 if (url == 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, url, modulePath, minifierType);
171 }
172
173 bytesArray[i] = bytes;
174 }
175
176 if ((modulePathsString != null) &&
177 !PropsValues.COMBO_CHECK_TIMESTAMP) {
178
179 _bytesArrayPortalCache.put(modulePathsString, bytesArray);
180 }
181 }
182
183 String contentType = ContentTypes.TEXT_JAVASCRIPT;
184
185 if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
186 contentType = ContentTypes.TEXT_CSS;
187 }
188
189 response.setContentType(contentType);
190
191 ServletResponseUtil.write(response, bytesArray);
192 }
193
194 protected byte[] getResourceContent(
195 HttpServletRequest request, HttpServletResponse response,
196 URL resourceURL, String resourcePath, String minifierType)
197 throws IOException {
198
199 String fileContentKey = resourcePath.concat(StringPool.QUESTION).concat(
200 minifierType);
201
202 FileContentBag fileContentBag = _fileContentBagPortalCache.get(
203 fileContentKey);
204
205 if ((fileContentBag != null) && !PropsValues.COMBO_CHECK_TIMESTAMP) {
206 return fileContentBag._fileContent;
207 }
208
209 URLConnection urlConnection = null;
210
211 if (resourceURL != null) {
212 urlConnection = resourceURL.openConnection();
213 }
214
215 if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
216 long elapsedTime =
217 System.currentTimeMillis() - fileContentBag._lastModified;
218
219 if ((urlConnection != null) &&
220 (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
221 (urlConnection.getLastModified() ==
222 fileContentBag._lastModified)) {
223
224 return fileContentBag._fileContent;
225 }
226
227 _fileContentBagPortalCache.remove(fileContentKey);
228 }
229
230 if (resourceURL == null) {
231 fileContentBag = _EMPTY_FILE_CONTENT_BAG;
232 }
233 else {
234 String stringFileContent = StringUtil.read(
235 urlConnection.getInputStream());
236
237 if (!StringUtil.endsWith(resourcePath, _CSS_MINIFIED_SUFFIX) &&
238 !StringUtil.endsWith(
239 resourcePath, _JAVASCRIPT_MINIFIED_SUFFIX)) {
240
241 if (minifierType.equals("css")) {
242 try {
243 stringFileContent = DynamicCSSUtil.parseSass(
244 getServletContext(), request, resourcePath,
245 stringFileContent);
246 }
247 catch (Exception e) {
248 _log.error(
249 "Unable to parse SASS on CSS " +
250 resourceURL.getPath(), e);
251
252 if (_log.isDebugEnabled()) {
253 _log.debug(stringFileContent);
254 }
255
256 response.setHeader(
257 HttpHeaders.CACHE_CONTROL,
258 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
259 }
260
261 stringFileContent = MinifierUtil.minifyCss(
262 stringFileContent);
263 }
264 else if (minifierType.equals("js")) {
265 stringFileContent = MinifierUtil.minifyJavaScript(
266 stringFileContent);
267 }
268 }
269
270 fileContentBag = new FileContentBag(
271 stringFileContent.getBytes(StringPool.UTF8),
272 urlConnection.getLastModified());
273 }
274
275 if (PropsValues.COMBO_CHECK_TIMESTAMP) {
276 int timeToLive =
277 (int)(PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL / Time.SECOND);
278
279 _fileContentBagPortalCache.put(
280 fileContentKey, fileContentBag, timeToLive);
281 }
282
283 return fileContentBag._fileContent;
284 }
285
286 protected URL getResourceURL(
287 ServletContext servletContext, String rootPath, String path)
288 throws Exception {
289
290 URL url = servletContext.getResource(path);
291
292 if (url == null) {
293 return null;
294 }
295
296 String filePath = ServletContextUtil.getResourcePath(url);
297
298 int pos = filePath.indexOf(
299 rootPath.concat(StringPool.SLASH).concat(_JAVASCRIPT_DIR));
300
301 if (pos == 0) {
302 return url;
303 }
304
305 return null;
306 }
307
308 protected boolean validateModuleExtension(String moduleName)
309 throws Exception {
310
311 boolean validModuleExtension = false;
312
313 String[] fileExtensions = PrefsPropsUtil.getStringArray(
314 PropsKeys.COMBO_ALLOWED_FILE_EXTENSIONS, StringPool.COMMA);
315
316 for (String fileExtension : fileExtensions) {
317 if (StringPool.STAR.equals(fileExtension) ||
318 StringUtil.endsWith(moduleName, fileExtension)) {
319
320 validModuleExtension = true;
321
322 break;
323 }
324 }
325
326 return validModuleExtension;
327 }
328
329 private static final String _CSS_EXTENSION = "css";
330
331 private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
332
333 private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
334 new FileContentBag(new byte[0], 0);
335
336 private static final String _JAVASCRIPT_DIR = "html/js";
337
338 private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
339
340 private static Log _log = LogFactoryUtil.getLog(ComboServlet.class);
341
342 private PortalCache<String, byte[][]> _bytesArrayPortalCache =
343 SingleVMPoolUtil.getCache(ComboServlet.class.getName());
344 private PortalCache<String, FileContentBag> _fileContentBagPortalCache =
345 SingleVMPoolUtil.getCache(FileContentBag.class.getName());
346
347 private static class FileContentBag implements Serializable {
348
349 public FileContentBag(byte[] fileContent, long lastModifiedTime) {
350 _fileContent = fileContent;
351 _lastModified = lastModifiedTime;
352 }
353
354 private byte[] _fileContent;
355 private long _lastModified;
356
357 }
358
359 }