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