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