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 int DEFAULT_MAX_RETRIED_STARTS = 10; // 10 times retry PA
63      private static final Log    log = LogFactory.getLog("deployment");
64  
65      protected PortletEntityAccessComponent entityAccess;
66      protected PortletFactory        portletFactory;
67      protected PortletRegistry       registry;
68      protected PortletWindowAccessor windowAccess;
69      protected SearchEngine          searchEngine;
70      protected RoleManager           roleManager;
71      protected PermissionManager     permissionManager;
72      protected boolean               autoCreateRoles;
73      protected List                  permissionRoles;
74      protected int  descriptorChangeMonitorInterval = DEFAULT_DESCRIPTOR_CHANGE_MONITOR_INTERVAL;
75      /***
76       * holds the max number of retries in case of unsuccessful PA start
77       * this addresses possible startup errors in clustered environments
78       */
79      protected int  maxRetriedStarts = DEFAULT_MAX_RETRIED_STARTS;
80      protected DescriptorChangeMonitor monitor;
81      protected boolean started;
82      protected String appRoot;
83      protected NodeManager nodeManager;
84      
85      /***
86  	 * Creates a new PortletApplicationManager object.
87  	 */
88  	public PortletApplicationManager(PortletFactory portletFactory, PortletRegistry registry,
89  		PortletEntityAccessComponent entityAccess, PortletWindowAccessor windowAccess,
90          PermissionManager permissionManager, SearchEngine searchEngine,
91          RoleManager roleManager, List permissionRoles, NodeManager nodeManager, String appRoot)
92  	{
93  		this.portletFactory     = portletFactory;
94  		this.registry		    = registry;
95  		this.entityAccess	    = entityAccess;
96  		this.windowAccess	    = windowAccess;
97          this.permissionManager  = permissionManager;
98          this.searchEngine       = searchEngine;
99          this.roleManager        = roleManager;        
100         this.permissionRoles    = permissionRoles;
101         this.nodeManager		= nodeManager;
102         this.appRoot            = appRoot;
103 	}
104     
105     public void start()
106     {
107         if ( descriptorChangeMonitorInterval > 0 )
108         {
109             try
110             {
111                 monitor = new DescriptorChangeMonitor(Thread.currentThread().getThreadGroup(),
112                                                 "PortletApplicationManager Descriptor Change Monitor Thread", this, descriptorChangeMonitorInterval, maxRetriedStarts);
113 
114                 monitor.setContextClassLoader(getClass().getClassLoader());
115                 monitor.start();
116                 log.info("PortletApplicationManager Descriptor Change Monitor started!");
117             }
118             catch (Exception e)
119             {
120                 log.warn("Unable to start PortletApplicationManager Descriptor Change Monitor: "+ e.toString(), e);
121                 monitor.safeStop();
122                 monitor = null;
123             }
124         }
125         started = true;
126     }
127     
128     public void stop()
129     {
130         started = false;
131         if (monitor != null)
132         {
133             monitor.safeStop();
134             monitor = null;
135         }
136     }
137     
138     public boolean isStarted()
139     {
140         return started;
141     }
142     
143     public void setRoleManager(RoleManager roleManager)
144     {
145         this.roleManager = roleManager;
146     }
147     
148     public void setAutoCreateRoles(boolean autoCreateRoles)
149     {
150         this.autoCreateRoles = autoCreateRoles;
151     }
152 
153 	public void setSearchEngine(SearchEngine searchEngine)
154 	{
155 		this.searchEngine = searchEngine;
156 	}
157     
158     private void checkStarted()
159     {
160         if (!started)
161         {
162             throw new IllegalStateException("Not started yet");
163         }
164     }
165 
166 	public void startLocalPortletApplication(String contextName, FileSystemHelper warStruct,
167 		ClassLoader paClassLoader)
168 		throws RegistryException
169 	{
170         checkStarted();
171         startPA(contextName, "/"+contextName, warStruct, paClassLoader, MutablePortletApplication.LOCAL);
172 	}
173 
174     public void startInternalApplication(String contextName) throws RegistryException
175     {
176         checkStarted();
177         File webinf = new File (appRoot);
178         ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();        
179         DirectoryHelper dir = new DirectoryHelper(webinf);
180         String appName = (contextName.startsWith("/")) ? contextName.substring(1) : contextName;
181         MutablePortletApplication app = registry.getPortletApplicationByIdentifier(appName);
182         if (app != null && app.getApplicationType() == MutablePortletApplication.LOCAL)
183         {
184             app.setApplicationType(MutablePortletApplication.INTERNAL);
185             registry.updatePortletApplication(app);
186         }
187         startPA(contextName, "/"+contextName, dir, contextClassLoader, MutablePortletApplication.INTERNAL);
188         // startInternal(contextName, warStruct, paClassLoader, true);        
189     }
190     
191 	public void startPortletApplication(String contextName, FileSystemHelper warStruct,
192 		ClassLoader paClassLoader)
193 		throws RegistryException
194 	{
195          startPortletApplication(contextName, "/"+contextName, warStruct, paClassLoader);
196 	}
197 	
198     public void startPortletApplication(String contextName, String contextPath, FileSystemHelper warStruct,
199             ClassLoader paClassLoader) throws RegistryException
200     {
201         checkStarted();
202         ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
203         Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
204         try
205         {
206             startPA(contextName, contextPath, warStruct, paClassLoader, MutablePortletApplication.WEBAPP);
207         }
208         finally
209         {
210             Thread.currentThread().setContextClassLoader(contextClassLoader);
211         }
212         
213     }    
214 
215 	public void stopLocalPortletApplication(String contextName)
216 		throws RegistryException
217 	{
218 		stopPA(contextName, MutablePortletApplication.LOCAL);
219 	}
220 
221 	public void stopPortletApplication(String contextName)
222 		throws RegistryException
223 	{
224         ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
225         Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
226         try
227         {
228             stopPA(contextName, MutablePortletApplication.WEBAPP);
229         }
230         finally
231         {
232             Thread.currentThread().setContextClassLoader(contextClassLoader);
233         }
234 	}
235 
236 	public void unregisterPortletApplication(String paName)
237 		throws RegistryException
238 	{
239         ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
240         Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
241         try
242         {
243             MutablePortletApplication pa = null;
244             
245             try
246             {
247                 pa = registry.getPortletApplication(paName);
248             }
249             catch (Exception e)
250             {
251                 // ignore errors during portal shutdown
252             }
253 
254             
255             if (pa != null)
256             {
257                 if (portletFactory.isPortletApplicationRegistered(pa))
258                 {
259                     throw new RegistryException("Portlet Application " + paName + " still running");
260                 }
261 
262                 unregisterPortletApplication(pa, true);
263                 try
264                 {
265                 	nodeManager.removeNode(paName);
266                 }
267                 catch (Exception ee)
268                 {
269                 	// we actually do not care about an exception in the remove operation...
270                 }
271             }
272         }
273         finally
274         {
275             Thread.currentThread().setContextClassLoader(contextClassLoader);
276         }
277 	}
278     
279 	protected void checkValidContextName(String contextName, boolean local)
280 		throws RegistryException
281 	{
282 		int prefixLength = LOCAL_PA_PREFIX.length();
283 
284 		if ((contextName.length() >= prefixLength)
285 			&& contextName.substring(0, prefixLength).equalsIgnoreCase(LOCAL_PA_PREFIX))
286 		{
287 			if (!local)
288 			{
289 				throw new RegistryException("Prefix \"" + LOCAL_PA_PREFIX
290 					+ "\" is reserved for Local Portlet Applications only.");
291 			}
292 		}
293 		else if (local)
294 		{
295 			throw new RegistryException("Prefix \"" + LOCAL_PA_PREFIX
296 				+ "\" is required for Local Portlet Applications.");
297 		}
298 	}
299 
300 	protected MutablePortletApplication registerPortletApplication(PortletApplicationWar paWar,
301 		MutablePortletApplication oldPA, int paType, ClassLoader paClassLoader)
302 		throws RegistryException
303 	{
304 		if (oldPA != null)
305 		{
306 			unregisterPortletApplication(oldPA, false);
307 			oldPA = null;
308 		}
309 
310 		MutablePortletApplication pa		 = null;
311 		boolean					  registered = false;
312 		String					  paName     = paWar.getPortletApplicationName();
313 
314 		try
315 		{
316 			log.info("Loading portlet.xml...." + paName);
317 			pa = paWar.createPortletApp(paClassLoader);
318 			pa.setApplicationType(paType);
319 
320 			// load the web.xml
321 			log.info("Loading web.xml...." + paName);
322 			MutableWebApplication wa = paWar.createWebApp();
323 			paWar.validate();
324 
325 			if (paType == MutablePortletApplication.LOCAL)
326 			{
327 				wa.setContextRoot("<portal>");
328 			}
329             else if (paType == MutablePortletApplication.INTERNAL)
330             {
331                 // TODO: this is screwing up the PSML as its set all over the place to "jetspeed-layouts", not good
332                 wa.setContextRoot("/" + paName);                
333             }
334 
335 			pa.setWebApplicationDefinition(wa);
336             
337             // Make sure existing entities are refreshed with the most
338             // recent PortletDefintion.
339             Collection portletDefs = pa.getPortletDefinitions();
340             if(portletDefs != null && portletDefs.size() > 0)
341             {
342                 Iterator pdItr = portletDefs.iterator();
343                 while(pdItr.hasNext())
344                 {
345                     PortletDefinition pd = (PortletDefinition) pdItr.next();
346                     Collection portletEntites = entityAccess.getPortletEntities(pd);
347                     if(portletEntites != null && portletEntites.size() > 0)
348                     {
349                         Iterator peItr = portletEntites.iterator();
350                         while(peItr.hasNext())
351                         {
352                             PortletEntityCtrl portletEntity = (PortletEntityCtrl) peItr.next();
353                             portletEntity.setPortletDefinition(pd);
354                         }
355                     }
356                 }
357             }
358 		}
359 		catch (Exception e)
360 		{
361 			String msg = "Failed to load portlet application for "
362 				+ paWar.getPortletApplicationName();
363 			log.error(msg, e);
364 			throw new RegistryException(msg);
365 		}
366 
367 		// register the portlet application
368 		try
369 		{
370 			registry.registerPortletApplication(pa);
371 			registered = true;
372 			log.info("Registered the portlet application " + paName);
373 
374 			// add to search engine result
375 			this.updateSearchEngine(false, pa);
376 			
377 			// and add to the current node info
378 			nodeManager.addNode(new Long(pa.getId().toString()), pa.getName());
379             
380             // grant default permissions to portlet application
381 			grantDefaultPermissions(paName);
382             
383             if ( autoCreateRoles && roleManager != null && pa.getWebApplicationDefinition().getSecurityRoles() != null )
384             {
385                 try
386                 {
387                     Iterator rolesIter = pa.getWebApplicationDefinition().getSecurityRoles().iterator();
388                     SecurityRole sr;
389                     while ( rolesIter.hasNext() )
390                     {
391                         sr = (SecurityRole)rolesIter.next();
392                         if ( !roleManager.roleExists(sr.getRoleName()) )
393                         {
394                             roleManager.addRole(sr.getRoleName());
395                             log.info("AutoCreated role: "+sr.getRoleName()+" from portlet application "+paName+" its web definition");
396                         }
397                     }
398                 }
399                 catch (SecurityException sex)
400                 {
401                     log.warn("Failed to autoCreate roles for portlet application " + paName+": "+sex.getMessage(), sex);
402                 }
403             }
404 
405 			return pa;
406 		}
407 		catch (Exception e)
408 		{
409 			String msg = "Failed to register portlet application, " + paName;
410 			log.error(msg, e);
411 
412 			if (registered)
413 			{
414 				try
415 				{
416 					unregisterPortletApplication(pa, (paType == MutablePortletApplication.LOCAL));
417 				}
418 				catch (Exception re)
419 				{
420 					log.error("Failed to rollback registration of portlet application " + paName, re);
421 				}
422 			}
423 
424 			throw new RegistryException(msg, e);
425 		}
426 	}
427 
428 	protected void startPA(String contextName, String contextPath, FileSystemHelper warStruct,
429 	        ClassLoader paClassLoader, int paType)
430 	throws RegistryException
431 	{
432 	    startPA(contextName, contextPath, warStruct, paClassLoader, paType, 0);
433 	}
434 	
435 	protected void startPA(String contextName, String contextPath, FileSystemHelper warStruct,
436 	        ClassLoader paClassLoader, int paType, long checksum)
437 	throws RegistryException
438 	{
439         boolean register = true;
440         boolean monitored = false;
441         DescriptorChangeMonitor changeMonitor = this.monitor;
442         if (changeMonitor != null)
443         {
444             monitored = changeMonitor.isMonitored(contextName);
445         }
446         if (log.isDebugEnabled())
447         {
448             log.debug("Is portlet application " + contextName + " monitored? -> " + monitored);
449         }
450         PortletApplicationWar paWar = null;
451 		try
452 		{
453             if (log.isDebugEnabled())
454             {
455                 log.debug("Try to start portlet application " + contextName + ".");
456             }
457             // create PA  from war (file) structure
458             // paWar = new PortletApplicationWar(warStruct, contextName, "/" + contextName, checksum);
459             paWar = new PortletApplicationWar(warStruct, contextName, contextPath, checksum);
460             try
461             {
462                 if (paClassLoader == null)
463                 {
464                     paClassLoader = paWar.createClassloader(getClass().getClassLoader());
465                 }                
466                 // create checksum from PA descriptors
467                 checksum = paWar.getPortletApplicationChecksum();                
468                 
469                 if (log.isDebugEnabled())
470                 {
471                     log.debug("New checksum for portlet application " + contextName + " is " + checksum);
472                 }
473             }
474             catch (IOException e)
475             {
476                 String msg = "Invalid PA WAR for " + contextName;
477                 log.error(msg, e);
478                 if ( paClassLoader == null )
479                 {
480                     // nothing to be done about it anymore: this pa is beyond repair :(
481                     throw new RegistryException(e);
482                 }
483                 register = false;
484             }
485 
486 			// try to get the PA from database by context name
487 			MutablePortletApplication pa = registry.getPortletApplication(contextName);
488 
489             if (pa != null)
490             {
491                 if (log.isDebugEnabled())
492                 {
493                     log.debug("Portlet Application " + contextName + " found in registry.");
494             	}
495                 if ( pa.getApplicationType() != paType )
496                 {
497                     throw new RegistryException("Cannot start portlet application "+contextName+": as Application Types don't match: " + pa.getApplicationType() + " != " + paType);
498                 }
499                 if (!monitored && changeMonitor != null)
500                 {
501                     changeMonitor.remove(contextName);
502                 }
503                 if (log.isDebugEnabled())
504                 {
505                     log.debug("unregistering portlet application " + contextName + "...");
506                 }
507                 portletFactory.unregisterPortletApplication(pa);                        
508             }
509 //            if (register && (pa == null || checksum != pa.getChecksum()))
510             if (register)
511             {
512             	if (pa == null)
513             	{ 
514             		// new
515 	                try
516 	                {
517 	                    if (log.isDebugEnabled())
518                         {
519 	                        log.debug("Register new portlet application " + contextName + ".");
520                         }
521 	                    pa = registerPortletApplication(paWar, pa, paType, paClassLoader);
522 	                }
523 	                catch (Exception e)
524 	                {
525 	                    String msg = "Error register new portlet application " + contextName + ".";
526 	                	
527 	                    if (log.isDebugEnabled())
528 	                	{
529 	                	    log.debug(msg);
530 	                	}
531                     	throw new RegistryException(msg);
532 	                    
533 	                }
534             	}
535             	else
536             	{
537                     if (log.isDebugEnabled())
538                     {
539                         log.debug("Re-register existing portlet application " + contextName + ".");
540                     }
541             		int status = nodeManager.checkNode(new Long(pa.getId().toString()), pa.getName());
542         			boolean reregister = false;
543         			boolean deploy = false;
544         			switch (status)
545         			{
546         				case  NodeManager.NODE_NEW:
547         				{
548                             if (log.isDebugEnabled())
549                             {
550                                 log.debug("Node for Portlet application " + contextName + " is NEW.");
551                             }
552             				//only reason is that the file got somehow corrupted 
553             				// so we really do not know what is going on here...
554             				// the best chance at this point is to reregister (which might be the absolute wrong choice)
555             				log.warn("The portlet application " + pa.getName() + " is registered in the database but not locally .... we will reregister");
556             				reregister = true;
557         					if (checksum != pa.getChecksum())
558         					{
559         					    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)");
560     							deploy = true;
561         					}
562         					break;
563         				}
564         				case  NodeManager.NODE_SAVED:
565         				{
566                             if (log.isDebugEnabled())
567                             {
568                                 log.debug("Node for Portlet application " + contextName + " is SAVED.");
569                             }
570         					if (checksum != pa.getChecksum())
571                     		{	
572         					    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)");
573         						//database and local node info are in synch, so we assume that this is a brand new
574         						// war .... let's deploy
575         						reregister = true;
576         						deploy = true;
577                     		}
578         					break;
579         				}
580         				case  NodeManager.NODE_OUTDATED:
581         				{
582                             // new version in database, maybe changed by a different cluster node
583         				    if (log.isDebugEnabled())
584         				    {
585         				        log.debug("Node for Portlet application " + contextName + " is OUTDATED (local PA.id < DB PA.id).");
586         				    }
587             				//database version is older (determined by id) than the database 
588         					//let's deploy and reregister
589         				    if (checksum != pa.getChecksum())
590         				    {
591         					    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");
592         					    // if the checksums do not match make sure the database is updated with the new PA from file system
593         					    // I've observed "unavailable PA" in clustered env for the cluster node that reported OUTDATED state
594         					    deploy = true;
595         					}
596         				    reregister = true;
597         				    break;
598         				}
599         			}
600         			if (deploy)
601         			{
602         			    if (log.isDebugEnabled())
603         			    {
604         			        log.debug("Register (deploy=true) Portlet application " + contextName + " in database.");
605                         }
606 	                    pa = registerPortletApplication(paWar, pa, paType, paClassLoader);
607         			}
608         			else
609         				if (reregister)
610         				{
611                             if (log.isDebugEnabled())
612                             {
613                                 log.debug("Re-Register (reregister=true) Portlet application " + contextName + ".");
614                             }
615         					// add to search engine result
616         					this.updateSearchEngine(true, pa);
617         					this.updateSearchEngine(false, pa);
618         					
619         					// and add to the current node info
620         					try
621         					{
622         						nodeManager.addNode(new Long(pa.getId().toString()), pa.getName());
623         					} catch (Exception e)
624         					{
625         					    log.error("Adding node for portlet application " + pa.getName() + " caused exception" , e);
626         					}
627         				}
628         				
629             	
630             	}
631             }
632             if (register)
633             {
634                 if (log.isDebugEnabled())
635                 {
636                     log.debug("Register Portlet application " + contextName + " in portlet factory.");
637                 }
638                 portletFactory.registerPortletApplication(pa, paClassLoader);
639             }
640             
641             if (!monitored && changeMonitor != null)
642             {
643                 if (log.isDebugEnabled())
644                 {
645                     log.debug("Add change monitor for application " + contextName + " with checksum " + checksum + ".");
646                 }
647                 changeMonitor.monitor(contextName, contextPath, paClassLoader, paType, warStruct.getRootDirectory(), checksum);
648             }
649 		}
650         catch (Exception e)
651         {
652             String msg = "Error starting portlet application " + contextName;
653             
654             log.error(msg, e);
655             // monitor PA for changes
656             // do not add monitor if a monitor already exists
657             if (!monitored && changeMonitor != null)
658             {
659                 // this code should be hit only during startup process
660                 if (log.isDebugEnabled())
661                 {
662                     log.debug("Add change monitor for application " + contextName + " and set unsuccessful starts to 1.");
663                 }
664                 changeMonitor.monitor(contextName, contextPath, paClassLoader, paType, warStruct.getRootDirectory(), checksum);
665                 changeMonitor.get(contextName).setUnsuccessfulStarts(1);
666             }
667             throw new RegistryException(msg);
668         }
669 		finally
670 		{
671 			if (paWar != null)
672 			{
673 				try
674 				{
675 					paWar.close();
676 				}
677 				catch (IOException e)
678 				{
679 				    log.error("Failed to close PA WAR for " + contextName, e);
680 				}
681 			}
682 		}
683 	}
684 
685 	protected void stopPA(String contextName, int paType)
686 		throws RegistryException
687 	{
688 		MutablePortletApplication pa = null;
689         
690         try
691         {
692             pa = registry.getPortletApplication(contextName);
693         }
694         catch (Exception e)
695         {
696             // ignore errors during portal shutdown
697         }
698         if  (pa != null && pa.getApplicationType() != paType) 
699         {
700             throw new RegistryException("Cannot stop portlet application "+contextName+": as Application Types don't match: " + pa.getApplicationType() + " != " + paType);
701         }
702         DescriptorChangeMonitor monitor = this.monitor;
703         if ( monitor != null )
704         {
705             monitor.remove(contextName);
706         }
707 		if (pa != null)
708 		{
709             portletFactory.unregisterPortletApplication(pa);
710 		}
711 	}
712 
713 	
714 	protected void updateSearchEngine(boolean remove,MutablePortletApplication pa )
715 	{
716 		if (searchEngine != null)
717 		{
718 			if (remove)
719 			{
720 				searchEngine.remove(pa);
721 				searchEngine.remove(pa.getPortletDefinitions());
722 				log.info("Un-Registered the portlet application in the search engine... " + pa.getName());
723 			}
724 			else
725 			{
726 			    searchEngine.add(pa);
727                 searchEngine.add(pa.getPortletDefinitions());
728                 log.info("Registered the portlet application in the search engine... " + pa.getName());
729 			}
730 		}
731 		
732 	}
733 	protected void unregisterPortletApplication(MutablePortletApplication pa,
734 		boolean purgeEntityInfo)
735 		throws RegistryException
736 	{
737 
738 		updateSearchEngine(true,pa);
739 		log.info("Remove all registry entries defined for portlet application " + pa.getName());
740 
741 		Iterator portlets = pa.getPortletDefinitions().iterator();
742 
743 		while (portlets.hasNext())
744 		{
745 			PortletDefinition portletDefinition = (PortletDefinition) portlets.next();
746 			Iterator		  entities = entityAccess.getPortletEntities(portletDefinition)
747 													 .iterator();
748 
749 			while (entities.hasNext())
750 			{
751 				PortletEntity entity = (PortletEntity) entities.next();
752 
753 				if (purgeEntityInfo)
754 				{
755 					try
756 					{
757 						entityAccess.removePortletEntity(entity);
758 					}
759 					catch (PortletEntityNotDeletedException e)
760 					{
761 						String msg = "Failed to delete Portlet Entity " + entity.getId();
762 						log.error(msg, e);
763 						throw new RegistryException(msg, e);
764 					}
765 				}
766 
767 				entityAccess.removeFromCache(entity);
768 				windowAccess.removeWindows(entity);
769 			}
770 		}
771 
772 		// todo keep (User)Prefs?
773 		registry.removeApplication(pa);
774         revokeDefaultPermissions(pa.getName());
775 	}
776     
777     protected void grantDefaultPermissions(String paName)
778     {
779         try
780         {
781             // create a default permission for this portlet app, granting configured roles to the portlet application 
782             Iterator roles = permissionRoles.iterator();
783             while (roles.hasNext())
784             {
785                 String roleName = (String)roles.next();
786                 Role userRole = roleManager.getRole(roleName);
787                 if (userRole != null)
788                 {
789                     Permission permission = new PortletPermission(paName + "::*", "view, edit");
790                     if (!permissionManager.permissionExists(permission))
791                     {
792                         permissionManager.addPermission(permission);
793                         permissionManager.grantPermission(userRole.getPrincipal(), permission);
794                     }                    
795                 }
796             }
797         }
798         catch (SecurityException e)
799         {
800             log.error("Error granting default permissions for " + paName, e);
801         }        
802     }
803     
804     protected void revokeDefaultPermissions(String paName)
805     {
806         try
807         {
808             Iterator roles = permissionRoles.iterator();
809             while (roles.hasNext())
810             {
811                 String roleName = (String)roles.next();
812                 Role userRole = roleManager.getRole(roleName);
813                 if (userRole != null)
814                 {
815                     Permission permission = new PortletPermission(paName + "::*", "view, edit");
816                     if (permissionManager.permissionExists(permission))
817                     {
818                         permissionManager.removePermission(permission);
819                     }                    
820                     
821                 }
822             }
823         }
824         catch (SecurityException e)
825         {
826             log.error("Error revoking default permissions for " + paName, e);
827         }
828     }
829 
830     public int getDescriptorChangeMonitorInterval()
831     {
832         return descriptorChangeMonitorInterval/1000;
833     }
834 
835     public void setDescriptorChangeMonitorInterval(int descriptorChangeMonitorInterval)
836     {
837         this.descriptorChangeMonitorInterval = descriptorChangeMonitorInterval*1000;
838     }    
839     
840     private static class DescriptorChangeMonitor extends Thread
841     {
842         private static class DescriptorChangeMonitorInfo
843         {
844             private String contextName;
845             private String contextPath;
846             private ClassLoader paClassLoader;
847             private int  paType;
848             private File paDir;
849             private File[] descriptors;
850             private long descriptorModificationTime;
851             private long extendedDescriptorModificationTime;
852             private long checksum;
853             private boolean obsolete;
854             
855             /***
856              * holds the number of unsuccessful starts of the monitored PA
857              */
858             private int unsuccessfulStarts;
859                         
860             /*
861              * Constructor only used for looking up the matching registered one in monitorsInfo
862              */
863             public DescriptorChangeMonitorInfo(String contextName)
864             {
865                 this.contextName = contextName;
866             }
867             
868             public DescriptorChangeMonitorInfo(String contextName, String contextPath, ClassLoader paClassLoader, int paType, File paDir, long checksum)
869             {
870                 this.contextName = contextName;
871                 this.contextPath = contextPath;
872                 this.paClassLoader = paClassLoader;
873                 this.paType = paType;
874                 this.paDir = paDir.isAbsolute() ? paDir : paDir.getAbsoluteFile();
875                 this.checksum = checksum;
876                 
877                 this.descriptors = new File[] { 
878                         new File(paDir, PortletApplicationWar.WEB_XML_PATH),
879                         new File(paDir, PortletApplicationWar.PORTLET_XML_PATH),
880                         new File(paDir, PortletApplicationWar.EXTENDED_PORTLET_XML_PATH) };
881 
882                 descriptorModificationTime = descriptors[1].lastModified();
883                 extendedDescriptorModificationTime = descriptors[2].lastModified();
884             }
885             
886             public String getContextName()
887             {
888                 return contextName;
889             }
890             
891             public ClassLoader getPAClassLoader()
892             {
893                 return paClassLoader;
894             }
895             
896             public int getPortletApplicationType()
897             {
898                 return paType;
899             }
900             
901             public File getPADir()
902             {
903                 return paDir;
904             }
905 
906             public long getChecksum()
907             {
908                 return checksum;
909             }
910             
911             public boolean isChanged()
912             {
913                 if ( !obsolete)
914                 {
915                     long newDescriptorModificationTime = descriptors[1].lastModified();
916                     long newExtendedDescriptorModificationTime = descriptors[2].lastModified();
917                     if ( descriptorModificationTime != newDescriptorModificationTime ||
918                             extendedDescriptorModificationTime != newExtendedDescriptorModificationTime )
919                     {
920                         descriptorModificationTime = newDescriptorModificationTime;
921                         extendedDescriptorModificationTime = newExtendedDescriptorModificationTime;
922                         long newChecksum = MultiFileChecksumHelper.getChecksum(descriptors);
923                     	if (log.isDebugEnabled())
924                         {
925                     		log.debug("checksum check for descriptors for application " + contextName + ": old (" + checksum + ") new (" + newChecksum + ").");
926                     	}
927                         if ( checksum != newChecksum )
928                         {
929                         	if (log.isDebugEnabled())
930                             {
931                         		log.debug("portlet descriptors for application " + contextName + " have changed.");
932                         	}
933                             checksum = newChecksum;
934                             // reset this to restart unsuccessful PA start handling for evers PA descriptor change
935                             unsuccessfulStarts = 0;
936                             return true;
937                         }
938                     }
939                 }
940                 return false;
941             }
942             
943             public void setObsolete()
944             {
945                 obsolete = true;
946             }
947             
948             public boolean isObsolete()
949             {
950                 return obsolete;
951             }
952 
953             public int getUnsuccessfulStarts()
954             {
955                 return unsuccessfulStarts;
956             }
957             
958             public void setUnsuccessfulStarts(int unsuccessfulStarts)
959             {
960                 this.unsuccessfulStarts = unsuccessfulStarts;
961             }
962             
963             public String getContextPath()
964             {
965                 return contextPath;
966             }
967         }        
968 
969         private PortletApplicationManager pam;
970         private long interval;
971         private boolean started = true;
972         private ArrayList monitorInfos;
973         private int maxRetriedStarts;
974 
975         public DescriptorChangeMonitor(ThreadGroup group, String name, PortletApplicationManager pam, long interval, int maxretriedStarts)
976         {
977             super(group, name);
978             this.pam = pam;
979             this.interval = interval;
980             monitorInfos = new ArrayList();
981             setPriority(MIN_PRIORITY);
982             setDaemon(true);
983             this.maxRetriedStarts = maxretriedStarts;
984         }
985         
986         public void run()
987         {
988             try
989             {
990                 sleep(interval);
991             }
992             catch (InterruptedException e)
993             {
994             }
995             while (started)
996             {
997                 checkDescriptorChanges();
998 
999                 try
1000                 {
1001                     sleep(interval);
1002                 }
1003                 catch (InterruptedException e)
1004                 {
1005 
1006                 }
1007             }
1008         }
1009 
1010         /***
1011          * notifies a switch variable that exits the watcher's montior loop started in the <code>run()</code> method.
1012          */
1013         public synchronized void safeStop()
1014         {
1015             started = false;
1016             monitorInfos.clear();
1017         }
1018         
1019         public synchronized void monitor(String contextName, String contextPath, ClassLoader paClassLoader, int paType, File paDir, long checksum)
1020         {
1021             monitorInfos.add(new DescriptorChangeMonitorInfo(contextName, contextPath, paClassLoader, paType, paDir, checksum));
1022         }
1023         
1024         public synchronized void remove(String contextName)
1025         {
1026             DescriptorChangeMonitorInfo monitorInfo;
1027             for ( int i = monitorInfos.size()-1; i > -1; i-- )
1028             {
1029                 monitorInfo = (DescriptorChangeMonitorInfo)monitorInfos.get(i);
1030                 if (contextName.equals(monitorInfo.getContextName()))
1031                 {
1032                     // will be removed by checkDescriptorChanges on next iteration
1033                     monitorInfo.setObsolete();
1034                     break;
1035                 }
1036             }
1037         }
1038 
1039         public synchronized DescriptorChangeMonitorInfo get(String contextName)
1040         {
1041             DescriptorChangeMonitorInfo monitorInfo;
1042             for ( int i = monitorInfos.size()-1; i > -1; i-- )
1043             {
1044                 monitorInfo = (DescriptorChangeMonitorInfo)monitorInfos.get(i);
1045                 if (contextName.equals(monitorInfo.getContextName()))
1046                 {
1047                     return monitorInfo;
1048                 }
1049             }
1050             return null;
1051         }
1052         
1053         public boolean isMonitored(String contextName)
1054         {
1055         	DescriptorChangeMonitorInfo monitorInfo = this.get(contextName);
1056         	if (monitorInfo != null && !monitorInfo.isObsolete())
1057             {
1058         		return true;
1059         	}
1060             return false;
1061         }
1062         
1063         private void checkDescriptorChanges()
1064         {
1065             int size;
1066             synchronized (this)
1067             {
1068                 size = monitorInfos.size();
1069             }
1070 
1071         	if (log.isDebugEnabled())
1072             {
1073         		log.debug("check for portlet application descriptor changes.");
1074         	}
1075             
1076             for (int i = size-1; i > -1; i--)
1077             {
1078                 DescriptorChangeMonitorInfo monitorInfo;
1079                 synchronized (this)
1080                 {
1081                     if ( started )
1082                     {
1083                         monitorInfo = (DescriptorChangeMonitorInfo)monitorInfos.get(i);
1084                         if (monitorInfo.isObsolete())
1085                         {
1086                             monitorInfos.remove(i);
1087                         }
1088                         else
1089                         {
1090                             try
1091                             {
1092                                 int unsuccessfulStarts = monitorInfo.getUnsuccessfulStarts();
1093                                 // try to restart PA if the PA-descriptors have change
1094                                 // OR (if we encountered an exception while starting the PA)
1095                                 // keep on trying to restart PA until maxRetriedStarts is reached
1096                                 // This ensures we finally startup in a clustered enviroment, where parallel registration
1097                                 // of PAs could lead to contraint violation eceptions in DB.
1098                                 // see https://issues.apache.org/jira/browse/JS2-666
1099                                 // Note: monitorInfo.isChanged() will reset unsuccessfulStarts to 0 if a PA descriptor change 
1100                                 // has been detected (monitorInfo.isChanged() == true).
1101                                 if (monitorInfo.isChanged() || (unsuccessfulStarts > 0 && unsuccessfulStarts <= maxRetriedStarts))
1102                                 {
1103                                     try
1104                                     {
1105                                         pam.startPA(monitorInfo.getContextName(), monitorInfo.getContextPath(), new DirectoryHelper(monitorInfo.getPADir()),
1106                                                 monitorInfo.getPAClassLoader(), monitorInfo.getPortletApplicationType(), monitorInfo.getChecksum());
1107                                         // great! we have a successful start. set unsuccessful starts to 0
1108                                         monitorInfo.setUnsuccessfulStarts(0);
1109                                     }
1110                                     catch (Exception e)
1111                                     {
1112                                         if (monitorInfo.isChanged())
1113                                         {
1114                                             log.error("Failed to restart PortletApplication "+monitorInfo.getContextName(),e);
1115                                         }
1116                                         else if (log.isWarnEnabled())
1117                                         {
1118                                             log.warn("Failed to restart PortletApplication "+monitorInfo.getContextName(),e);
1119                                         }
1120                                         // we encountered an error while starting the PA
1121                                         // this could result from clustered environments problems (see above)
1122                                         // increase unsuccessfulStarts until the maxRetriedStarts is reached
1123                                         monitorInfo.setUnsuccessfulStarts(unsuccessfulStarts + 1);
1124                                         if (log.isDebugEnabled())
1125                                         {
1126                                             log.debug("Number of unsuccessful PA starts is " + monitorInfo.getUnsuccessfulStarts() + ".");
1127                                         }
1128                                         if (monitorInfo.getUnsuccessfulStarts() > maxRetriedStarts)
1129                                         {
1130                                             log.error("Max number of retries (" + maxRetriedStarts +") reached. Ignoring Monitor for " + monitorInfo.getContextName());
1131                                         }
1132                                     }
1133                                 }
1134                             }
1135                             catch (Exception e)
1136                             {
1137                                 // ignore filesystem and/or descriptor errors, maybe next time round they'll be fixed again
1138                                 log.error("Descriptor Change check failure for PortletApplication "+monitorInfo.getContextName(),e);
1139                             }
1140                         }
1141                     }
1142                 }
1143             }
1144         }
1145     }
1146 
1147     public void setMaxRetriedStarts(int maxRetriedStarts)
1148     {
1149         this.maxRetriedStarts = maxRetriedStarts;
1150     }
1151 
1152     public int getMaxRetriedStarts()
1153     {
1154         return maxRetriedStarts;
1155     }    
1156 }