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 else if (minifierType.equals("js")) {
190 modulePathsString +=
191 StringPool.POUND + LanguageUtil.getLanguageId(request);
192 }
193
194 bytesArray = _bytesArrayPortalCache.get(modulePathsString);
195 }
196
197 if (bytesArray == null) {
198 bytesArray = new byte[modulePaths.length][];
199
200 for (int i = 0; i < modulePaths.length; i++) {
201 String modulePath = modulePaths[i];
202
203 if (!validateModuleExtension(modulePath)) {
204 response.setHeader(
205 HttpHeaders.CACHE_CONTROL,
206 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
207 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
208
209 return;
210 }
211
212 byte[] bytes = new byte[0];
213
214 if (Validator.isNotNull(modulePath)) {
215 URL url = getResourceURL(request, modulePath);
216
217 if (url == null) {
218 response.setHeader(
219 HttpHeaders.CACHE_CONTROL,
220 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
221 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
222
223 return;
224 }
225
226 bytes = getResourceContent(
227 request, response, url, modulePath, minifierType);
228 }
229
230 bytesArray[i] = bytes;
231 }
232
233 if ((modulePathsString != null) &&
234 !PropsValues.COMBO_CHECK_TIMESTAMP) {
235
236 _bytesArrayPortalCache.put(modulePathsString, bytesArray);
237 }
238 }
239
240 String contentType = ContentTypes.TEXT_JAVASCRIPT;
241
242 if (StringUtil.equalsIgnoreCase(extension, _CSS_EXTENSION)) {
243 contentType = ContentTypes.TEXT_CSS;
244 }
245
246 response.setContentType(contentType);
247
248 ServletResponseUtil.write(response, bytesArray);
249 }
250
251 protected byte[] getResourceContent(
252 HttpServletRequest request, HttpServletResponse response,
253 URL resourceURL, String modulePath, String minifierType)
254 throws IOException {
255
256 String resourcePath = getResourcePath(modulePath);
257
258 String portletId = getModulePortletId(modulePath);
259
260 Portlet portlet = PortletLocalServiceUtil.getPortletById(portletId);
261
262 if (!resourcePath.startsWith(portlet.getContextPath())) {
263 resourcePath = portlet.getContextPath() + resourcePath;
264 }
265
266 String fileContentKey = resourcePath.concat(StringPool.QUESTION).concat(
267 minifierType);
268
269 FileContentBag fileContentBag = _fileContentBagPortalCache.get(
270 fileContentKey);
271
272 if ((fileContentBag != null) && !PropsValues.COMBO_CHECK_TIMESTAMP) {
273 return fileContentBag._fileContent;
274 }
275
276 URLConnection urlConnection = null;
277
278 if (resourceURL != null) {
279 urlConnection = resourceURL.openConnection();
280 }
281
282 if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
283 long elapsedTime =
284 System.currentTimeMillis() - fileContentBag._lastModified;
285
286 if ((urlConnection != null) &&
287 (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
288 (urlConnection.getLastModified() ==
289 fileContentBag._lastModified)) {
290
291 return fileContentBag._fileContent;
292 }
293
294 _fileContentBagPortalCache.remove(fileContentKey);
295 }
296
297 if (resourceURL == null) {
298 fileContentBag = _EMPTY_FILE_CONTENT_BAG;
299 }
300 else {
301 String stringFileContent = StringUtil.read(
302 urlConnection.getInputStream());
303
304 if (!StringUtil.endsWith(resourcePath, _CSS_MINIFIED_SUFFIX) &&
305 !StringUtil.endsWith(
306 resourcePath, _JAVASCRIPT_MINIFIED_SUFFIX)) {
307
308 if (minifierType.equals("css")) {
309 try {
310 stringFileContent = DynamicCSSUtil.parseSass(
311 getServletContext(), request, resourcePath,
312 stringFileContent);
313 }
314 catch (Exception e) {
315 _log.error(
316 "Unable to parse SASS on CSS " +
317 resourceURL.getPath(), 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 urlConnection.getLastModified());
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 URL getResourceURL(HttpServletRequest request, String modulePath)
368 throws Exception {
369
370 String portletId = getModulePortletId(modulePath);
371
372 Portlet portlet = PortletLocalServiceUtil.getPortletById(portletId);
373
374 if (portlet.isUndeployedPortlet()) {
375 return null;
376 }
377
378 PortletApp portletApp = portlet.getPortletApp();
379
380 ServletContext servletContext = portletApp.getServletContext();
381
382 String resourcePath = getResourcePath(modulePath);
383
384 String contextPath = servletContext.getContextPath();
385
386 if (resourcePath.startsWith(contextPath)) {
387 resourcePath = resourcePath.substring(contextPath.length());
388 }
389
390 URL url = servletContext.getResource(resourcePath);
391
392 if (url != null) {
393 return url;
394 }
395
396 url = new URL(
397 request.getScheme(), request.getLocalAddr(), request.getLocalPort(),
398 contextPath + resourcePath);
399
400 HttpURLConnection urlConnection =
401 (HttpURLConnection)url.openConnection();
402
403 if (urlConnection.getResponseCode() == HttpServletResponse.SC_OK) {
404 return url;
405 }
406
407 throw new ServletException(
408 "Resource " + resourcePath + " does not exist in " +
409 portlet.getContextPath());
410 }
411
412 protected String translate(
413 HttpServletRequest request, String portletId,
414 String stringFileContent) {
415
416 String languageId = LanguageUtil.getLanguageId(request);
417
418 Locale locale = LocaleUtil.fromLanguageId(languageId);
419
420 ResourceBundle resourceBundle = LanguageResources.getResourceBundle(
421 locale);
422
423 Portlet portlet = PortletLocalServiceUtil.getPortletById(portletId);
424
425 if (portlet != null) {
426 PortletConfig portletConfig = PortletConfigFactoryUtil.create(
427 portlet, getServletContext());
428
429 if (portletConfig != null) {
430 resourceBundle = new AggregateResourceBundle(
431 portletConfig.getResourceBundle(locale), resourceBundle);
432 }
433 }
434
435 return LanguageUtil.process(resourceBundle, locale, stringFileContent);
436 }
437
438 protected boolean validateModuleExtension(String moduleName)
439 throws Exception {
440
441 int index = moduleName.indexOf(CharPool.QUESTION);
442
443 if (index != -1) {
444 moduleName = moduleName.substring(0, index);
445 }
446
447 boolean validModuleExtension = false;
448
449 String[] fileExtensions = PrefsPropsUtil.getStringArray(
450 PropsKeys.COMBO_ALLOWED_FILE_EXTENSIONS, StringPool.COMMA);
451
452 for (String fileExtension : fileExtensions) {
453 if (StringPool.STAR.equals(fileExtension) ||
454 StringUtil.endsWith(moduleName, fileExtension)) {
455
456 validModuleExtension = true;
457
458 break;
459 }
460 }
461
462 return validModuleExtension;
463 }
464
465 private static final String _CSS_EXTENSION = "css";
466
467 private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
468
469 private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
470 new FileContentBag(new byte[0], 0);
471
472 private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
473
474 private static Log _log = LogFactoryUtil.getLog(ComboServlet.class);
475
476 private static PortalCache<String, byte[][]> _bytesArrayPortalCache =
477 SingleVMPoolUtil.getCache(ComboServlet.class.getName());
478 private static PortalCache<String, FileContentBag>
479 _fileContentBagPortalCache = SingleVMPoolUtil.getCache(
480 FileContentBag.class.getName());
481
482 private Set<String> _protectedParameters = SetUtil.fromArray(
483 new String[] {
484 "b", "browserId", "minifierType", "languageId", "t", "themeId"
485 });
486
487 private static class FileContentBag implements Serializable {
488
489 public FileContentBag(byte[] fileContent, long lastModifiedTime) {
490 _fileContent = fileContent;
491 _lastModified = lastModifiedTime;
492 }
493
494 private byte[] _fileContent;
495 private long _lastModified;
496
497 }
498
499 }