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.language.LanguageUtil;
020 import com.liferay.portal.kernel.log.Log;
021 import com.liferay.portal.kernel.log.LogFactoryUtil;
022 import com.liferay.portal.kernel.servlet.HttpHeaders;
023 import com.liferay.portal.kernel.servlet.RequestDispatcherUtil;
024 import com.liferay.portal.kernel.servlet.ServletResponseUtil;
025 import com.liferay.portal.kernel.util.CharPool;
026 import com.liferay.portal.kernel.util.ContentTypes;
027 import com.liferay.portal.kernel.util.FileUtil;
028 import com.liferay.portal.kernel.util.HttpUtil;
029 import com.liferay.portal.kernel.util.ObjectValuePair;
030 import com.liferay.portal.kernel.util.ParamUtil;
031 import com.liferay.portal.kernel.util.PropsKeys;
032 import com.liferay.portal.kernel.util.SetUtil;
033 import com.liferay.portal.kernel.util.StringPool;
034 import com.liferay.portal.kernel.util.StringUtil;
035 import com.liferay.portal.kernel.util.Time;
036 import com.liferay.portal.kernel.util.Validator;
037 import com.liferay.portal.minifier.MinifierUtil;
038 import com.liferay.portal.model.Portlet;
039 import com.liferay.portal.model.PortletApp;
040 import com.liferay.portal.service.PortletLocalServiceUtil;
041 import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
042 import com.liferay.portal.util.AggregateUtil;
043 import com.liferay.portal.util.PortalUtil;
044 import com.liferay.portal.util.PortletKeys;
045 import com.liferay.portal.util.PrefsPropsUtil;
046 import com.liferay.portal.util.PropsValues;
047
048 import java.io.IOException;
049 import java.io.Serializable;
050
051 import java.util.Arrays;
052 import java.util.Collections;
053 import java.util.Enumeration;
054 import java.util.LinkedHashSet;
055 import java.util.Map;
056 import java.util.Set;
057
058 import javax.servlet.RequestDispatcher;
059 import javax.servlet.ServletContext;
060 import javax.servlet.ServletException;
061 import javax.servlet.http.HttpServlet;
062 import javax.servlet.http.HttpServletRequest;
063 import javax.servlet.http.HttpServletResponse;
064
065
071 public class ComboServlet extends HttpServlet {
072
073 public static void clearCache() {
074 _bytesArrayPortalCache.removeAll();
075 _fileContentBagPortalCache.removeAll();
076 }
077
078 @Override
079 public void service(
080 HttpServletRequest request, HttpServletResponse response)
081 throws IOException, ServletException {
082
083 try {
084 doService(request, response);
085 }
086 catch (Exception e) {
087 _log.error(e, e);
088
089 PortalUtil.sendError(
090 HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e, request,
091 response);
092 }
093 }
094
095 protected static String getModulePortletId(String modulePath) {
096 int index = modulePath.indexOf(CharPool.COLON);
097
098 if (index > 0) {
099 return modulePath.substring(0, index);
100 }
101
102 return PortletKeys.PORTAL;
103 }
104
105 protected static String getResourcePath(String modulePath) {
106 int index = modulePath.indexOf(CharPool.COLON);
107
108 if (index > 0) {
109 return modulePath.substring(index + 1);
110 }
111
112 return modulePath;
113 }
114
115 protected void doService(
116 HttpServletRequest request, HttpServletResponse response)
117 throws Exception {
118
119 Set<String> modulePathsSet = new LinkedHashSet<>();
120
121 Map<String, String[]> parameterMap = HttpUtil.getParameterMap(
122 request.getQueryString());
123
124 Enumeration<String> enu = Collections.enumeration(
125 parameterMap.keySet());
126
127 while (enu.hasMoreElements()) {
128 String name = enu.nextElement();
129
130 if (_protectedParameters.contains(name)) {
131 continue;
132 }
133
134 name = HttpUtil.decodePath(name);
135
136 ServletContext servletContext = getServletContext();
137
138 String contextPath = servletContext.getContextPath();
139
140 if (name.startsWith(contextPath)) {
141 name = name.replaceFirst(contextPath, StringPool.BLANK);
142 }
143
144 modulePathsSet.add(name);
145 }
146
147 if (modulePathsSet.isEmpty()) {
148 throw new IllegalArgumentException(
149 "Query string translates to an empty module paths set");
150 }
151
152 String[] modulePaths = modulePathsSet.toArray(
153 new String[modulePathsSet.size()]);
154
155 String firstModulePath = modulePaths[0];
156
157 String extension = FileUtil.getExtension(firstModulePath);
158
159 String minifierType = ParamUtil.getString(request, "minifierType");
160
161 if (Validator.isNull(minifierType)) {
162 minifierType = "js";
163
164 if (StringUtil.equalsIgnoreCase(extension, _CSS_EXTENSION)) {
165 minifierType = "css";
166 }
167 }
168
169 if (!minifierType.equals("css") && !minifierType.equals("js")) {
170 minifierType = "js";
171 }
172
173 String modulePathsString = null;
174
175 byte[][] bytesArray = null;
176
177 if (!PropsValues.COMBO_CHECK_TIMESTAMP) {
178 modulePathsString = Arrays.toString(modulePaths);
179
180 if (minifierType.equals("css") &&
181 PortalUtil.isRightToLeft(request)) {
182
183 modulePathsString += ".rtl";
184 }
185 else if (minifierType.equals("js")) {
186 modulePathsString +=
187 StringPool.POUND + LanguageUtil.getLanguageId(request);
188 }
189
190 bytesArray = _bytesArrayPortalCache.get(modulePathsString);
191 }
192
193 if (bytesArray == null) {
194 bytesArray = new byte[modulePaths.length][];
195
196 for (int i = 0; i < modulePaths.length; i++) {
197 String modulePath = modulePaths[i];
198
199 if (!validateModuleExtension(modulePath)) {
200 response.setHeader(
201 HttpHeaders.CACHE_CONTROL,
202 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
203 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
204
205 return;
206 }
207
208 byte[] bytes = new byte[0];
209
210 if (Validator.isNotNull(modulePath)) {
211 RequestDispatcher requestDispatcher =
212 getResourceRequestDispatcher(
213 request, response, modulePath);
214
215 if (requestDispatcher == null) {
216 response.setHeader(
217 HttpHeaders.CACHE_CONTROL,
218 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
219 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
220
221 return;
222 }
223
224 bytes = getResourceContent(
225 requestDispatcher, request, response, modulePath,
226 minifierType);
227 }
228
229 bytesArray[i] = bytes;
230 }
231
232 if ((modulePathsString != null) &&
233 !PropsValues.COMBO_CHECK_TIMESTAMP) {
234
235 _bytesArrayPortalCache.put(modulePathsString, bytesArray);
236 }
237 }
238
239 String contentType = ContentTypes.TEXT_JAVASCRIPT;
240
241 if (StringUtil.equalsIgnoreCase(extension, _CSS_EXTENSION)) {
242 contentType = ContentTypes.TEXT_CSS;
243 }
244
245 response.setContentType(contentType);
246
247 ServletResponseUtil.write(response, bytesArray);
248 }
249
250 protected byte[] getResourceContent(
251 RequestDispatcher requestDispatcher, HttpServletRequest request,
252 HttpServletResponse response, String modulePath,
253 String minifierType)
254 throws Exception {
255
256 String resourcePath = getResourcePath(modulePath);
257
258 String portletId = getModulePortletId(modulePath);
259
260 Portlet portlet = PortletLocalServiceUtil.getPortletById(portletId);
261
262 if (!resourcePath.startsWith(portlet.getContextPath())) {
263 resourcePath = portlet.getContextPath() + resourcePath;
264 }
265
266 String fileContentKey = resourcePath.concat(StringPool.QUESTION).concat(
267 minifierType);
268
269 FileContentBag fileContentBag = _fileContentBagPortalCache.get(
270 fileContentKey);
271
272 if ((fileContentBag != null) && !PropsValues.COMBO_CHECK_TIMESTAMP) {
273 return fileContentBag._fileContent;
274 }
275
276 if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
277 long elapsedTime =
278 System.currentTimeMillis() - fileContentBag._lastModified;
279
280 if ((requestDispatcher != null) &&
281 (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
282 (RequestDispatcherUtil.getLastModifiedTime(
283 requestDispatcher, request, response) ==
284 fileContentBag._lastModified)) {
285
286 return fileContentBag._fileContent;
287 }
288
289 _fileContentBagPortalCache.remove(fileContentKey);
290 }
291
292 if (requestDispatcher == null) {
293 fileContentBag = _EMPTY_FILE_CONTENT_BAG;
294 }
295 else {
296 ObjectValuePair<String, Long> objectValuePair =
297 RequestDispatcherUtil.getContentAndLastModifiedTime(
298 requestDispatcher, request, response);
299
300 String stringFileContent = objectValuePair.getKey();
301
302 if (!StringUtil.endsWith(resourcePath, _CSS_MINIFIED_SUFFIX) &&
303 !StringUtil.endsWith(
304 resourcePath, _JAVASCRIPT_MINIFIED_SUFFIX)) {
305
306 if (minifierType.equals("css")) {
307 try {
308 stringFileContent = DynamicCSSUtil.replaceToken(
309 getServletContext(), request, stringFileContent);
310 }
311 catch (Exception e) {
312 _log.error(
313 "Unable to replace tokens in CSS " + resourcePath,
314 e);
315
316 if (_log.isDebugEnabled()) {
317 _log.debug(stringFileContent);
318 }
319
320 response.setHeader(
321 HttpHeaders.CACHE_CONTROL,
322 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
323 }
324
325 String baseURL = StringPool.BLANK;
326
327 int slashIndex = resourcePath.lastIndexOf(CharPool.SLASH);
328
329 if (slashIndex != -1) {
330 baseURL = resourcePath.substring(0, slashIndex + 1);
331 }
332
333 stringFileContent = AggregateUtil.updateRelativeURLs(
334 stringFileContent, baseURL);
335
336 stringFileContent = MinifierUtil.minifyCss(
337 stringFileContent);
338 }
339 else if (minifierType.equals("js")) {
340 stringFileContent = MinifierUtil.minifyJavaScript(
341 resourcePath, stringFileContent);
342 }
343 }
344
345 fileContentBag = new FileContentBag(
346 stringFileContent.getBytes(StringPool.UTF8),
347 objectValuePair.getValue());
348 }
349
350 if (PropsValues.COMBO_CHECK_TIMESTAMP) {
351 int timeToLive =
352 (int)(PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL / Time.SECOND);
353
354 _fileContentBagPortalCache.put(
355 fileContentKey, fileContentBag, timeToLive);
356 }
357
358 return fileContentBag._fileContent;
359 }
360
361 protected RequestDispatcher getResourceRequestDispatcher(
362 HttpServletRequest request, HttpServletResponse response,
363 String modulePath)
364 throws Exception {
365
366 String portletId = getModulePortletId(modulePath);
367
368 Portlet portlet = PortletLocalServiceUtil.getPortletById(portletId);
369
370 if (portlet.isUndeployedPortlet()) {
371 return null;
372 }
373
374 PortletApp portletApp = portlet.getPortletApp();
375
376 ServletContext servletContext = portletApp.getServletContext();
377
378 String resourcePath = getResourcePath(modulePath);
379
380 if (!PortalUtil.isValidResourceId(resourcePath)) {
381 if (_log.isWarnEnabled()) {
382 _log.warn(
383 "Invalid resource " + request.getRequestURL() + "?" +
384 request.getQueryString());
385 }
386
387 return null;
388 }
389
390 return servletContext.getRequestDispatcher(resourcePath);
391 }
392
393 protected boolean validateModuleExtension(String moduleName)
394 throws Exception {
395
396 int index = moduleName.indexOf(CharPool.QUESTION);
397
398 if (index != -1) {
399 moduleName = moduleName.substring(0, index);
400 }
401
402 boolean validModuleExtension = false;
403
404 String[] fileExtensions = PrefsPropsUtil.getStringArray(
405 PropsKeys.COMBO_ALLOWED_FILE_EXTENSIONS, StringPool.COMMA);
406
407 for (String fileExtension : fileExtensions) {
408 if (StringPool.STAR.equals(fileExtension) ||
409 StringUtil.endsWith(moduleName, fileExtension)) {
410
411 validModuleExtension = true;
412
413 break;
414 }
415 }
416
417 return validModuleExtension;
418 }
419
420 private static final String _CSS_EXTENSION = "css";
421
422 private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
423
424 private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
425 new FileContentBag(new byte[0], 0);
426
427 private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
428
429 private static final Log _log = LogFactoryUtil.getLog(ComboServlet.class);
430
431 private static final PortalCache<String, byte[][]> _bytesArrayPortalCache =
432 SingleVMPoolUtil.getPortalCache(ComboServlet.class.getName());
433 private static final PortalCache<String, FileContentBag>
434 _fileContentBagPortalCache = SingleVMPoolUtil.getPortalCache(
435 FileContentBag.class.getName());
436
437 private final Set<String> _protectedParameters = SetUtil.fromArray(
438 new String[] {
439 "b", "browserId", "minifierType", "languageId", "t", "themeId"
440 });
441
442 private static class FileContentBag implements Serializable {
443
444 public FileContentBag(byte[] fileContent, long lastModifiedTime) {
445 _fileContent = fileContent;
446 _lastModified = lastModifiedTime;
447 }
448
449 private final byte[] _fileContent;
450 private final long _lastModified;
451
452 }
453
454 }