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