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.AggregateResourceBundle;
026 import com.liferay.portal.kernel.util.CharPool;
027 import com.liferay.portal.kernel.util.ContentTypes;
028 import com.liferay.portal.kernel.util.FileUtil;
029 import com.liferay.portal.kernel.util.HttpUtil;
030 import com.liferay.portal.kernel.util.LocaleUtil;
031 import com.liferay.portal.kernel.util.ObjectValuePair;
032 import com.liferay.portal.kernel.util.ParamUtil;
033 import com.liferay.portal.kernel.util.PropsKeys;
034 import com.liferay.portal.kernel.util.SetUtil;
035 import com.liferay.portal.kernel.util.StringPool;
036 import com.liferay.portal.kernel.util.StringUtil;
037 import com.liferay.portal.kernel.util.Time;
038 import com.liferay.portal.kernel.util.Validator;
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 ServletContext servletContext = getServletContext();
145
146 String contextPath = servletContext.getContextPath();
147
148 if (name.startsWith(contextPath)) {
149 name = name.replaceFirst(contextPath, StringPool.BLANK);
150 }
151
152 modulePathsSet.add(name);
153 }
154
155 if (modulePathsSet.isEmpty()) {
156 throw new IllegalArgumentException(
157 "Query string translates to an empty module paths set");
158 }
159
160 String[] modulePaths = modulePathsSet.toArray(
161 new String[modulePathsSet.size()]);
162
163 String firstModulePath = modulePaths[0];
164
165 String extension = FileUtil.getExtension(firstModulePath);
166
167 String minifierType = ParamUtil.getString(request, "minifierType");
168
169 if (Validator.isNull(minifierType)) {
170 minifierType = "js";
171
172 if (StringUtil.equalsIgnoreCase(extension, _CSS_EXTENSION)) {
173 minifierType = "css";
174 }
175 }
176
177 if (!minifierType.equals("css") && !minifierType.equals("js")) {
178 minifierType = "js";
179 }
180
181 String modulePathsString = null;
182
183 byte[][] bytesArray = null;
184
185 if (!PropsValues.COMBO_CHECK_TIMESTAMP) {
186 modulePathsString = Arrays.toString(modulePaths);
187
188 if (minifierType.equals("css") &&
189 PortalUtil.isRightToLeft(request)) {
190
191 modulePathsString += ".rtl";
192 }
193 else if (minifierType.equals("js")) {
194 modulePathsString +=
195 StringPool.POUND + LanguageUtil.getLanguageId(request);
196 }
197
198 bytesArray = _bytesArrayPortalCache.get(modulePathsString);
199 }
200
201 if (bytesArray == null) {
202 bytesArray = new byte[modulePaths.length][];
203
204 for (int i = 0; i < modulePaths.length; i++) {
205 String modulePath = modulePaths[i];
206
207 if (!validateModuleExtension(modulePath)) {
208 response.setHeader(
209 HttpHeaders.CACHE_CONTROL,
210 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
211 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
212
213 return;
214 }
215
216 byte[] bytes = new byte[0];
217
218 if (Validator.isNotNull(modulePath)) {
219 RequestDispatcher requestDispatcher =
220 getResourceRequestDispatcher(
221 request, response, modulePath);
222
223 if (requestDispatcher == null) {
224 response.setHeader(
225 HttpHeaders.CACHE_CONTROL,
226 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
227 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
228
229 return;
230 }
231
232 bytes = getResourceContent(
233 requestDispatcher, request, response, modulePath,
234 minifierType);
235 }
236
237 bytesArray[i] = bytes;
238 }
239
240 if ((modulePathsString != null) &&
241 !PropsValues.COMBO_CHECK_TIMESTAMP) {
242
243 _bytesArrayPortalCache.put(modulePathsString, bytesArray);
244 }
245 }
246
247 String contentType = ContentTypes.TEXT_JAVASCRIPT;
248
249 if (StringUtil.equalsIgnoreCase(extension, _CSS_EXTENSION)) {
250 contentType = ContentTypes.TEXT_CSS;
251 }
252
253 response.setContentType(contentType);
254
255 ServletResponseUtil.write(response, bytesArray);
256 }
257
258 protected byte[] getResourceContent(
259 RequestDispatcher requestDispatcher, HttpServletRequest request,
260 HttpServletResponse response, String modulePath,
261 String minifierType)
262 throws Exception {
263
264 String resourcePath = getResourcePath(modulePath);
265
266 String portletId = getModulePortletId(modulePath);
267
268 Portlet portlet = PortletLocalServiceUtil.getPortletById(portletId);
269
270 if (!resourcePath.startsWith(portlet.getContextPath())) {
271 resourcePath = portlet.getContextPath() + resourcePath;
272 }
273
274 String fileContentKey = resourcePath.concat(StringPool.QUESTION).concat(
275 minifierType);
276
277 FileContentBag fileContentBag = _fileContentBagPortalCache.get(
278 fileContentKey);
279
280 if ((fileContentBag != null) && !PropsValues.COMBO_CHECK_TIMESTAMP) {
281 return fileContentBag._fileContent;
282 }
283
284 if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
285 long elapsedTime =
286 System.currentTimeMillis() - fileContentBag._lastModified;
287
288 if ((requestDispatcher != null) &&
289 (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
290 (RequestDispatcherUtil.getLastModifiedTime(
291 requestDispatcher, request, response) ==
292 fileContentBag._lastModified)) {
293
294 return fileContentBag._fileContent;
295 }
296
297 _fileContentBagPortalCache.remove(fileContentKey);
298 }
299
300 if (requestDispatcher == null) {
301 fileContentBag = _EMPTY_FILE_CONTENT_BAG;
302 }
303 else {
304 ObjectValuePair<String, Long> objectValuePair =
305 RequestDispatcherUtil.getContentAndLastModifiedTime(
306 requestDispatcher, request, response);
307
308 String stringFileContent = objectValuePair.getKey();
309
310 if (!StringUtil.endsWith(resourcePath, _CSS_MINIFIED_SUFFIX) &&
311 !StringUtil.endsWith(
312 resourcePath, _JAVASCRIPT_MINIFIED_SUFFIX)) {
313
314 if (minifierType.equals("css")) {
315 try {
316 stringFileContent = DynamicCSSUtil.replaceToken(
317 getServletContext(), request, stringFileContent);
318 }
319 catch (Exception e) {
320 _log.error(
321 "Unable to replace tokens in CSS " + resourcePath,
322 e);
323
324 if (_log.isDebugEnabled()) {
325 _log.debug(stringFileContent);
326 }
327
328 response.setHeader(
329 HttpHeaders.CACHE_CONTROL,
330 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
331 }
332
333 String baseURL = StringPool.BLANK;
334
335 int slashIndex = resourcePath.lastIndexOf(CharPool.SLASH);
336
337 if (slashIndex != -1) {
338 baseURL = resourcePath.substring(0, slashIndex + 1);
339 }
340
341 stringFileContent = AggregateUtil.updateRelativeURLs(
342 stringFileContent, baseURL);
343
344 stringFileContent = MinifierUtil.minifyCss(
345 stringFileContent);
346 }
347 else if (minifierType.equals("js")) {
348 stringFileContent = translate(
349 request, portletId, stringFileContent);
350
351 stringFileContent = MinifierUtil.minifyJavaScript(
352 resourcePath, stringFileContent);
353 }
354 }
355
356 fileContentBag = new FileContentBag(
357 stringFileContent.getBytes(StringPool.UTF8),
358 objectValuePair.getValue());
359 }
360
361 if (PropsValues.COMBO_CHECK_TIMESTAMP) {
362 int timeToLive =
363 (int)(PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL / Time.SECOND);
364
365 _fileContentBagPortalCache.put(
366 fileContentKey, fileContentBag, timeToLive);
367 }
368
369 return fileContentBag._fileContent;
370 }
371
372 protected RequestDispatcher getResourceRequestDispatcher(
373 HttpServletRequest request, HttpServletResponse response,
374 String modulePath)
375 throws Exception {
376
377 String portletId = getModulePortletId(modulePath);
378
379 Portlet portlet = PortletLocalServiceUtil.getPortletById(portletId);
380
381 if (portlet.isUndeployedPortlet()) {
382 return null;
383 }
384
385 PortletApp portletApp = portlet.getPortletApp();
386
387 ServletContext servletContext = portletApp.getServletContext();
388
389 String resourcePath = getResourcePath(modulePath);
390
391 if (!PortalUtil.isValidResourceId(resourcePath)) {
392 if (_log.isWarnEnabled()) {
393 _log.warn(
394 "Invalid resource " + request.getRequestURL() + "?" +
395 request.getQueryString());
396 }
397
398 return null;
399 }
400
401 return servletContext.getRequestDispatcher(resourcePath);
402 }
403
404 protected String translate(
405 HttpServletRequest request, String portletId,
406 String stringFileContent) {
407
408 String languageId = LanguageUtil.getLanguageId(request);
409
410 Locale locale = LocaleUtil.fromLanguageId(languageId);
411
412 ResourceBundle resourceBundle = LanguageResources.getResourceBundle(
413 locale);
414
415 Portlet portlet = PortletLocalServiceUtil.getPortletById(portletId);
416
417 if (portlet != null) {
418 PortletConfig portletConfig = PortletConfigFactoryUtil.create(
419 portlet, getServletContext());
420
421 if (portletConfig != null) {
422 resourceBundle = new AggregateResourceBundle(
423 portletConfig.getResourceBundle(locale), resourceBundle);
424 }
425 }
426
427 return LanguageUtil.process(resourceBundle, locale, stringFileContent);
428 }
429
430 protected boolean validateModuleExtension(String moduleName)
431 throws Exception {
432
433 int index = moduleName.indexOf(CharPool.QUESTION);
434
435 if (index != -1) {
436 moduleName = moduleName.substring(0, index);
437 }
438
439 boolean validModuleExtension = false;
440
441 String[] fileExtensions = PrefsPropsUtil.getStringArray(
442 PropsKeys.COMBO_ALLOWED_FILE_EXTENSIONS, StringPool.COMMA);
443
444 for (String fileExtension : fileExtensions) {
445 if (StringPool.STAR.equals(fileExtension) ||
446 StringUtil.endsWith(moduleName, fileExtension)) {
447
448 validModuleExtension = true;
449
450 break;
451 }
452 }
453
454 return validModuleExtension;
455 }
456
457 private static final String _CSS_EXTENSION = "css";
458
459 private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
460
461 private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
462 new FileContentBag(new byte[0], 0);
463
464 private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
465
466 private static final Log _log = LogFactoryUtil.getLog(ComboServlet.class);
467
468 private static final PortalCache<String, byte[][]> _bytesArrayPortalCache =
469 SingleVMPoolUtil.getPortalCache(ComboServlet.class.getName());
470 private static final PortalCache<String, FileContentBag>
471 _fileContentBagPortalCache = SingleVMPoolUtil.getPortalCache(
472 FileContentBag.class.getName());
473
474 private final Set<String> _protectedParameters = SetUtil.fromArray(
475 new String[] {
476 "b", "browserId", "minifierType", "languageId", "t", "themeId"
477 });
478
479 private static class FileContentBag implements Serializable {
480
481 public FileContentBag(byte[] fileContent, long lastModifiedTime) {
482 _fileContent = fileContent;
483 _lastModified = lastModifiedTime;
484 }
485
486 private final byte[] _fileContent;
487 private final long _lastModified;
488
489 }
490
491 }