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