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