1
19
20 package com.liferay.portal.deploy.hot;
21
22 import com.liferay.portal.events.EventsProcessor;
23 import com.liferay.portal.kernel.bean.PortalBeanLocatorUtil;
24 import com.liferay.portal.kernel.configuration.Configuration;
25 import com.liferay.portal.kernel.configuration.ConfigurationFactoryUtil;
26 import com.liferay.portal.kernel.deploy.hot.HotDeployEvent;
27 import com.liferay.portal.kernel.deploy.hot.HotDeployException;
28 import com.liferay.portal.kernel.events.Action;
29 import com.liferay.portal.kernel.events.ActionWrapper;
30 import com.liferay.portal.kernel.events.InvokerSimpleAction;
31 import com.liferay.portal.kernel.events.SimpleAction;
32 import com.liferay.portal.kernel.events.SimpleActionWrapper;
33 import com.liferay.portal.kernel.language.LanguageUtil;
34 import com.liferay.portal.kernel.log.Log;
35 import com.liferay.portal.kernel.log.LogFactoryUtil;
36 import com.liferay.portal.kernel.util.FileUtil;
37 import com.liferay.portal.kernel.util.GetterUtil;
38 import com.liferay.portal.kernel.util.HttpUtil;
39 import com.liferay.portal.kernel.util.StringPool;
40 import com.liferay.portal.kernel.util.StringUtil;
41 import com.liferay.portal.kernel.util.Time;
42 import com.liferay.portal.kernel.util.Validator;
43 import com.liferay.portal.kernel.xml.Document;
44 import com.liferay.portal.kernel.xml.Element;
45 import com.liferay.portal.kernel.xml.SAXReaderUtil;
46 import com.liferay.portal.model.ModelListener;
47 import com.liferay.portal.model.ModelListenerWrapper;
48 import com.liferay.portal.security.auth.CompanyThreadLocal;
49 import com.liferay.portal.service.persistence.BasePersistence;
50 import com.liferay.portal.servlet.filters.cache.CacheUtil;
51 import com.liferay.portal.util.PortalInstances;
52 import com.liferay.portal.util.PortalUtil;
53 import com.liferay.portal.util.PropsKeys;
54 import com.liferay.portal.util.PropsUtil;
55 import com.liferay.portal.util.PropsValues;
56
57 import java.io.File;
58
59 import java.lang.reflect.Field;
60
61 import java.util.ArrayList;
62 import java.util.HashMap;
63 import java.util.HashSet;
64 import java.util.Iterator;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Properties;
68 import java.util.Set;
69
70 import javax.servlet.ServletContext;
71
72
78 public class HookHotDeployListener extends BaseHotDeployListener {
79
80 public void invokeDeploy(HotDeployEvent event) throws HotDeployException {
81 try {
82 doInvokeDeploy(event);
83 }
84 catch (Throwable t) {
85 throwHotDeployException(event, "Error registering hook for ", t);
86 }
87 }
88
89 public void invokeUndeploy(HotDeployEvent event) throws HotDeployException {
90 try {
91 doInvokeUndeploy(event);
92 }
93 catch (Throwable t) {
94 throwHotDeployException(event, "Error unregistering hook for ", t);
95 }
96 }
97
98 protected boolean containsKey(Properties portalProperties, String key) {
99 if (_log.isDebugEnabled()) {
100 return true;
101 }
102 else {
103 return portalProperties.containsKey(key);
104 }
105 }
106
107 protected void destroyCustomJspBag(CustomJspBag customJspBag) {
108 String customJspDir = customJspBag.getCustomJspDir();
109 List<String> customJsps = customJspBag.getCustomJsps();
110
112 String portalWebDir = PortalUtil.getPortalWebDir();
113
114 for (String customJsp : customJsps) {
115 int pos = customJsp.indexOf(customJspDir);
116
117 String portalJsp = customJsp.substring(
118 pos + customJspDir.length(), customJsp.length());
119
120 File portalJspFile = new File(portalWebDir + portalJsp);
121 File portalJspBackupFile = getPortalJspBackupFile(portalJspFile);
122
123 if (portalJspBackupFile.exists()) {
124 FileUtil.copyFile(portalJspBackupFile, portalJspFile);
125
126 portalJspBackupFile.delete();
127 }
128 else if (portalJspFile.exists()) {
129 portalJspFile.delete();
130 }
131 }
132 }
133
134 protected void destroyPortalProperties(Properties portalProperties)
135 throws Exception {
136
137 PropsUtil.removeProperties(portalProperties);
138
139 if (_log.isDebugEnabled() &&
140 portalProperties.containsKey(PropsKeys.LOCALES)) {
141
142 _log.debug(
143 "Portlet locales " +
144 portalProperties.getProperty(PropsKeys.LOCALES));
145 _log.debug(
146 "Original locales " + PropsUtil.get(PropsKeys.LOCALES));
147 _log.debug(
148 "Original locales array length " +
149 PropsUtil.getArray(PropsKeys.LOCALES).length);
150 }
151
152 resetPortalProperties(portalProperties);
153 }
154
155 protected void doInvokeDeploy(HotDeployEvent event) throws Exception {
156 ServletContext servletContext = event.getServletContext();
157
158 String servletContextName = servletContext.getServletContextName();
159
160 if (_log.isDebugEnabled()) {
161 _log.debug("Invoking deploy for " + servletContextName);
162 }
163
164 String xml = HttpUtil.URLtoString(
165 servletContext.getResource("/WEB-INF/liferay-hook.xml"));
166
167 if (xml == null) {
168 return;
169 }
170
171 if (_log.isInfoEnabled()) {
172 _log.info("Registering hook for " + servletContextName);
173 }
174
175 _servletContextNames.add(servletContextName);
176
177 ClassLoader portletClassLoader = event.getContextClassLoader();
178
179 Document doc = SAXReaderUtil.read(xml, true);
180
181 Element root = doc.getRootElement();
182
183 ModelListenersContainer modelListenersContainer =
184 new ModelListenersContainer();
185
186 _modelListenersContainerMap.put(
187 servletContextName, modelListenersContainer);
188
189 List<Element> modelListenerEls = root.elements("model-listener");
190
191 for (Element modelListenerEl : modelListenerEls) {
192 String modelListenerClass = modelListenerEl.elementText(
193 "model-listener-class");
194 String modelName = modelListenerEl.elementText("model-name");
195
196 ModelListener modelListener = initModelListener(
197 modelListenerClass, modelName, portletClassLoader);
198
199 if (modelListener != null) {
200 modelListenersContainer.addModelListener(
201 modelName, modelListener);
202 }
203 }
204
205 String portalPropertiesLocation = root.elementText("portal-properties");
206
207 if (Validator.isNotNull(portalPropertiesLocation)) {
208 Configuration portalPropertiesConfiguration = null;
209
210 try {
211 String name = portalPropertiesLocation;
212
213 int pos = name.lastIndexOf(".properties");
214
215 if (pos != -1) {
216 name = name.substring(0, pos);
217 }
218
219 portalPropertiesConfiguration =
220 ConfigurationFactoryUtil.getConfiguration(
221 portletClassLoader, name);
222 }
223 catch (Exception e) {
224 _log.error("Unable to read " + portalPropertiesLocation, e);
225 }
226
227 if (portalPropertiesConfiguration != null) {
228 Properties portalProperties =
229 portalPropertiesConfiguration.getProperties();
230
231 if (portalProperties.size() > 0) {
232 _portalPropertiesMap.put(
233 servletContextName, portalProperties);
234
235 initPortalProperties(portalProperties);
236 }
237 }
238 }
239
240 String customJspDir = root.elementText("custom-jsp-dir");
241
242 if (Validator.isNotNull(customJspDir)) {
243 if (_log.isDebugEnabled()) {
244 _log.debug("Custom JSP directory: " + customJspDir);
245 }
246
247 List<String> customJsps = new ArrayList<String>();
248
249 String webDir = servletContext.getRealPath(StringPool.SLASH);
250
251 getCustomJsps(servletContext, webDir, customJspDir, customJsps);
252
253 if (customJsps.size() > 0) {
254 CustomJspBag customJspBag = new CustomJspBag(
255 customJspDir, customJsps);
256
257 if (_log.isDebugEnabled()) {
258 StringBuilder sb = new StringBuilder();
259
260 sb.append("Custom JSP files:\n");
261
262 Iterator<String> itr = customJsps.iterator();
263
264 while (itr.hasNext()) {
265 String customJsp = itr.next();
266
267 sb.append(customJsp);
268
269 if (itr.hasNext()) {
270 sb.append(StringPool.NEW_LINE);
271 }
272 }
273
274 _log.debug(sb.toString());
275 }
276
277 _customJspBagsMap.put(servletContextName, customJspBag);
278
279 initCustomJspBag(customJspBag);
280 }
281 }
282
283 EventsContainer eventsContainer = new EventsContainer();
284
285 _eventsContainerMap.put(servletContextName, eventsContainer);
286
287 List<Element> eventEls = root.elements("event");
288
289 for (Element eventEl : eventEls) {
290 String eventClass = eventEl.elementText("event-class");
291 String eventType = eventEl.elementText("event-type");
292
293 Object obj = initEvent(eventClass, eventType, portletClassLoader);
294
295 if (obj != null) {
296 eventsContainer.addEvent(eventType, obj);
297 }
298 }
299
300 if (_log.isInfoEnabled()) {
301 _log.info(
302 "Hook for " + servletContextName + " is available for use");
303 }
304 }
305
306 protected void doInvokeUndeploy(HotDeployEvent event) throws Exception {
307 ServletContext servletContext = event.getServletContext();
308
309 String servletContextName = servletContext.getServletContextName();
310
311 if (_log.isDebugEnabled()) {
312 _log.debug("Invoking undeploy for " + servletContextName);
313 }
314
315 if (!_servletContextNames.remove(servletContextName)) {
316 return;
317 }
318
319 ModelListenersContainer modelListenersContainer =
320 _modelListenersContainerMap.remove(servletContextName);
321
322 if (modelListenersContainer != null) {
323 modelListenersContainer.unregisterModelListeners();
324 }
325
326 Properties portalProperties = _portalPropertiesMap.remove(
327 servletContextName);
328
329 if (portalProperties != null) {
330 destroyPortalProperties(portalProperties);
331 }
332
333 CustomJspBag customJspBag = _customJspBagsMap.remove(
334 servletContextName);
335
336 if (customJspBag != null) {
337 destroyCustomJspBag(customJspBag);
338 }
339
340 EventsContainer eventsContainer = _eventsContainerMap.remove(
341 servletContextName);
342
343 if (eventsContainer != null) {
344 eventsContainer.unregisterEvents();
345 }
346
347 if (_log.isInfoEnabled()) {
348 _log.info("Hook for " + servletContextName + " was unregistered");
349 }
350 }
351
352 protected void getCustomJsps(
353 ServletContext servletContext, String webDir, String resourcePath,
354 List<String> customJsps) {
355
356 Set<String> resourcePaths = servletContext.getResourcePaths(
357 resourcePath);
358
359 for (String curResourcePath : resourcePaths) {
360 if (curResourcePath.endsWith(StringPool.SLASH)) {
361 getCustomJsps(
362 servletContext, webDir, curResourcePath, customJsps);
363 }
364 else {
365 String customJsp = webDir + curResourcePath;
366
367 customJsp = StringUtil.replace(
368 customJsp, StringPool.DOUBLE_SLASH, StringPool.SLASH);
369
370 customJsps.add(customJsp);
371 }
372 }
373 }
374
375 protected BasePersistence getPersistence(String modelName) {
376 int pos = modelName.lastIndexOf(StringPool.PERIOD);
377
378 String entityName = modelName.substring(pos + 1);
379
380 pos = modelName.lastIndexOf(".model.");
381
382 String packagePath = modelName.substring(0, pos);
383
384 return (BasePersistence)PortalBeanLocatorUtil.locate(
385 packagePath + ".service.persistence." + entityName +
386 "Persistence.impl");
387 }
388
389 protected File getPortalJspBackupFile(File portalJspFile) {
390 String fileName = portalJspFile.toString();
391
392 if (fileName.endsWith(".jsp")) {
393 fileName =
394 fileName.substring(0, fileName.length() - 4) + ".portal.jsp";
395 }
396 else if (fileName.endsWith(".jspf")) {
397 fileName =
398 fileName.substring(0, fileName.length() - 5) + ".portal.jsp";
399 }
400
401 return new File(fileName);
402 }
403
404 protected void initCustomJspBag(CustomJspBag customJspBag)
405 throws Exception {
406
407 String customJspDir = customJspBag.getCustomJspDir();
408 List<String> customJsps = customJspBag.getCustomJsps();
409
411 String portalWebDir = PortalUtil.getPortalWebDir();
412
413 for (String customJsp : customJsps) {
414 int pos = customJsp.indexOf(customJspDir);
415
416 String portalJsp = customJsp.substring(
417 pos + customJspDir.length(), customJsp.length());
418
419 File portalJspFile = new File(portalWebDir + portalJsp);
420 File portalJspBackupFile = getPortalJspBackupFile(portalJspFile);
421
422 if (portalJspFile.exists() && !portalJspBackupFile.exists()) {
423 FileUtil.copyFile(portalJspFile, portalJspBackupFile);
424 }
425
426 String customJspContent = FileUtil.read(customJsp);
427
428 FileUtil.write(portalJspFile, customJspContent);
429 }
430 }
431
432 protected Object initEvent(
433 String eventClass, String eventType, ClassLoader portletClassLoader)
434 throws Exception {
435
436 if (eventType.equals(PropsKeys.APPLICATION_STARTUP_EVENTS)) {
437 SimpleAction simpleAction = new InvokerSimpleAction(
438 (SimpleAction)portletClassLoader.loadClass(
439 eventClass).newInstance());
440
441 simpleAction = new SimpleActionWrapper(
442 simpleAction, portletClassLoader);
443
444 long companyId = CompanyThreadLocal.getCompanyId();
445
446 long[] companyIds = PortalInstances.getCompanyIds();
447
448 for (long curCompanyId : companyIds) {
449 CompanyThreadLocal.setCompanyId(curCompanyId);
450
451 simpleAction.run(new String[] {String.valueOf(curCompanyId)});
452 }
453
454 CompanyThreadLocal.setCompanyId(companyId);
455
456 return null;
457 }
458
459 if (eventType.equals(PropsKeys.LOGIN_EVENTS_POST) ||
460 eventType.equals(PropsKeys.LOGIN_EVENTS_PRE) ||
461 eventType.equals(PropsKeys.LOGOUT_EVENTS_POST) ||
462 eventType.equals(PropsKeys.LOGOUT_EVENTS_PRE) ||
463 eventType.equals(PropsKeys.SERVLET_SERVICE_EVENTS_POST) ||
464 eventType.equals(PropsKeys.SERVLET_SERVICE_EVENTS_PRE)) {
465
466 Action action = (Action)portletClassLoader.loadClass(
467 eventClass).newInstance();
468
469 action = new ActionWrapper(action, portletClassLoader);
470
471 EventsProcessor.registerEvent(eventType, action);
472
473 return action;
474 }
475
476 return null;
477 }
478
479 protected ModelListener initModelListener(
480 String modelListenerClass, String modelName,
481 ClassLoader portletClassLoader)
482 throws Exception {
483
484 ModelListener modelListener =
485 (ModelListener)portletClassLoader.loadClass(
486 modelListenerClass).newInstance();
487
488 modelListener = new ModelListenerWrapper(
489 modelListener, portletClassLoader);
490
491 BasePersistence persistence = getPersistence(modelName);
492
493 persistence.registerListener(modelListener);
494
495 return modelListener;
496 }
497
498 protected void initPortalProperties(Properties portalProperties)
499 throws Exception {
500
501 PropsUtil.addProperties(portalProperties);
502
503 if (_log.isDebugEnabled() &&
504 portalProperties.containsKey(PropsKeys.LOCALES)) {
505
506 _log.debug(
507 "Portlet locales " +
508 portalProperties.getProperty(PropsKeys.LOCALES));
509 _log.debug(
510 "Merged locales " + PropsUtil.get(PropsKeys.LOCALES));
511 _log.debug(
512 "Merged locales array length " +
513 PropsUtil.getArray(PropsKeys.LOCALES).length);
514 }
515
516 resetPortalProperties(portalProperties);
517 }
518
519 protected void resetPortalProperties(Properties portalProperties)
520 throws Exception {
521
522 for (String fieldName : _PROPS_KEYS_BOOLEAN) {
523 String key = StringUtil.replace(
524 fieldName.toLowerCase(), StringPool.UNDERLINE,
525 StringPool.PERIOD);
526
527 if (!containsKey(portalProperties, key)) {
528 continue;
529 }
530
531 try {
532 Field field = PropsValues.class.getField(fieldName);
533
534 Boolean value = Boolean.valueOf(GetterUtil.getBoolean(
535 PropsUtil.get(key)));
536
537 field.setBoolean(null, value);
538 }
539 catch (Exception e) {
540 _log.error(
541 "Error setting field " + fieldName + ": " + e.getMessage());
542 }
543 }
544
545 for (String fieldName : _PROPS_KEYS_INTEGER) {
546 String key = StringUtil.replace(
547 fieldName.toLowerCase(), StringPool.UNDERLINE,
548 StringPool.PERIOD);
549
550 if (!containsKey(portalProperties, key)) {
551 continue;
552 }
553
554 try {
555 Field field = PropsValues.class.getField(fieldName);
556
557 Integer value = Integer.valueOf(GetterUtil.getInteger(
558 PropsUtil.get(key)));
559
560 field.setInt(null, value);
561 }
562 catch (Exception e) {
563 _log.error(
564 "Error setting field " + fieldName + ": " + e.getMessage());
565 }
566 }
567
568 for (String fieldName : _PROPS_KEYS_LONG) {
569 String key = StringUtil.replace(
570 fieldName.toLowerCase(), StringPool.UNDERLINE,
571 StringPool.PERIOD);
572
573 if (!containsKey(portalProperties, key)) {
574 continue;
575 }
576
577 try {
578 Field field = PropsValues.class.getField(fieldName);
579
580 Long value = Long.valueOf(GetterUtil.getLong(
581 PropsUtil.get(key)));
582
583 field.setLong(null, value);
584 }
585 catch (Exception e) {
586 _log.error(
587 "Error setting field " + fieldName + ": " + e.getMessage());
588 }
589 }
590
591 for (String fieldName : _PROPS_KEYS_STRING) {
592 String key = StringUtil.replace(
593 fieldName.toLowerCase(), StringPool.UNDERLINE,
594 StringPool.PERIOD);
595
596 if (!containsKey(portalProperties, key)) {
597 continue;
598 }
599
600 try {
601 Field field = PropsValues.class.getField(fieldName);
602
603 String value = GetterUtil.getString(PropsUtil.get(key));
604
605 field.set(null, value);
606 }
607 catch (Exception e) {
608 _log.error(
609 "Error setting field " + fieldName + ": " + e.getMessage());
610 }
611 }
612
613 for (String fieldName : _PROPS_KEYS_STRING_ARRAY) {
614 String key = StringUtil.replace(
615 fieldName.toLowerCase(), StringPool.UNDERLINE,
616 StringPool.PERIOD);
617
618 if (!containsKey(portalProperties, key)) {
619 continue;
620 }
621
622 try {
623 Field field = PropsValues.class.getField(fieldName);
624
625 String[] value = PropsUtil.getArray(key);
626
627 field.set(null, value);
628 }
629 catch (Exception e) {
630 _log.error(
631 "Error setting field " + fieldName + ": " + e.getMessage());
632 }
633 }
634
635 if (containsKey(portalProperties, PropsKeys.LOCALES)) {
636 PropsValues.LOCALES = PropsUtil.getArray(PropsKeys.LOCALES);
637
638 LanguageUtil.init();
639 }
640
641 CacheUtil.clearCache();
642 }
643
644 private static final String[] _PROPS_KEYS_BOOLEAN = new String[] {
645 "AUTH_FORWARD_BY_LAST_PATH",
646 "JAVASCRIPT_FAST_LOAD",
647 "LAYOUT_TEMPLATE_CACHE_ENABLED",
648 "LAYOUT_USER_PRIVATE_LAYOUTS_AUTO_CREATE",
649 "LAYOUT_USER_PRIVATE_LAYOUTS_ENABLED",
650 "LAYOUT_USER_PRIVATE_LAYOUTS_MODIFIABLE",
651 "LAYOUT_USER_PUBLIC_LAYOUTS_AUTO_CREATE",
652 "LAYOUT_USER_PUBLIC_LAYOUTS_ENABLED",
653 "LAYOUT_USER_PUBLIC_LAYOUTS_MODIFIABLE",
654 "MY_PLACES_SHOW_COMMUNITY_PRIVATE_SITES_WITH_NO_LAYOUTS",
655 "MY_PLACES_SHOW_COMMUNITY_PUBLIC_SITES_WITH_NO_LAYOUTS",
656 "MY_PLACES_SHOW_ORGANIZATION_PRIVATE_SITES_WITH_NO_LAYOUTS",
657 "MY_PLACES_SHOW_ORGANIZATION_PUBLIC_SITES_WITH_NO_LAYOUTS",
658 "MY_PLACES_SHOW_USER_PRIVATE_SITES_WITH_NO_LAYOUTS",
659 "MY_PLACES_SHOW_USER_PUBLIC_SITES_WITH_NO_LAYOUTS",
660 "ORGANIZATIONS_COUNTRY_REQUIRED",
661 "TERMS_OF_USE_REQUIRED",
662 "THEME_CSS_FAST_LOAD",
663 "THEME_IMAGES_FAST_LOAD"
664 };
665
666 private static final String[] _PROPS_KEYS_INTEGER = new String[] {
667 };
668
669 private static final String[] _PROPS_KEYS_LONG = new String[] {
670 };
671
672 private static final String[] _PROPS_KEYS_STRING = new String[] {
673 "PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR",
674 "PASSWORDS_PASSWORDPOLICYTOOLKIT_STATIC"
675 };
676
677 private static final String[] _PROPS_KEYS_STRING_ARRAY = new String[] {
678 "LAYOUT_STATIC_PORTLETS_ALL"
679 };
680
681 private static Log _log =
682 LogFactoryUtil.getLog(HookHotDeployListener.class);
683
684 private Set<String> _servletContextNames = new HashSet<String>();
685 private Map<String, EventsContainer> _eventsContainerMap =
686 new HashMap<String, EventsContainer>();
687 private Map<String, ModelListenersContainer> _modelListenersContainerMap =
688 new HashMap<String, ModelListenersContainer>();
689 private Map<String, Properties> _portalPropertiesMap =
690 new HashMap<String, Properties>();
691 private Map<String, CustomJspBag> _customJspBagsMap =
692 new HashMap<String, CustomJspBag>();
693
694 private class EventsContainer {
695
696 public void addEvent(String eventType, Object event) {
697 List<Object> events = _eventsMap.get(eventType);
698
699 if (events == null) {
700 events = new ArrayList<Object>();
701
702 _eventsMap.put(eventType, events);
703 }
704
705 events.add(event);
706 }
707
708 public void unregisterEvents() {
709 for (Map.Entry<String, List<Object>> entry :
710 _eventsMap.entrySet()) {
711
712 String eventType = entry.getKey();
713 List<Object> events = entry.getValue();
714
715 for (Object event : events) {
716 EventsProcessor.unregisterEvent(eventType, event);
717 }
718 }
719 }
720
721 private Map<String, List<Object>> _eventsMap =
722 new HashMap<String, List<Object>>();
723
724 }
725
726 private class ModelListenersContainer {
727
728 public void addModelListener(
729 String modelName, ModelListener modelListener) {
730
731 List<ModelListener> modelListeners = _modelListenersMap.get(
732 modelName);
733
734 if (modelListeners == null) {
735 modelListeners = new ArrayList<ModelListener>();
736
737 _modelListenersMap.put(modelName, modelListeners);
738 }
739
740 modelListeners.add(modelListener);
741 }
742
743 public void unregisterModelListeners() {
744 for (Map.Entry<String, List<ModelListener>> entry :
745 _modelListenersMap.entrySet()) {
746
747 String modelName = entry.getKey();
748 List<ModelListener> modelListeners = entry.getValue();
749
750 BasePersistence persistence = getPersistence(modelName);
751
752 for (ModelListener modelListener : modelListeners) {
753 persistence.unregisterListener(modelListener);
754 }
755 }
756 }
757
758 private Map<String, List<ModelListener>> _modelListenersMap =
759 new HashMap<String, List<ModelListener>>();
760
761 }
762
763 private class CustomJspBag {
764
765 public CustomJspBag(String customJspDir, List<String> customJsps) {
766 _customJspDir = customJspDir;
767 _customJsps = customJsps;
768 _timestamp = Time.getTimestamp();
769 }
770
771 public String getCustomJspDir() {
772 return _customJspDir;
773 }
774
775 public List<String> getCustomJsps() {
776 return _customJsps;
777 }
778
779 public String getTimestamp() {
780 return _timestamp;
781 }
782
783 private String _customJspDir;
784 private List<String> _customJsps;
785 private String _timestamp;
786
787 }
788
789 }