1   /**
2    * Copyright (c) 2000-2008 Liferay, Inc. All rights reserved.
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20   * SOFTWARE.
21   */
22  
23  package com.liferay.portal.deploy.hot;
24  
25  import com.liferay.portal.events.EventsProcessor;
26  import com.liferay.portal.kernel.bean.PortalBeanLocatorUtil;
27  import com.liferay.portal.kernel.configuration.Configuration;
28  import com.liferay.portal.kernel.configuration.ConfigurationFactoryUtil;
29  import com.liferay.portal.kernel.deploy.hot.HotDeployEvent;
30  import com.liferay.portal.kernel.deploy.hot.HotDeployException;
31  import com.liferay.portal.kernel.events.Action;
32  import com.liferay.portal.kernel.events.InvokerSimpleAction;
33  import com.liferay.portal.kernel.events.SimpleAction;
34  import com.liferay.portal.kernel.language.LanguageUtil;
35  import com.liferay.portal.kernel.util.FileUtil;
36  import com.liferay.portal.kernel.util.GetterUtil;
37  import com.liferay.portal.kernel.util.HttpUtil;
38  import com.liferay.portal.kernel.util.StringPool;
39  import com.liferay.portal.kernel.util.StringUtil;
40  import com.liferay.portal.kernel.util.Time;
41  import com.liferay.portal.kernel.util.Validator;
42  import com.liferay.portal.kernel.xml.Document;
43  import com.liferay.portal.kernel.xml.Element;
44  import com.liferay.portal.kernel.xml.SAXReaderUtil;
45  import com.liferay.portal.model.ModelListener;
46  import com.liferay.portal.service.persistence.BasePersistence;
47  import com.liferay.portal.servlet.filters.layoutcache.LayoutCacheUtil;
48  import com.liferay.portal.util.PortalInstances;
49  import com.liferay.portal.util.PortalUtil;
50  import com.liferay.portal.util.PropsKeys;
51  import com.liferay.portal.util.PropsUtil;
52  import com.liferay.portal.util.PropsValues;
53  import com.liferay.util.WebDirDetector;
54  
55  import java.io.File;
56  
57  import java.lang.reflect.Field;
58  
59  import java.util.ArrayList;
60  import java.util.HashMap;
61  import java.util.Iterator;
62  import java.util.List;
63  import java.util.Map;
64  import java.util.Properties;
65  import java.util.Set;
66  
67  import javax.servlet.ServletContext;
68  
69  import org.apache.commons.logging.Log;
70  import org.apache.commons.logging.LogFactory;
71  
72  /**
73   * <a href="HookHotDeployListener.java.html"><b><i>View Source</i></b></a>
74   *
75   * @author Brian Wing Shun Chan
76   *
77   */
78  public class HookHotDeployListener extends BaseHotDeployListener {
79  
80      public void invokeDeploy(HotDeployEvent event) throws HotDeployException {
81          try {
82              doInvokeDeploy(event);
83          }
84          catch (Exception e) {
85              throwHotDeployException(event, "Error registering hook for ", e);
86          }
87      }
88  
89      public void invokeUndeploy(HotDeployEvent event) throws HotDeployException {
90          try {
91              doInvokeUndeploy(event);
92          }
93          catch (Exception e) {
94              throwHotDeployException(event, "Error unregistering hook for ", e);
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         //String timestamp = customJspBag.getTimestamp();
111 
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 = new File(
122                 portalWebDir + portalJsp + ".portal");
123 
124             if (portalJspBackupFile.exists()) {
125                 FileUtil.copyFile(portalJspBackupFile, portalJspFile);
126 
127                 portalJspBackupFile.delete();
128             }
129         }
130     }
131 
132     protected void destroyPortalProperties(Properties portalProperties)
133         throws Exception {
134 
135         PropsUtil.removeProperties(portalProperties);
136 
137         if (_log.isDebugEnabled() &&
138             portalProperties.containsKey(PropsKeys.LOCALES)) {
139 
140             _log.debug(
141                 "Portlet locales " +
142                     portalProperties.getProperty(PropsKeys.LOCALES));
143             _log.debug(
144                 "Original locales " + PropsUtil.get(PropsKeys.LOCALES));
145             _log.debug(
146                 "Original locales array length " +
147                     PropsUtil.getArray(PropsKeys.LOCALES).length);
148         }
149 
150         resetPortalProperties(portalProperties);
151     }
152 
153     protected void doInvokeDeploy(HotDeployEvent event) throws Exception {
154         ServletContext servletContext = event.getServletContext();
155 
156         String servletContextName = servletContext.getServletContextName();
157 
158         if (_log.isDebugEnabled()) {
159             _log.debug("Invoking deploy for " + servletContextName);
160         }
161 
162         String xml = HttpUtil.URLtoString(
163             servletContext.getResource("/WEB-INF/liferay-hook.xml"));
164 
165         if (xml == null) {
166             return;
167         }
168 
169         if (_log.isInfoEnabled()) {
170             _log.info("Registering hook for " + servletContextName);
171         }
172 
173         ClassLoader portletClassLoader = event.getContextClassLoader();
174 
175         Document doc = SAXReaderUtil.read(xml, true);
176 
177         Element root = doc.getRootElement();
178 
179         EventsContainer eventsContainer = new EventsContainer();
180 
181         _eventsContainerMap.put(servletContextName, new EventsContainer());
182 
183         List<Element> eventEls = root.elements("event");
184 
185         for (Element eventEl : eventEls) {
186             String eventClass = eventEl.elementText("event-class");
187             String eventType = eventEl.elementText("event-type");
188 
189             Object obj = initEvent(eventClass, eventType, portletClassLoader);
190 
191             if (obj != null) {
192                 eventsContainer.addEvent(eventType, obj);
193             }
194         }
195 
196         ModelListenersContainer modelListenersContainer =
197             new ModelListenersContainer();
198 
199         _modelListenersContainerMap.put(
200             servletContextName, new ModelListenersContainer());
201 
202         List<Element> modelListenerEls = root.elements("model-listener");
203 
204         for (Element modelListenerEl : modelListenerEls) {
205             String modelListenerClass = modelListenerEl.elementText(
206                 "model-listener-class");
207             String modelName = modelListenerEl.elementText("model-name");
208 
209             ModelListener modelListener = initModelListener(
210                 modelListenerClass, modelName, portletClassLoader);
211 
212             if (modelListener != null) {
213                 modelListenersContainer.addModelListener(
214                     modelName, modelListener);
215             }
216         }
217 
218         String portalPropertiesLocation = root.elementText("portal-properties");
219 
220         if (Validator.isNotNull(portalPropertiesLocation)) {
221             Configuration portalPropertiesConfiguration = null;
222 
223             try {
224                 String name = portalPropertiesLocation;
225 
226                 int pos = name.lastIndexOf(".properties");
227 
228                 if (pos != -1) {
229                     name = name.substring(0, pos);
230                 }
231 
232                 portalPropertiesConfiguration =
233                     ConfigurationFactoryUtil.getConfiguration(
234                         portletClassLoader, name);
235             }
236             catch (Exception e) {
237                 _log.error("Unable to read " + portalPropertiesLocation, e);
238             }
239 
240             if (portalPropertiesConfiguration != null) {
241                 Properties portalProperties =
242                     portalPropertiesConfiguration.getProperties();
243 
244                 if (portalProperties.size() > 0) {
245                     _portalPropertiesMap.put(
246                         servletContextName, portalProperties);
247 
248                     initPortalProperties(portalProperties);
249                 }
250             }
251         }
252 
253         String customJspDir = root.elementText("custom-jsp-dir");
254 
255         if (Validator.isNotNull(customJspDir)) {
256             if (_log.isDebugEnabled()) {
257                 _log.debug("Custom JSP directory: " + customJspDir);
258             }
259 
260             List<String> customJsps = new ArrayList<String>();
261 
262             String webDir = WebDirDetector.getRootDir(portletClassLoader);
263 
264             getCustomJsps(servletContext, webDir, customJspDir, customJsps);
265 
266             if (customJsps.size() > 0) {
267                 CustomJspBag customJspBag = new CustomJspBag(
268                     customJspDir, customJsps);
269 
270                 if (_log.isDebugEnabled()) {
271                     StringBuilder sb = new StringBuilder();
272 
273                     sb.append("Custom JSP files:\n");
274 
275                     Iterator<String> itr = customJsps.iterator();
276 
277                     while (itr.hasNext()) {
278                         String customJsp = itr.next();
279 
280                         sb.append(customJsp);
281 
282                         if (itr.hasNext()) {
283                             sb.append(StringPool.NEW_LINE);
284                         }
285                     }
286 
287                     _log.debug(sb.toString());
288                 }
289 
290                 _customJspBagsMap.put(servletContextName, customJspBag);
291 
292                 initCustomJspBag(customJspBag);
293             }
294         }
295 
296         if (_log.isInfoEnabled()) {
297             _log.info(
298                 "Hook for " + servletContextName + " registered successfully");
299         }
300     }
301 
302     protected void doInvokeUndeploy(HotDeployEvent event) throws Exception {
303         ServletContext servletContext = event.getServletContext();
304 
305         String servletContextName = servletContext.getServletContextName();
306 
307         if (_log.isDebugEnabled()) {
308             _log.debug("Invoking undeploy for " + servletContextName);
309         }
310 
311         String xml = HttpUtil.URLtoString(
312             servletContext.getResource("/WEB-INF/liferay-hook.xml"));
313 
314         if (xml == null) {
315             return;
316         }
317 
318         EventsContainer eventsContainer = _eventsContainerMap.get(
319             servletContextName);
320 
321         if (eventsContainer != null) {
322             eventsContainer.unregisterEvents();
323         }
324 
325         ModelListenersContainer modelListenersContainer =
326             _modelListenersContainerMap.get(servletContextName);
327 
328         if (modelListenersContainer != null) {
329             modelListenersContainer.unregisterModelListeners();
330         }
331 
332         Properties portalProperties = _portalPropertiesMap.get(
333             servletContextName);
334 
335         if (portalProperties != null) {
336             destroyPortalProperties(portalProperties);
337         }
338 
339         CustomJspBag customJspBag = _customJspBagsMap.get(servletContextName);
340 
341         if (customJspBag != null) {
342             destroyCustomJspBag(customJspBag);
343         }
344 
345         if (_log.isInfoEnabled()) {
346             _log.info(
347                 "Hook for " + servletContextName +
348                     " unregistered successfully");
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 void initCustomJspBag(CustomJspBag customJspBag)
390         throws Exception {
391 
392         String customJspDir = customJspBag.getCustomJspDir();
393         List<String> customJsps = customJspBag.getCustomJsps();
394         //String timestamp = customJspBag.getTimestamp();
395 
396         String portalWebDir = PortalUtil.getPortalWebDir();
397 
398         for (String customJsp : customJsps) {
399             int pos = customJsp.indexOf(customJspDir);
400 
401             String portalJsp = customJsp.substring(
402                 pos + customJspDir.length(), customJsp.length());
403 
404             File portalJspFile = new File(portalWebDir + portalJsp);
405             File portalJspBackupFile = new File(
406                 portalWebDir + portalJsp + ".portal");
407 
408             if (portalJspFile.exists() && !portalJspBackupFile.exists()) {
409                 FileUtil.copyFile(portalJspFile, portalJspBackupFile);
410             }
411 
412             String customJspContent = FileUtil.read(customJsp);
413 
414             FileUtil.write(portalJspFile, customJspContent);
415         }
416     }
417 
418     protected Object initEvent(
419             String eventClass, String eventType, ClassLoader portletClassLoader)
420         throws Exception {
421 
422         if (eventType.equals(PropsKeys.APPLICATION_STARTUP_EVENTS)) {
423             SimpleAction simpleAction = new InvokerSimpleAction(
424                 (SimpleAction)portletClassLoader.loadClass(
425                     eventClass).newInstance());
426 
427             long[] companyIds = PortalInstances.getCompanyIds();
428 
429             for (long companyId : companyIds) {
430                 simpleAction.run(new String[] {String.valueOf(companyId)});
431             }
432 
433             return null;
434         }
435 
436         if (eventType.equals(PropsKeys.LOGIN_EVENTS_POST) ||
437             eventType.equals(PropsKeys.LOGIN_EVENTS_PRE) ||
438             eventType.equals(PropsKeys.LOGOUT_EVENTS_POST) ||
439             eventType.equals(PropsKeys.LOGOUT_EVENTS_PRE) ||
440             eventType.equals(PropsKeys.SERVLET_SERVICE_EVENTS_POST) ||
441             eventType.equals(PropsKeys.SERVLET_SERVICE_EVENTS_PRE)) {
442 
443             Action action = (Action)portletClassLoader.loadClass(
444                 eventClass).newInstance();
445 
446             EventsProcessor.registerEvent(eventType, action);
447 
448             return action;
449         }
450 
451         return null;
452     }
453 
454     protected ModelListener initModelListener(
455             String modelListenerClass, String modelName,
456             ClassLoader portletClassLoader)
457         throws Exception {
458 
459         ModelListener modelListener =
460             (ModelListener)portletClassLoader.loadClass(
461                 modelListenerClass).newInstance();
462 
463         BasePersistence persistence = getPersistence(modelName);
464 
465         persistence.registerListener(modelListener);
466 
467         return modelListener;
468     }
469 
470     protected void initPortalProperties(Properties portalProperties)
471         throws Exception {
472 
473         PropsUtil.addProperties(portalProperties);
474 
475         if (_log.isDebugEnabled() &&
476             portalProperties.containsKey(PropsKeys.LOCALES)) {
477 
478             _log.debug(
479                 "Portlet locales " +
480                     portalProperties.getProperty(PropsKeys.LOCALES));
481             _log.debug(
482                 "Merged locales " + PropsUtil.get(PropsKeys.LOCALES));
483             _log.debug(
484                 "Merged locales array length " +
485                     PropsUtil.getArray(PropsKeys.LOCALES).length);
486         }
487 
488         resetPortalProperties(portalProperties);
489     }
490 
491     protected void resetPortalProperties(Properties portalProperties)
492         throws Exception {
493 
494         for (String fieldName : _PROPS_KEYS_BOOLEAN) {
495             String key = StringUtil.replace(
496                 fieldName.toLowerCase(), StringPool.UNDERLINE,
497                 StringPool.PERIOD);
498 
499             if (!containsKey(portalProperties, key)) {
500                 continue;
501             }
502 
503             try {
504                 Field field = PropsValues.class.getField(fieldName);
505 
506                 Boolean value = Boolean.valueOf(GetterUtil.getBoolean(
507                     PropsUtil.get(key)));
508 
509                 field.setBoolean(null, value);
510             }
511             catch (Exception e) {
512                 _log.error(
513                     "Error setting field " + fieldName + ": " + e.getMessage());
514             }
515         }
516 
517         for (String fieldName : _PROPS_KEYS_INTEGER) {
518             String key = StringUtil.replace(
519                 fieldName.toLowerCase(), StringPool.UNDERLINE,
520                 StringPool.PERIOD);
521 
522             if (!containsKey(portalProperties, key)) {
523                 continue;
524             }
525 
526             try {
527                 Field field = PropsValues.class.getField(fieldName);
528 
529                 Integer value = Integer.valueOf(GetterUtil.getInteger(
530                     PropsUtil.get(key)));
531 
532                 field.setInt(null, value);
533             }
534             catch (Exception e) {
535                 _log.error(
536                     "Error setting field " + fieldName + ": " + e.getMessage());
537             }
538         }
539 
540         for (String fieldName : _PROPS_KEYS_LONG) {
541             String key = StringUtil.replace(
542                 fieldName.toLowerCase(), StringPool.UNDERLINE,
543                 StringPool.PERIOD);
544 
545             if (!containsKey(portalProperties, key)) {
546                 continue;
547             }
548 
549             try {
550                 Field field = PropsValues.class.getField(fieldName);
551 
552                 Long value = Long.valueOf(GetterUtil.getLong(
553                     PropsUtil.get(key)));
554 
555                 field.setLong(null, value);
556             }
557             catch (Exception e) {
558                 _log.error(
559                     "Error setting field " + fieldName + ": " + e.getMessage());
560             }
561         }
562 
563         for (String fieldName : _PROPS_KEYS_STRING) {
564             String key = StringUtil.replace(
565                 fieldName.toLowerCase(), StringPool.UNDERLINE,
566                 StringPool.PERIOD);
567 
568             if (!containsKey(portalProperties, key)) {
569                 continue;
570             }
571 
572             try {
573                 Field field = PropsValues.class.getField(fieldName);
574 
575                 String value = GetterUtil.getString(PropsUtil.get(key));
576 
577                 field.set(null, value);
578             }
579             catch (Exception e) {
580                 _log.error(
581                     "Error setting field " + fieldName + ": " + e.getMessage());
582             }
583         }
584 
585         for (String fieldName : _PROPS_KEYS_STRING_ARRAY) {
586             String key = StringUtil.replace(
587                 fieldName.toLowerCase(), StringPool.UNDERLINE,
588                 StringPool.PERIOD);
589 
590             if (!containsKey(portalProperties, key)) {
591                 continue;
592             }
593 
594             try {
595                 Field field = PropsValues.class.getField(fieldName);
596 
597                 String[] value = PropsUtil.getArray(key);
598 
599                 field.set(null, value);
600             }
601             catch (Exception e) {
602                 _log.error(
603                     "Error setting field " + fieldName + ": " + e.getMessage());
604             }
605         }
606 
607         if (containsKey(portalProperties, PropsKeys.LOCALES)) {
608             PropsValues.LOCALES = PropsUtil.getArray(PropsKeys.LOCALES);
609 
610             LanguageUtil.init();
611         }
612 
613         LayoutCacheUtil.clearCache();
614     }
615 
616     private static final String[] _PROPS_KEYS_BOOLEAN = new String[] {
617         "AUTH_FORWARD_BY_LAST_PATH",
618         "JAVASCRIPT_FAST_LOAD",
619         "LAYOUT_TEMPLATE_CACHE_ENABLED",
620         "LAYOUT_USER_PRIVATE_LAYOUTS_AUTO_CREATE",
621         "LAYOUT_USER_PRIVATE_LAYOUTS_ENABLED",
622         "LAYOUT_USER_PRIVATE_LAYOUTS_MODIFIABLE",
623         "LAYOUT_USER_PUBLIC_LAYOUTS_AUTO_CREATE",
624         "LAYOUT_USER_PUBLIC_LAYOUTS_ENABLED",
625         "LAYOUT_USER_PUBLIC_LAYOUTS_MODIFIABLE",
626         "MY_PLACES_SHOW_COMMUNITY_PRIVATE_SITES_WITH_NO_LAYOUTS",
627         "MY_PLACES_SHOW_COMMUNITY_PUBLIC_SITES_WITH_NO_LAYOUTS",
628         "MY_PLACES_SHOW_ORGANIZATION_PRIVATE_SITES_WITH_NO_LAYOUTS",
629         "MY_PLACES_SHOW_ORGANIZATION_PUBLIC_SITES_WITH_NO_LAYOUTS",
630         "MY_PLACES_SHOW_USER_PRIVATE_SITES_WITH_NO_LAYOUTS",
631         "MY_PLACES_SHOW_USER_PUBLIC_SITES_WITH_NO_LAYOUTS",
632         "ORGANIZATIONS_COUNTRY_REQUIRED",
633         "TERMS_OF_USE_REQUIRED",
634         "THEME_CSS_FAST_LOAD"
635     };
636 
637     private static final String[] _PROPS_KEYS_INTEGER = new String[] {
638     };
639 
640     private static final String[] _PROPS_KEYS_LONG = new String[] {
641     };
642 
643     private static final String[] _PROPS_KEYS_STRING = new String[] {
644         "PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR",
645         "PASSWORDS_PASSWORDPOLICYTOOLKIT_STATIC"
646     };
647 
648     private static final String[] _PROPS_KEYS_STRING_ARRAY = new String[] {
649         "LAYOUT_STATIC_PORTLETS_ALL"
650     };
651 
652     private static Log _log = LogFactory.getLog(HookHotDeployListener.class);
653 
654     private Map<String, EventsContainer> _eventsContainerMap =
655         new HashMap<String, EventsContainer>();
656     private Map<String, ModelListenersContainer> _modelListenersContainerMap =
657         new HashMap<String, ModelListenersContainer>();
658     private Map<String, Properties> _portalPropertiesMap =
659         new HashMap<String, Properties>();
660     private Map<String, CustomJspBag> _customJspBagsMap =
661         new HashMap<String, CustomJspBag>();
662 
663     private class EventsContainer {
664 
665         public void addEvent(String eventType, Object event) {
666             List<Object> events = _eventsMap.get(eventType);
667 
668             if (events == null) {
669                 events = new ArrayList<Object>();
670 
671                 _eventsMap.put(eventType, events);
672             }
673 
674             events.add(event);
675         }
676 
677         public void unregisterEvents() {
678             for (Map.Entry<String, List<Object>> entry :
679                     _eventsMap.entrySet()) {
680 
681                 String eventType = entry.getKey();
682                 List<Object> events = entry.getValue();
683 
684                 for (Object event : events) {
685                     EventsProcessor.unregisterEvent(eventType, event);
686                 }
687             }
688         }
689 
690         private Map<String, List<Object>> _eventsMap =
691             new HashMap<String, List<Object>>();
692 
693     }
694 
695     private class ModelListenersContainer {
696 
697         public void addModelListener(
698             String modelName, ModelListener modelListener) {
699 
700             List<ModelListener> modelListeners = _modelListenersMap.get(
701                 modelName);
702 
703             if (modelListeners == null) {
704                 modelListeners = new ArrayList<ModelListener>();
705 
706                 _modelListenersMap.put(modelName, modelListeners);
707             }
708 
709             modelListeners.add(modelListener);
710         }
711 
712         public void unregisterModelListeners() {
713             for (Map.Entry<String, List<ModelListener>> entry :
714                     _modelListenersMap.entrySet()) {
715 
716                 String modelName = entry.getKey();
717                 List<ModelListener> modelListeners = entry.getValue();
718 
719                 BasePersistence persistence = getPersistence(modelName);
720 
721                 for (ModelListener modelListener : modelListeners) {
722                     persistence.unregisterListener(modelListener);
723                 }
724             }
725         }
726 
727         private Map<String, List<ModelListener>> _modelListenersMap =
728             new HashMap<String, List<ModelListener>>();
729 
730     }
731 
732     private class CustomJspBag {
733 
734         public CustomJspBag(String customJspDir, List<String> customJsps) {
735             _customJspDir = customJspDir;
736             _customJsps = customJsps;
737             _timestamp = Time.getTimestamp();
738         }
739 
740         public String getCustomJspDir() {
741             return _customJspDir;
742         }
743 
744         public List<String> getCustomJsps() {
745             return _customJsps;
746         }
747 
748         public String getTimestamp() {
749             return _timestamp;
750         }
751 
752         private String _customJspDir;
753         private List<String> _customJsps;
754         private String _timestamp;
755 
756     }
757 
758 }