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