View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.tools.pamanager;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.security.Permission;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.jetspeed.cluster.NodeManager;
30  import org.apache.jetspeed.components.portletentity.PortletEntityAccessComponent;
31  import org.apache.jetspeed.components.portletentity.PortletEntityNotDeletedException;
32  import org.apache.jetspeed.components.portletregistry.PortletRegistry;
33  import org.apache.jetspeed.components.portletregistry.RegistryException;
34  import org.apache.jetspeed.container.window.PortletWindowAccessor;
35  import org.apache.jetspeed.factory.PortletFactory;
36  import org.apache.jetspeed.om.common.portlet.MutablePortletApplication;
37  import org.apache.jetspeed.om.common.servlet.MutableWebApplication;
38  import org.apache.jetspeed.search.SearchEngine;
39  import org.apache.jetspeed.security.PermissionManager;
40  import org.apache.jetspeed.security.PortletPermission;
41  import org.apache.jetspeed.security.Role;
42  import org.apache.jetspeed.security.RoleManager;
43  import org.apache.jetspeed.security.SecurityException;
44  import org.apache.jetspeed.util.DirectoryHelper;
45  import org.apache.jetspeed.util.FileSystemHelper;
46  import org.apache.jetspeed.util.MultiFileChecksumHelper;
47  import org.apache.jetspeed.util.descriptor.PortletApplicationWar;
48  import org.apache.pluto.om.common.SecurityRole;
49  import org.apache.pluto.om.entity.PortletEntity;
50  import org.apache.pluto.om.entity.PortletEntityCtrl;
51  import org.apache.pluto.om.portlet.PortletDefinition;
52  
53  /***
54   * PortletApplicationManager
55   *
56   * @author <a href="mailto:ate@douma.nu">Ate Douma</a>
57   * @version $Id: PortletApplicationManager.java,v 1.21 2005/04/09 00:24:44 shinsuke Exp $
58   */
59  public class PortletApplicationManager implements PortletApplicationManagement
60  {
61      private static int DEFAULT_DESCRIPTOR_CHANGE_MONITOR_INTERVAL = 10*1000; // 10 seconds
62      private static final Log    log = LogFactory.getLog("deployment");
63  
64      protected PortletEntityAccessComponent entityAccess;
65      protected PortletFactory        portletFactory;
66      protected PortletRegistry       registry;
67      protected PortletWindowAccessor windowAccess;
68      protected SearchEngine          searchEngine;
69      protected RoleManager           roleManager;
70      protected PermissionManager     permissionManager;
71      protected boolean               autoCreateRoles;
72      protected List                  permissionRoles;
73      protected int  descriptorChangeMonitorInterval = DEFAULT_DESCRIPTOR_CHANGE_MONITOR_INTERVAL;
74      protected DescriptorChangeMonitor monitor;
75      protected boolean started;
76      protected String appRoot;
77      protected NodeManager nodeManager;
78      
79      /***
80  	 * Creates a new PortletApplicationManager object.
81  	 */
82  	public PortletApplicationManager(PortletFactory portletFactory, PortletRegistry registry,
83  		PortletEntityAccessComponent entityAccess, PortletWindowAccessor windowAccess,
84          PermissionManager permissionManager, SearchEngine searchEngine,
85          RoleManager roleManager, List permissionRoles, NodeManager nodeManager, String appRoot)
86  	{
87  		this.portletFactory     = portletFactory;
88  		this.registry		    = registry;
89  		this.entityAccess	    = entityAccess;
90  		this.windowAccess	    = windowAccess;
91          this.permissionManager  = permissionManager;
92          this.searchEngine       = searchEngine;
93          this.roleManager        = roleManager;        
94          this.permissionRoles    = permissionRoles;
95          this.nodeManager		= nodeManager;
96          this.appRoot            = appRoot;
97  	}
98      
99      public void start()
100     {
101         if ( descriptorChangeMonitorInterval > 0 )
102         {
103             try
104             {
105                 monitor = new DescriptorChangeMonitor(Thread.currentThread().getThreadGroup(),
106                                                 "PortletApplicationManager Descriptor Change Monitor Thread", this, descriptorChangeMonitorInterval);
107 
108                 monitor.setContextClassLoader(getClass().getClassLoader());
109                 monitor.start();
110                 log.info("PortletApplicationManager Descriptor Change Monitor started!");
111             }
112             catch (Exception e)
113             {
114                 log.warn("Unable to start PortletApplicationManager Descriptor Change Monitor: "+ e.toString(), e);
115                 monitor.safeStop();
116                 monitor = null;
117             }
118         }
119         started = true;
120     }
121     
122     public void stop()
123     {
124         started = false;
125         if (monitor != null)
126         {
127             monitor.safeStop();
128             monitor = null;
129         }
130     }
131     
132     public boolean isStarted()
133     {
134         return started;
135     }
136     
137     public void setRoleManager(RoleManager roleManager)
138     {
139         this.roleManager = roleManager;
140     }
141     
142     public void setAutoCreateRoles(boolean autoCreateRoles)
143     {
144         this.autoCreateRoles = autoCreateRoles;
145     }
146 
147 	public void setSearchEngine(SearchEngine searchEngine)
148 	{
149 		this.searchEngine = searchEngine;
150 	}
151     
152     private void checkStarted()
153     {
154         if (!started)
155         {
156             throw new IllegalStateException("Not started yet");
157         }
158     }
159 
160 	public void startLocalPortletApplication(String contextName, FileSystemHelper warStruct,
161 		ClassLoader paClassLoader)
162 		throws RegistryException
163 	{
164         checkStarted();
165         startPA(contextName, warStruct, paClassLoader, MutablePortletApplication.LOCAL);
166 	}
167 
168     public void startInternalApplication(String contextName) throws RegistryException
169     {
170         checkStarted();
171         File webinf = new File (appRoot);
172         ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();        
173         DirectoryHelper dir = new DirectoryHelper(webinf);
174         String appName = (contextName.startsWith("/")) ? contextName.substring(1) : contextName;
175         MutablePortletApplication app = registry.getPortletApplicationByIdentifier(appName);
176         if (app != null && app.getApplicationType() == MutablePortletApplication.LOCAL)
177         {
178             app.setApplicationType(MutablePortletApplication.INTERNAL);
179             registry.updatePortletApplication(app);
180         }
181         startPA(contextName, dir, contextClassLoader, MutablePortletApplication.INTERNAL);
182         // startInternal(contextName, warStruct, paClassLoader, true);        
183     }
184     
185 	public void startPortletApplication(String contextName, FileSystemHelper warStruct,
186 		ClassLoader paClassLoader)
187 		throws RegistryException
188 	{
189         checkStarted();
190         ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
191         Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
192         try
193         {
194             startPA(contextName, warStruct, paClassLoader, MutablePortletApplication.WEBAPP);
195         }
196         finally
197         {
198             Thread.currentThread().setContextClassLoader(contextClassLoader);
199         }
200 	}
201 
202 	public void stopLocalPortletApplication(String contextName)
203 		throws RegistryException
204 	{
205 		stopPA(contextName, MutablePortletApplication.LOCAL);
206 	}
207 
208 	public void stopPortletApplication(String contextName)
209 		throws RegistryException
210 	{
211         ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
212         Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
213         try
214         {
215             stopPA(contextName, MutablePortletApplication.WEBAPP);
216         }
217         finally
218         {
219             Thread.currentThread().setContextClassLoader(contextClassLoader);
220         }
221 	}
222 
223 	public void unregisterPortletApplication(String paName)
224 		throws RegistryException
225 	{
226         ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
227         Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
228         try
229         {
230             MutablePortletApplication pa = null;
231             
232             try
233             {
234                 pa = registry.getPortletApplication(paName);
235             }
236             catch (Exception e)
237             {
238                 // ignore errors during portal shutdown
239             }
240 
241             
242             if (pa != null)
243             {
244                 if (portletFactory.isPortletApplicationRegistered(pa))
245                 {
246                     throw new RegistryException("Portlet Application " + paName + " still running");
247                 }
248 
249                 unregisterPortletApplication(pa, true);
250                 try
251                 {
252                 	nodeManager.removeNode(paName);
253                 }
254                 catch (Exception ee)
255                 {
256                 	// we actually do not care about an exception in the remove operation...
257                 }
258             }
259         }
260         finally
261         {
262             Thread.currentThread().setContextClassLoader(contextClassLoader);
263         }
264 	}
265     
266 	protected void checkValidContextName(String contextName, boolean local)
267 		throws RegistryException
268 	{
269 		int prefixLength = LOCAL_PA_PREFIX.length();
270 
271 		if ((contextName.length() >= prefixLength)
272 			&& contextName.substring(0, prefixLength).equalsIgnoreCase(LOCAL_PA_PREFIX))
273 		{
274 			if (!local)
275 			{
276 				throw new RegistryException("Prefix \"" + LOCAL_PA_PREFIX
277 					+ "\" is reserved for Local Portlet Applications only.");
278 			}
279 		}
280 		else if (local)
281 		{
282 			throw new RegistryException("Prefix \"" + LOCAL_PA_PREFIX
283 				+ "\" is required for Local Portlet Applications.");
284 		}
285 	}
286 
287 	protected MutablePortletApplication registerPortletApplication(PortletApplicationWar paWar,
288 		MutablePortletApplication oldPA, int paType, ClassLoader paClassLoader)
289 		throws RegistryException
290 	{
291 		if (oldPA != null)
292 		{
293 			unregisterPortletApplication(oldPA, false);
294 			oldPA = null;
295 		}
296 
297 		MutablePortletApplication pa		 = null;
298 		boolean					  registered = false;
299 		String					  paName     = paWar.getPortletApplicationName();
300 
301 		try
302 		{
303 			log.info("Loading portlet.xml...." + paName);
304 			pa = paWar.createPortletApp(paClassLoader);
305 			pa.setApplicationType(paType);
306 
307 			// load the web.xml
308 			log.info("Loading web.xml...." + paName);
309 			MutableWebApplication wa = paWar.createWebApp();
310 			paWar.validate();
311 
312 			if (paType == MutablePortletApplication.LOCAL)
313 			{
314 				wa.setContextRoot("<portal>");
315 			}
316             else if (paType == MutablePortletApplication.INTERNAL)
317             {
318                 // TODO: this is screwing up the PSML as its set all over the place to "jetspeed-layouts", not good
319                 wa.setContextRoot("/" + paName);                
320             }
321 
322 			pa.setWebApplicationDefinition(wa);
323             
324             // Make sure existing entities are refreshed with the most
325             // recent PortletDefintion.
326             Collection portletDefs = pa.getPortletDefinitions();
327             if(portletDefs != null && portletDefs.size() > 0)
328             {
329                 Iterator pdItr = portletDefs.iterator();
330                 while(pdItr.hasNext())
331                 {
332                     PortletDefinition pd = (PortletDefinition) pdItr.next();
333                     Collection portletEntites = entityAccess.getPortletEntities(pd);
334                     if(portletEntites != null && portletEntites.size() > 0)
335                     {
336                         Iterator peItr = portletEntites.iterator();
337                         while(peItr.hasNext())
338                         {
339                             PortletEntityCtrl portletEntity = (PortletEntityCtrl) peItr.next();
340                             portletEntity.setPortletDefinition(pd);
341                         }
342                     }
343                 }
344             }
345 		}
346 		catch (Exception e)
347 		{
348 			String msg = "Failed to load portlet application for "
349 				+ paWar.getPortletApplicationName();
350 			log.error(msg, e);
351 			throw new RegistryException(msg);
352 		}
353 
354 		// register the portlet application
355 		try
356 		{
357 			registry.registerPortletApplication(pa);
358 			registered = true;
359 			log.info("Registered the portlet application " + paName);
360 
361 			// add to search engine result
362 			this.updateSearchEngine(false, pa);
363 			
364 			// and add to the current node info
365 			nodeManager.addNode(new Long(pa.getId().toString()), pa.getName());
366             
367             // grant default permissions to portlet application
368 			grantDefaultPermissions(paName);
369             
370             if ( autoCreateRoles && roleManager != null && pa.getWebApplicationDefinition().getSecurityRoles() != null )
371             {
372                 try
373                 {
374                     Iterator rolesIter = pa.getWebApplicationDefinition().getSecurityRoles().iterator();
375                     SecurityRole sr;
376                     while ( rolesIter.hasNext() )
377                     {
378                         sr = (SecurityRole)rolesIter.next();
379                         if ( !roleManager.roleExists(sr.getRoleName()) )
380                         {
381                             roleManager.addRole(sr.getRoleName());
382                             log.info("AutoCreated role: "+sr.getRoleName()+" from portlet application "+paName+" its web definition");
383                         }
384                     }
385                 }
386                 catch (SecurityException sex)
387                 {
388                     log.warn("Failed to autoCreate roles for portlet application " + paName+": "+sex.getMessage(), sex);
389                 }
390             }
391 
392 			return pa;
393 		}
394 		catch (Exception e)
395 		{
396 			String msg = "Failed to register portlet application, " + paName;
397 			log.error(msg, e);
398 
399 			if (registered)
400 			{
401 				try
402 				{
403 					unregisterPortletApplication(pa, (paType == MutablePortletApplication.LOCAL));
404 				}
405 				catch (Exception re)
406 				{
407 					log.error("Failed to rollback registration of portlet application" + paName, re);
408 				}
409 			}
410 
411 			throw new RegistryException(msg, e);
412 		}
413 	}
414 
415 	protected void startPA(String contextName, FileSystemHelper warStruct,
416 	        ClassLoader paClassLoader, int paType)
417 	throws RegistryException
418 	{
419 	    startPA(contextName, warStruct, paClassLoader, paType, 0);
420 	}
421 	
422 	protected void startPA(String contextName, FileSystemHelper warStruct,
423 	        ClassLoader paClassLoader, int paType, long checksum)
424 	throws RegistryException
425 	{
426         PortletApplicationWar paWar = null;
427 		try
428 		{
429             boolean register = true;
430             boolean monitored = checksum != 0;
431             paWar = new PortletApplicationWar(warStruct, contextName, "/" + contextName, checksum);
432             try
433             {
434                 if (paClassLoader == null)
435                 {
436                     paClassLoader = paWar.createClassloader(getClass().getClassLoader());
437                 }                
438                 checksum = paWar.getPortletApplicationChecksum();                
439             }
440             catch (IOException e)
441             {
442                 String msg = "Invalid PA WAR for " + contextName;
443                 log.error(msg, e);
444                 if ( paClassLoader == null )
445                 {
446                     // nothing to be done about it anymore: this pa is beyond repair :(
447                     throw new RegistryException(e);
448                 }
449                 register = false;
450             }
451 
452 			MutablePortletApplication pa = registry.getPortletApplication(contextName);
453 
454             if (pa != null)
455             {
456                 if ( pa.getApplicationType() != paType )
457                 {
458                     throw new RegistryException("Cannot start portlet application "+contextName+": as Application Types don't match: " + pa.getApplicationType() + " != " + paType);
459                 }
460                 DescriptorChangeMonitor changeMonitor = this.monitor;
461                 if (!monitored && changeMonitor != null)
462                 {
463                     changeMonitor.remove(contextName);
464                 }
465                 portletFactory.unregisterPortletApplication(pa);                        
466             }
467 //            if (register && (pa == null || checksum != pa.getChecksum()))
468             if (register)
469             {
470             	if (pa == null)
471             	{ 
472             		// new
473 	                try
474 	                {
475 	                    pa = registerPortletApplication(paWar, pa, paType, paClassLoader);
476 	                }
477 	                catch (Exception e)
478 	                {
479 	                    // don't register the pa
480 	                    register = false;
481 	                }
482             	}
483             	else
484             	{
485             		int status = nodeManager.checkNode(new Long(pa.getId().toString()), pa.getName());
486         			boolean reregister = false;
487         			boolean deploy = false;
488         			switch (status)
489         			{
490         				case  NodeManager.NODE_NEW:
491         				{
492             				//only reason is that the file got somehow corrupted 
493             				// so we really do not know what is going on here...
494             				// the best chance at this point is to reregister (which might be the absolute wrong choice)
495             				log.warn("The portlet application " + pa.getName() + " is registered in the database but not locally .... we will reregister");
496             				reregister = true;
497         					if (checksum != pa.getChecksum())
498         					{
499                 				log.warn("The provided portlet application " + pa.getName() + " is a different version than in the database (db-checksum=" + pa.getChecksum() + ", local-checksum=: " + checksum + ") .... we will redeploy (also to the database)");
500     							deploy = true;
501         					}
502         					break;
503         				}
504         				case  NodeManager.NODE_SAVED:
505         				{
506         					if (checksum != pa.getChecksum())
507                     		{	
508                 				log.warn("The provided portlet application " + pa.getName() + " is a different version than in the local node info and the database (db-checksum=" + pa.getChecksum() + ", local-checksum=: " + checksum + ") .... we will reregister AND redeploy (also to the database)");
509         						//database and local node info are in synch, so we assume that this is a brand new
510         						// war .... let's deploy
511         						reregister = true;
512         						deploy = true;
513                     		}
514         					break;
515         				}
516         				case  NodeManager.NODE_OUTDATED:
517         				{
518             				//database version is older (determined by id) than the database 
519         					//let's deploy and reregister
520         					if (checksum != pa.getChecksum())
521                 				log.error("The portlet application " + pa.getName() + " provided for the upgrade IS WRONG. The database checksum= " + pa.getChecksum() + ", but the local=" + checksum + "....THIS NEEDS TO BE CORRECTED");
522        						reregister = true;
523         					break;
524         				}
525         			}
526         			if (deploy)
527 	                    pa = registerPortletApplication(paWar, pa, paType, paClassLoader);
528         			else
529         				if (reregister)
530         				{
531         					// add to search engine result
532         					this.updateSearchEngine(true, pa);
533         					this.updateSearchEngine(false, pa);
534         					
535         					// and add to the current node info
536         					try
537         					{
538         						nodeManager.addNode(new Long(pa.getId().toString()), pa.getName());
539         					} catch (Exception e)
540         					{
541         						log.error("Adding node for portlet application " + pa.getName() + " caused exception" , e);
542         					}
543         				}
544         				
545             	
546             	}
547             }
548             if (register)
549             {
550                 portletFactory.registerPortletApplication(pa, paClassLoader);
551             }
552             
553             DescriptorChangeMonitor changeMonitor = this.monitor;
554             if (!monitored && changeMonitor != null)
555             {
556                 changeMonitor.monitor(contextName,paClassLoader, paType, warStruct.getRootDirectory(), checksum);
557             }
558 		}
559 		finally
560 		{
561 			if (paWar != null)
562 			{
563 				try
564 				{
565 					paWar.close();
566 				}
567 				catch (IOException e)
568 				{
569 					log.error("Failed to close PA WAR for " + contextName, e);
570 				}
571 			}
572 		}
573 	}
574 
575 	protected void stopPA(String contextName, int paType)
576 		throws RegistryException
577 	{
578 		MutablePortletApplication pa = null;
579         
580         try
581         {
582             pa = registry.getPortletApplication(contextName);
583         }
584         catch (Exception e)
585         {
586             // ignore errors during portal shutdown
587         }
588         if  (pa != null && pa.getApplicationType() != paType) 
589         {
590             throw new RegistryException("Cannot stop portlet application "+contextName+": as Application Types don't match: " + pa.getApplicationType() + " != " + paType);
591         }
592         DescriptorChangeMonitor monitor = this.monitor;
593         if ( monitor != null )
594         {
595             monitor.remove(contextName);
596         }
597 		if (pa != null)
598 		{
599             portletFactory.unregisterPortletApplication(pa);
600 		}
601 	}
602 
603 	
604 	protected void updateSearchEngine(boolean remove,MutablePortletApplication pa )
605 	{
606 		if (searchEngine != null)
607 		{
608 			if (remove)
609 			{
610 				searchEngine.remove(pa);
611 				searchEngine.remove(pa.getPortletDefinitions());
612 					log.info("Un-Registered the portlet application in the search engine... " + pa.getName());
613 			}
614 			else
615 			{
616 					searchEngine.add(pa);
617 					searchEngine.add(pa.getPortletDefinitions());
618 					log.info("Registered the portlet application in the search engine... " + pa.getName());
619 			}
620 		}
621 		
622 	}
623 	protected void unregisterPortletApplication(MutablePortletApplication pa,
624 		boolean purgeEntityInfo)
625 		throws RegistryException
626 	{
627 
628 		updateSearchEngine(true,pa);
629 		log.info("Remove all registry entries defined for portlet application " + pa.getName());
630 
631 		Iterator portlets = pa.getPortletDefinitions().iterator();
632 
633 		while (portlets.hasNext())
634 		{
635 			PortletDefinition portletDefinition = (PortletDefinition) portlets.next();
636 			Iterator		  entities = entityAccess.getPortletEntities(portletDefinition)
637 													 .iterator();
638 
639 			while (entities.hasNext())
640 			{
641 				PortletEntity entity = (PortletEntity) entities.next();
642 
643 				if (purgeEntityInfo)
644 				{
645 					try
646 					{
647 						entityAccess.removePortletEntity(entity);
648 					}
649 					catch (PortletEntityNotDeletedException e)
650 					{
651 						String msg = "Failed to delete Portlet Entity " + entity.getId();
652 						log.error(msg, e);
653 						throw new RegistryException(msg, e);
654 					}
655 				}
656 
657 				entityAccess.removeFromCache(entity);
658 				windowAccess.removeWindows(entity);
659 			}
660 		}
661 
662 		// todo keep (User)Prefs?
663 		registry.removeApplication(pa);
664         revokeDefaultPermissions(pa.getName());
665 	}
666     
667     protected void grantDefaultPermissions(String paName)
668     {
669         try
670         {
671             // create a default permission for this portlet app, granting configured roles to the portlet application 
672             Iterator roles = permissionRoles.iterator();
673             while (roles.hasNext())
674             {
675                 String roleName = (String)roles.next();
676                 Role userRole = roleManager.getRole(roleName);
677                 if (userRole != null)
678                 {
679                     Permission permission = new PortletPermission(paName + "::*", "view, edit");
680                     if (!permissionManager.permissionExists(permission))
681                     {
682                         permissionManager.addPermission(permission);
683                         permissionManager.grantPermission(userRole.getPrincipal(), permission);
684                     }                    
685                 }
686             }
687         }
688         catch (SecurityException e)
689         {
690             log.error("Error granting default permissions for " + paName, e);
691         }        
692     }
693     
694     protected void revokeDefaultPermissions(String paName)
695     {
696         try
697         {
698             Iterator roles = permissionRoles.iterator();
699             while (roles.hasNext())
700             {
701                 String roleName = (String)roles.next();
702                 Role userRole = roleManager.getRole(roleName);
703                 if (userRole != null)
704                 {
705                     Permission permission = new PortletPermission(paName + "::*", "view, edit");
706                     if (permissionManager.permissionExists(permission))
707                     {
708                         permissionManager.removePermission(permission);
709                     }                    
710                     
711                 }
712             }
713         }
714         catch (SecurityException e)
715         {
716             log.error("Error revoking default permissions for " + paName, e);
717         }
718     }
719 
720     public int getDescriptorChangeMonitorInterval()
721     {
722         return descriptorChangeMonitorInterval/1000;
723     }
724 
725     public void setDescriptorChangeMonitorInterval(int descriptorChangeMonitorInterval)
726     {
727         this.descriptorChangeMonitorInterval = descriptorChangeMonitorInterval*1000;
728     }    
729     
730     private static class DescriptorChangeMonitor extends Thread
731     {
732         private static class DescriptorChangeMonitorInfo
733         {
734             private String contextName;
735             private ClassLoader paClassLoader;
736             private int  paType;
737             private File paDir;
738             private File[] descriptors;
739             private long descriptorModificationTime;
740             private long extendedDescriptorModificationTime;
741             private long checksum;
742             private boolean obsolete;
743                         
744             /*
745              * Constructor only used for looking up the matching registered one in monitorsInfo
746              */
747             public DescriptorChangeMonitorInfo(String contextName)
748             {
749                 this.contextName = contextName;
750             }
751             
752             public DescriptorChangeMonitorInfo(String contextName, ClassLoader paClassLoader, int paType, File paDir, long checksum)
753             {
754                 this.contextName = contextName;
755                 this.paClassLoader = paClassLoader;
756                 this.paType = paType;
757                 this.paDir = paDir.isAbsolute() ? paDir : paDir.getAbsoluteFile();
758                 this.checksum = checksum;
759                 
760                 this.descriptors = new File[] { 
761                         new File(paDir, PortletApplicationWar.WEB_XML_PATH),
762                         new File(paDir, PortletApplicationWar.PORTLET_XML_PATH),
763                         new File(paDir, PortletApplicationWar.EXTENDED_PORTLET_XML_PATH) };
764 
765                 descriptorModificationTime = descriptors[1].lastModified();
766                 extendedDescriptorModificationTime = descriptors[2].lastModified();
767             }
768             
769             public String getContextName()
770             {
771                 return contextName;
772             }
773             
774             public ClassLoader getPAClassLoader()
775             {
776                 return paClassLoader;
777             }
778             
779             public int getPortletApplicationType()
780             {
781                 return paType;
782             }
783             
784             public File getPADir()
785             {
786                 return paDir;
787             }
788 
789             public long getChecksum()
790             {
791                 return checksum;
792             }
793             
794             public boolean isChanged()
795             {
796                 if ( !obsolete)
797                 {
798                     long newDescriptorModificationTime = descriptors[1].lastModified();
799                     long newExtendedDescriptorModificationTime = descriptors[2].lastModified();
800                     if ( descriptorModificationTime != newDescriptorModificationTime ||
801                             extendedDescriptorModificationTime != newExtendedDescriptorModificationTime )
802                     {
803                         descriptorModificationTime = newDescriptorModificationTime;
804                         extendedDescriptorModificationTime = newExtendedDescriptorModificationTime;
805                         long newChecksum = MultiFileChecksumHelper.getChecksum(descriptors);
806                         if ( checksum != newChecksum )
807                         {
808                             checksum = newChecksum;
809                             return true;
810                         }
811                     }
812                 }
813                 return false;
814             }
815             
816             public void setObsolete()
817             {
818                 obsolete = true;
819             }
820             
821             public boolean isObsolete()
822             {
823                 return obsolete;
824             }
825         }        
826 
827         private PortletApplicationManager pam;
828         private long interval;
829         private boolean started = true;
830         private ArrayList monitorInfos;
831 
832         public DescriptorChangeMonitor(ThreadGroup group, String name, PortletApplicationManager pam, long interval)
833         {
834             super(group, name);
835             this.pam = pam;
836             this.interval = interval;
837             monitorInfos = new ArrayList();
838             setPriority(MIN_PRIORITY);
839             setDaemon(true);
840         }
841         
842         public void run()
843         {
844             try
845             {
846                 sleep(interval);
847             }
848             catch (InterruptedException e)
849             {
850             }
851             while (started)
852             {
853                 checkDescriptorChanges();
854 
855                 try
856                 {
857                     sleep(interval);
858                 }
859                 catch (InterruptedException e)
860                 {
861 
862                 }
863             }
864         }
865 
866         /***
867          * notifies a switch variable that exits the watcher's montior loop started in the <code>run()</code> method.
868          */
869         public synchronized void safeStop()
870         {
871             started = false;
872             monitorInfos.clear();
873         }
874         
875         public synchronized void monitor(String contextName, ClassLoader paClassLoader, int paType, File paDir, long checksum)
876         {
877             monitorInfos.add(new DescriptorChangeMonitorInfo(contextName, paClassLoader, paType, paDir, checksum));
878         }
879         
880         public synchronized void remove(String contextName)
881         {
882             DescriptorChangeMonitorInfo monitorInfo;
883             for ( int i = monitorInfos.size()-1; i > -1; i-- )
884             {
885                 monitorInfo = (DescriptorChangeMonitorInfo)monitorInfos.get(i);
886                 if (contextName.equals(monitorInfo.getContextName()))
887                 {
888                     // will be removed by checkDescriptorChanges on next iteration
889                     monitorInfo.setObsolete();
890                     break;
891                 }
892             }
893         }
894         
895         private void checkDescriptorChanges()
896         {
897             int size;
898             synchronized (this)
899             {
900                 size = monitorInfos.size();
901             }
902             for (int i = size-1; i > -1; i--)
903             {
904                 DescriptorChangeMonitorInfo monitorInfo;
905                 synchronized (this)
906                 {
907                     if ( started )
908                     {
909                         monitorInfo = (DescriptorChangeMonitorInfo)monitorInfos.get(i);
910                         if (monitorInfo.isObsolete())
911                         {
912                             monitorInfos.remove(i);
913                         }
914                         else
915                         {
916                             try
917                             {
918                                 if (monitorInfo.isChanged())
919                                 {
920                                     try
921                                     {
922                                         pam.startPA(monitorInfo.getContextName(), new DirectoryHelper(monitorInfo.getPADir()),
923                                                 monitorInfo.getPAClassLoader(), monitorInfo.getPortletApplicationType(), monitorInfo.getChecksum());
924                                     }
925                                     catch (Exception e)
926                                     {
927                                         log.error("Failed to restart PortletApplication "+monitorInfo.getContextName(),e);
928                                     }
929                                 }
930                             }
931                             catch (Exception e)
932                             {
933                                 // ignore filesystem and/or descriptor errors, maybe next time round they'll be fixed again
934                                 log.error("Descriptor Change check failure for PortletApplication "+monitorInfo.getContextName(),e);
935                             }
936                         }
937                     }
938                 }
939             }
940         }        
941     }    
942 }