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.log.Log;
020 import com.liferay.portal.kernel.log.LogFactoryUtil;
021 import com.liferay.portal.kernel.servlet.HttpHeaders;
022 import com.liferay.portal.kernel.servlet.ServletContextUtil;
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.ParamUtil;
029 import com.liferay.portal.kernel.util.PropsKeys;
030 import com.liferay.portal.kernel.util.ServerDetector;
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.servlet.filters.dynamiccss.DynamicCSSUtil;
037 import com.liferay.portal.util.AggregateUtil;
038 import com.liferay.portal.util.MinifierUtil;
039 import com.liferay.portal.util.PortalUtil;
040 import com.liferay.portal.util.PrefsPropsUtil;
041 import com.liferay.portal.util.PropsValues;
042
043 import java.io.IOException;
044 import java.io.Serializable;
045
046 import java.net.URL;
047 import java.net.URLConnection;
048
049 import java.util.Arrays;
050 import java.util.Collections;
051 import java.util.Enumeration;
052 import java.util.LinkedHashSet;
053 import java.util.Map;
054 import java.util.Set;
055
056 import javax.servlet.ServletContext;
057 import javax.servlet.ServletException;
058 import javax.servlet.http.HttpServlet;
059 import javax.servlet.http.HttpServletRequest;
060 import javax.servlet.http.HttpServletResponse;
061
062
068 public class ComboServlet extends HttpServlet {
069
070 @Override
071 public void service(
072 HttpServletRequest request, HttpServletResponse response)
073 throws IOException, ServletException {
074
075 try {
076 doService(request, response);
077 }
078 catch (Exception e) {
079 _log.error(e, e);
080
081 PortalUtil.sendError(
082 HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e, request,
083 response);
084 }
085 }
086
087 protected void doService(
088 HttpServletRequest request, HttpServletResponse response)
089 throws Exception {
090
091 Set<String> modulePathsSet = new LinkedHashSet<String>();
092
093 Enumeration<String> enu = request.getParameterNames();
094
095 if (ServerDetector.isWebSphere()) {
096 Map<String, String[]> parameterMap = HttpUtil.getParameterMap(
097 request.getQueryString());
098
099 enu = Collections.enumeration(parameterMap.keySet());
100 }
101
102 while (enu.hasMoreElements()) {
103 String name = enu.nextElement();
104
105 if (_protectedParameters.contains(name)) {
106 continue;
107 }
108
109 modulePathsSet.add(name);
110 }
111
112 if (modulePathsSet.size() == 0) {
113 response.sendError(
114 HttpServletResponse.SC_BAD_REQUEST,
115 "Modules paths set is empty");
116
117 return;
118 }
119
120 String[] modulePaths = modulePathsSet.toArray(
121 new String[modulePathsSet.size()]);
122
123 String firstModulePath = modulePaths[0];
124
125 String extension = FileUtil.getExtension(firstModulePath);
126
127 String minifierType = ParamUtil.getString(request, "minifierType");
128
129 if (Validator.isNull(minifierType)) {
130 minifierType = "js";
131
132 if (StringUtil.equalsIgnoreCase(extension, _CSS_EXTENSION)) {
133 minifierType = "css";
134 }
135 }
136
137 if (!minifierType.equals("css") && !minifierType.equals("js")) {
138 minifierType = "js";
139 }
140
141 String modulePathsString = null;
142
143 byte[][] bytesArray = null;
144
145 if (!PropsValues.COMBO_CHECK_TIMESTAMP) {
146 modulePathsString = Arrays.toString(modulePaths);
147
148 if (minifierType.equals("css") &&
149 PortalUtil.isRightToLeft(request)) {
150
151 modulePathsString += ".rtl";
152 }
153
154 bytesArray = _bytesArrayPortalCache.get(modulePathsString);
155 }
156
157 if (bytesArray == null) {
158 ServletContext servletContext = getServletContext();
159
160 String rootPath = ServletContextUtil.getRootPath(servletContext);
161
162 bytesArray = new byte[modulePaths.length][];
163
164 for (int i = 0; i < modulePaths.length; i++) {
165 String modulePath = modulePaths[i];
166
167 if (!validateModuleExtension(modulePath)) {
168 response.setHeader(
169 HttpHeaders.CACHE_CONTROL,
170 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
171 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
172
173 return;
174 }
175
176 byte[] bytes = new byte[0];
177
178 if (Validator.isNotNull(modulePath)) {
179 modulePath = StringUtil.replaceFirst(
180 modulePath, PortalUtil.getPathContext(),
181 StringPool.BLANK);
182
183 URL url = getResourceURL(
184 servletContext, rootPath, modulePath);
185
186 if (url == null) {
187 response.setHeader(
188 HttpHeaders.CACHE_CONTROL,
189 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
190 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
191
192 return;
193 }
194
195 bytes = getResourceContent(
196 request, response, url, modulePath, minifierType);
197 }
198
199 bytesArray[i] = bytes;
200 }
201
202 if ((modulePathsString != null) &&
203 !PropsValues.COMBO_CHECK_TIMESTAMP) {
204
205 _bytesArrayPortalCache.put(modulePathsString, bytesArray);
206 }
207 }
208
209 String contentType = ContentTypes.TEXT_JAVASCRIPT;
210
211 if (StringUtil.equalsIgnoreCase(extension, _CSS_EXTENSION)) {
212 contentType = ContentTypes.TEXT_CSS;
213 }
214
215 response.setContentType(contentType);
216
217 ServletResponseUtil.write(response, bytesArray);
218 }
219
220 protected byte[] getResourceContent(
221 HttpServletRequest request, HttpServletResponse response,
222 URL resourceURL, String resourcePath, String minifierType)
223 throws IOException {
224
225 String fileContentKey = resourcePath.concat(StringPool.QUESTION).concat(
226 minifierType);
227
228 FileContentBag fileContentBag = _fileContentBagPortalCache.get(
229 fileContentKey);
230
231 if ((fileContentBag != null) && !PropsValues.COMBO_CHECK_TIMESTAMP) {
232 return fileContentBag._fileContent;
233 }
234
235 URLConnection urlConnection = null;
236
237 if (resourceURL != null) {
238 urlConnection = resourceURL.openConnection();
239 }
240
241 if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
242 long elapsedTime =
243 System.currentTimeMillis() - fileContentBag._lastModified;
244
245 if ((urlConnection != null) &&
246 (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
247 (urlConnection.getLastModified() ==
248 fileContentBag._lastModified)) {
249
250 return fileContentBag._fileContent;
251 }
252
253 _fileContentBagPortalCache.remove(fileContentKey);
254 }
255
256 if (resourceURL == null) {
257 fileContentBag = _EMPTY_FILE_CONTENT_BAG;
258 }
259 else {
260 String stringFileContent = StringUtil.read(
261 urlConnection.getInputStream());
262
263 if (!StringUtil.endsWith(resourcePath, _CSS_MINIFIED_SUFFIX) &&
264 !StringUtil.endsWith(
265 resourcePath, _JAVASCRIPT_MINIFIED_SUFFIX)) {
266
267 if (minifierType.equals("css")) {
268 try {
269 stringFileContent = DynamicCSSUtil.parseSass(
270 getServletContext(), request, resourcePath,
271 stringFileContent);
272 }
273 catch (Exception e) {
274 _log.error(
275 "Unable to parse SASS on CSS " +
276 resourceURL.getPath(), e);
277
278 if (_log.isDebugEnabled()) {
279 _log.debug(stringFileContent);
280 }
281
282 response.setHeader(
283 HttpHeaders.CACHE_CONTROL,
284 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
285 }
286
287 String baseURL = StringPool.BLANK;
288
289 int index = resourcePath.lastIndexOf(CharPool.SLASH);
290
291 if (index != -1) {
292 baseURL = resourcePath.substring(0, index + 1);
293 }
294
295 stringFileContent = AggregateUtil.updateRelativeURLs(
296 stringFileContent, baseURL);
297
298 stringFileContent = MinifierUtil.minifyCss(
299 stringFileContent);
300 }
301 else if (minifierType.equals("js")) {
302 stringFileContent = MinifierUtil.minifyJavaScript(
303 stringFileContent);
304 }
305 }
306
307 fileContentBag = new FileContentBag(
308 stringFileContent.getBytes(StringPool.UTF8),
309 urlConnection.getLastModified());
310 }
311
312 if (PropsValues.COMBO_CHECK_TIMESTAMP) {
313 int timeToLive =
314 (int)(PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL / Time.SECOND);
315
316 _fileContentBagPortalCache.put(
317 fileContentKey, fileContentBag, timeToLive);
318 }
319
320 return fileContentBag._fileContent;
321 }
322
323 protected URL getResourceURL(
324 ServletContext servletContext, String rootPath, String path)
325 throws Exception {
326
327 URL url = servletContext.getResource(path);
328
329 if (url == null) {
330 return null;
331 }
332
333 String filePath = ServletContextUtil.getResourcePath(url);
334
335 int pos = filePath.indexOf(
336 rootPath.concat(StringPool.SLASH).concat(_JAVASCRIPT_DIR));
337
338 if (pos == 0) {
339 return url;
340 }
341
342 return null;
343 }
344
345 protected boolean validateModuleExtension(String moduleName)
346 throws Exception {
347
348 boolean validModuleExtension = false;
349
350 String[] fileExtensions = PrefsPropsUtil.getStringArray(
351 PropsKeys.COMBO_ALLOWED_FILE_EXTENSIONS, StringPool.COMMA);
352
353 for (String fileExtension : fileExtensions) {
354 if (StringPool.STAR.equals(fileExtension) ||
355 StringUtil.endsWith(moduleName, fileExtension)) {
356
357 validModuleExtension = true;
358
359 break;
360 }
361 }
362
363 return validModuleExtension;
364 }
365
366 private static final String _CSS_EXTENSION = "css";
367
368 private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
369
370 private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
371 new FileContentBag(new byte[0], 0);
372
373 private static final String _JAVASCRIPT_DIR = "html/js";
374
375 private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
376
377 private static Log _log = LogFactoryUtil.getLog(ComboServlet.class);
378
379 private PortalCache<String, byte[][]> _bytesArrayPortalCache =
380 SingleVMPoolUtil.getCache(ComboServlet.class.getName());
381 private PortalCache<String, FileContentBag> _fileContentBagPortalCache =
382 SingleVMPoolUtil.getCache(FileContentBag.class.getName());
383 private Set<String> _protectedParameters = SetUtil.fromArray(
384 new String[] {"b", "browserId", "minifierType", "languageId", "t"});
385
386 private static class FileContentBag implements Serializable {
387
388 public FileContentBag(byte[] fileContent, long lastModifiedTime) {
389 _fileContent = fileContent;
390 _lastModified = lastModifiedTime;
391 }
392
393 private byte[] _fileContent;
394 private long _lastModified;
395
396 }
397
398 }