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