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