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