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