001
014
015 package com.liferay.portal.servlet;
016
017 import com.liferay.portal.ModulePathSetException;
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 throw new ModulePathSetException("Modules paths set is empty");
115 }
116
117 String[] modulePaths = modulePathsSet.toArray(
118 new String[modulePathsSet.size()]);
119
120 String firstModulePath = modulePaths[0];
121
122 String extension = FileUtil.getExtension(firstModulePath);
123
124 String minifierType = ParamUtil.getString(request, "minifierType");
125
126 if (Validator.isNull(minifierType)) {
127 minifierType = "js";
128
129 if (StringUtil.equalsIgnoreCase(extension, _CSS_EXTENSION)) {
130 minifierType = "css";
131 }
132 }
133
134 if (!minifierType.equals("css") && !minifierType.equals("js")) {
135 minifierType = "js";
136 }
137
138 String modulePathsString = null;
139
140 byte[][] bytesArray = null;
141
142 if (!PropsValues.COMBO_CHECK_TIMESTAMP) {
143 modulePathsString = Arrays.toString(modulePaths);
144
145 if (minifierType.equals("css") &&
146 PortalUtil.isRightToLeft(request)) {
147
148 modulePathsString += ".rtl";
149 }
150
151 bytesArray = _bytesArrayPortalCache.get(modulePathsString);
152 }
153
154 if (bytesArray == null) {
155 ServletContext servletContext = getServletContext();
156
157 String rootPath = ServletContextUtil.getRootPath(servletContext);
158
159 bytesArray = new byte[modulePaths.length][];
160
161 for (int i = 0; i < modulePaths.length; i++) {
162 String modulePath = modulePaths[i];
163
164 if (!validateModuleExtension(modulePath)) {
165 response.setHeader(
166 HttpHeaders.CACHE_CONTROL,
167 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
168 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
169
170 return;
171 }
172
173 byte[] bytes = new byte[0];
174
175 if (Validator.isNotNull(modulePath)) {
176 modulePath = StringUtil.replaceFirst(
177 modulePath, PortalUtil.getPathContext(),
178 StringPool.BLANK);
179
180 URL url = getResourceURL(
181 servletContext, rootPath, modulePath);
182
183 if (url == null) {
184 response.setHeader(
185 HttpHeaders.CACHE_CONTROL,
186 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
187 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
188
189 return;
190 }
191
192 bytes = getResourceContent(
193 request, response, url, modulePath, minifierType);
194 }
195
196 bytesArray[i] = bytes;
197 }
198
199 if ((modulePathsString != null) &&
200 !PropsValues.COMBO_CHECK_TIMESTAMP) {
201
202 _bytesArrayPortalCache.put(modulePathsString, bytesArray);
203 }
204 }
205
206 String contentType = ContentTypes.TEXT_JAVASCRIPT;
207
208 if (StringUtil.equalsIgnoreCase(extension, _CSS_EXTENSION)) {
209 contentType = ContentTypes.TEXT_CSS;
210 }
211
212 response.setContentType(contentType);
213
214 ServletResponseUtil.write(response, bytesArray);
215 }
216
217 protected byte[] getResourceContent(
218 HttpServletRequest request, HttpServletResponse response,
219 URL resourceURL, String resourcePath, String minifierType)
220 throws IOException {
221
222 String fileContentKey = resourcePath.concat(StringPool.QUESTION).concat(
223 minifierType);
224
225 FileContentBag fileContentBag = _fileContentBagPortalCache.get(
226 fileContentKey);
227
228 if ((fileContentBag != null) && !PropsValues.COMBO_CHECK_TIMESTAMP) {
229 return fileContentBag._fileContent;
230 }
231
232 URLConnection urlConnection = null;
233
234 if (resourceURL != null) {
235 urlConnection = resourceURL.openConnection();
236 }
237
238 if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
239 long elapsedTime =
240 System.currentTimeMillis() - fileContentBag._lastModified;
241
242 if ((urlConnection != null) &&
243 (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
244 (urlConnection.getLastModified() ==
245 fileContentBag._lastModified)) {
246
247 return fileContentBag._fileContent;
248 }
249
250 _fileContentBagPortalCache.remove(fileContentKey);
251 }
252
253 if (resourceURL == null) {
254 fileContentBag = _EMPTY_FILE_CONTENT_BAG;
255 }
256 else {
257 String stringFileContent = StringUtil.read(
258 urlConnection.getInputStream());
259
260 if (!StringUtil.endsWith(resourcePath, _CSS_MINIFIED_SUFFIX) &&
261 !StringUtil.endsWith(
262 resourcePath, _JAVASCRIPT_MINIFIED_SUFFIX)) {
263
264 if (minifierType.equals("css")) {
265 try {
266 stringFileContent = DynamicCSSUtil.parseSass(
267 getServletContext(), request, resourcePath,
268 stringFileContent);
269 }
270 catch (Exception e) {
271 _log.error(
272 "Unable to parse SASS on CSS " +
273 resourceURL.getPath(), e);
274
275 if (_log.isDebugEnabled()) {
276 _log.debug(stringFileContent);
277 }
278
279 response.setHeader(
280 HttpHeaders.CACHE_CONTROL,
281 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
282 }
283
284 String baseURL = StringPool.BLANK;
285
286 int index = resourcePath.lastIndexOf(CharPool.SLASH);
287
288 if (index != -1) {
289 baseURL = resourcePath.substring(0, index + 1);
290 }
291
292 stringFileContent = AggregateUtil.updateRelativeURLs(
293 stringFileContent, baseURL);
294
295 stringFileContent = MinifierUtil.minifyCss(
296 stringFileContent);
297 }
298 else if (minifierType.equals("js")) {
299 stringFileContent = MinifierUtil.minifyJavaScript(
300 stringFileContent);
301 }
302 }
303
304 fileContentBag = new FileContentBag(
305 stringFileContent.getBytes(StringPool.UTF8),
306 urlConnection.getLastModified());
307 }
308
309 if (PropsValues.COMBO_CHECK_TIMESTAMP) {
310 int timeToLive =
311 (int)(PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL / Time.SECOND);
312
313 _fileContentBagPortalCache.put(
314 fileContentKey, fileContentBag, timeToLive);
315 }
316
317 return fileContentBag._fileContent;
318 }
319
320 protected URL getResourceURL(
321 ServletContext servletContext, String rootPath, String path)
322 throws Exception {
323
324 URL url = servletContext.getResource(path);
325
326 if (url == null) {
327 return null;
328 }
329
330 String filePath = ServletContextUtil.getResourcePath(url);
331
332 int pos = filePath.indexOf(
333 rootPath.concat(StringPool.SLASH).concat(_JAVASCRIPT_DIR));
334
335 if (pos == 0) {
336 return url;
337 }
338
339 return null;
340 }
341
342 protected boolean validateModuleExtension(String moduleName)
343 throws Exception {
344
345 boolean validModuleExtension = false;
346
347 String[] fileExtensions = PrefsPropsUtil.getStringArray(
348 PropsKeys.COMBO_ALLOWED_FILE_EXTENSIONS, StringPool.COMMA);
349
350 for (String fileExtension : fileExtensions) {
351 if (StringPool.STAR.equals(fileExtension) ||
352 StringUtil.endsWith(moduleName, fileExtension)) {
353
354 validModuleExtension = true;
355
356 break;
357 }
358 }
359
360 return validModuleExtension;
361 }
362
363 private static final String _CSS_EXTENSION = "css";
364
365 private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
366
367 private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
368 new FileContentBag(new byte[0], 0);
369
370 private static final String _JAVASCRIPT_DIR = "html/js";
371
372 private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
373
374 private static Log _log = LogFactoryUtil.getLog(ComboServlet.class);
375
376 private PortalCache<String, byte[][]> _bytesArrayPortalCache =
377 SingleVMPoolUtil.getCache(ComboServlet.class.getName());
378 private PortalCache<String, FileContentBag> _fileContentBagPortalCache =
379 SingleVMPoolUtil.getCache(FileContentBag.class.getName());
380 private Set<String> _protectedParameters = SetUtil.fromArray(
381 new String[] {"b", "browserId", "minifierType", "languageId", "t"});
382
383 private static class FileContentBag implements Serializable {
384
385 public FileContentBag(byte[] fileContent, long lastModifiedTime) {
386 _fileContent = fileContent;
387 _lastModified = lastModifiedTime;
388 }
389
390 private byte[] _fileContent;
391 private long _lastModified;
392
393 }
394
395 }