View Javadoc

1   /*
2    * Copyright 2000-2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.jetspeed.services.portletfactory;
18  
19  //jetspeed stuff
20  import org.apache.jetspeed.portal.Portlet;
21  import org.apache.jetspeed.portal.PortletConfig;
22  import org.apache.jetspeed.portal.PortletException;
23  import org.apache.jetspeed.portal.BasePortletConfig;
24  
25  import org.apache.jetspeed.portal.security.portlets.PortletWrapper;
26  
27  import org.apache.jetspeed.om.SecurityReference;
28  import org.apache.jetspeed.om.profile.Entry;
29  import org.apache.jetspeed.om.profile.Parameter;
30  import org.apache.jetspeed.om.profile.MetaInfo;
31  import org.apache.jetspeed.services.JetspeedSecurity;
32  import org.apache.jetspeed.services.Registry;
33  import org.apache.jetspeed.services.PortalToolkit;
34  import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
35  import org.apache.jetspeed.services.logging.JetspeedLogger;
36  import org.apache.jetspeed.services.portletcache.PortletCache;
37  import org.apache.jetspeed.services.portletcache.Cacheable;
38  import org.apache.jetspeed.om.registry.PortletEntry;
39  import org.apache.jetspeed.om.profile.Profile;
40  import org.apache.jetspeed.util.MetaData;
41  import org.apache.jetspeed.services.rundata.JetspeedRunDataService;
42  import org.apache.jetspeed.services.rundata.JetspeedRunData;
43  
44  import org.apache.turbine.services.TurbineServices;
45  import org.apache.turbine.services.TurbineBaseService;
46  import org.apache.turbine.services.InitializationException;
47  import org.apache.turbine.services.resources.ResourceService;
48  import org.apache.turbine.services.rundata.RunDataService;
49  
50  import java.util.Hashtable;
51  import java.util.Iterator;
52  import java.util.Map;
53  import java.util.HashMap;
54  import javax.servlet.ServletConfig;
55  
56  /***
57   * Simple implementation of the PortalFactoryService.
58   * 
59   * @author <a href="mailto:raphael@apache.org">Raphaël Luta</a>
60   * @author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
61   * @version $Id: JetspeedPortletFactoryService.java,v 1.23 2004/02/23 03:36:42 jford Exp $
62   */
63  public class JetspeedPortletFactoryService extends TurbineBaseService
64      implements PortletFactoryService
65  {
66      /***
67       * Static initialization of the logger for this class
68       */    
69      private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(JetspeedPortletFactoryService.class.getName());
70      
71      /*** The default control to use when none is specified */
72      private boolean enableCache = false;
73  
74      /*** The JetspeedRunData Service. */
75      private JetspeedRunDataService runDataService = null;
76      
77      /***
78       * This is the early initialization method called by the 
79       * Turbine <code>Service</code> framework
80       */
81      public void init( ServletConfig conf ) throws InitializationException
82      {
83  
84          ResourceService serviceConf = ((TurbineServices)TurbineServices.getInstance())
85                                                       .getResources(PortletFactoryService.SERVICE_NAME);
86  
87          this.enableCache = serviceConf.getBoolean("enable.cache",true);
88          
89          // get the runData service
90          this.runDataService =
91              (JetspeedRunDataService)TurbineServices.getInstance()
92                  .getService(RunDataService.SERVICE_NAME);
93  
94          setInit(true);
95      }
96              
97      /***
98       * Given a PSML Entry return an instanciated Portlet.
99       *
100      * @param entry a PSML Entry describing a portlet
101      * @param id the PSML entry's portlet id     
102      * @return an instanciated portlet corresponding to this entry
103      */
104     public Portlet getPortlet( Entry entry ) throws PortletException
105     {
106         PortletEntry regEntry = (PortletEntry)Registry.getEntry(Registry.PORTLET, 
107                                                                 entry.getParent() );
108         if (regEntry == null)
109         {
110             throw new PortletException("PortletFactory: unknown portlet entry in Registry: "+entry.getParent());
111         }
112         
113         if (PortletEntry.TYPE_ABSTRACT.equals(regEntry.getType()))
114         {
115             throw new PortletException("PortletFactory: can't instanciate abstract registry entry: "+regEntry.getName());
116         }
117             
118         PortletConfig pc = getPortletConfig(regEntry, entry.getId());
119 
120         // Set portlet config with values from PSML Entry
121         pc.getInitParameters().putAll(getParameters(entry));
122         pc.setPortletSkin( PortalToolkit.getSkin( entry.getSkin() ) );
123         pc.setSecurityRef( getSecurityReference(entry, regEntry));
124 
125         return getPortlet( getClassname(regEntry), pc, entry.getId() );
126     }
127 
128     /***
129      * Given a Portlet registry entry name, instanciate it
130      *
131      * @param name the name of a portlet in the registry
132      * @return an instanciated portlet corresponding to this entry
133      */
134     public Portlet getPortlet( String name, String id ) throws PortletException
135     {
136         PortletEntry regEntry = (PortletEntry)Registry.getEntry(Registry.PORTLET, name );
137 
138         if (regEntry == null)
139         {
140             throw new PortletException("PortletFactory: unknown portlet entry in Registry: "+name);
141         }
142         
143         if (PortletEntry.TYPE_ABSTRACT.equals(regEntry.getType()))
144         {
145             throw new PortletException("PortletFactory: can't instanciate abstract registry entry: "+name);
146         }
147             
148         PortletConfig pc = getPortletConfig(regEntry, id);
149         
150         return getPortlet( getClassname(regEntry), pc, null );
151     }
152     
153     /*** 
154      * Instanciates or retrieve from memory cache a portlet corresponding to the 
155      * passed parameters
156      *
157      * @param classname the classname of the portlet to instanciate
158      * @param pc the PortletConfig object to be associated with this object
159      * @param id the PSML entry's portlet id
160      * @return the Portlet created or retrieve from cache
161      */
162     protected Portlet getPortlet( String classname, PortletConfig pc, String id )
163         throws PortletException
164     {
165 
166         //record the begining of the portlet creation
167         long begin = System.currentTimeMillis();
168 
169         Portlet portlet = null;
170         Class portletClass = null;
171         String handle = null;
172         
173         try
174         {
175             portletClass = Class.forName(classname);
176         }
177         catch (Exception e)
178         {
179             throw new PortletException( "PortletFactory: Unable to load class " + classname );
180         }
181         
182         if (enableCache)
183         {
184             try
185             {
186                 // try to invoke a static getHandle() for this class
187                 Class[] signatureParams = { Object.class };
188                 Object[] methodParams = { pc };
189                 handle = (String)portletClass.getMethod("getHandle",signatureParams)
190                                              .invoke(null,methodParams);
191                 // make sure the handle is differenciated by class
192                 handle = String.valueOf(classname.hashCode())+handle;
193             }
194             catch (NoSuchMethodException e)
195             {
196                 // ignore, this simply means the portlet is not cacheable
197             }
198             catch (Exception e)
199             {
200                 // invocation failed or security exception, in both case
201                 // log the error and treat the class as non cacheable
202                 logger.error("PortletFactory: failed to get cache handle",e);
203             }
204         }
205         
206         try {
207 
208             if (enableCache && (handle != null))
209             {
210                 portlet = (Portlet)PortletCache.getCacheable( handle );
211 
212                 //portlet in cache but expired, remove it from cache
213                 if ((portlet!=null) && ((Cacheable)portlet).getExpire().isExpired() )
214                 {
215                     logger.info( "The portlet (" + handle + ") is expired" );
216                     PortletCache.removeCacheable(handle);
217                     if ( logger.isDebugEnabled() )
218                     {
219                         logger.debug( "After removal of object(" + handle + ")." );
220                     }
221                     portlet = null;
222                 }
223             }
224 
225             // we found a portlet in the cache
226             if ( (portlet != null)
227                  && ( portlet instanceof Cacheable )
228                  && (! ((Cacheable)portlet).getExpire().isExpired()) )
229             {
230                 // update the config for the portlet to the current one
231                 // Note: this is what was used to find the cached portlet.
232                 // Note: the init params may have changed in the psml since caching,
233                 //       this will update the portlet to use them.
234                 portlet.setPortletConfig( pc );
235                 portlet.setID( id );
236                 portlet.setName( pc.getName() );
237 
238                 //FIXME: we now avoid to override metainfo when nothing is set
239                 //in the markup, so that cached portlets can keep their metainfo
240                 //This may lead to an incorrect metainfo retrieved if the first
241                 //instance of the portlet, which is put in the cache, has some
242                 //special metainfo defined in the markup 
243 
244                 MetaData meta = pc.getMetainfo();
245                 
246                 if ( meta != null)
247                 {
248 
249                     if (! MetaData.DEFAULT_TITLE.equals( meta.getTitle() ) )
250                     {
251                         portlet.setTitle( meta.getTitle() );
252                     }
253     
254                     if (! MetaData.DEFAULT_DESCRIPTION.equals( meta.getDescription() ) )
255                     {
256                         portlet.setDescription( meta.getDescription() );
257                     }
258                 }
259 
260                 //FIXME: Notice here we are putting the portlet without wrapper
261                 //in the cache, and we must wrap it on return.
262                 //Security implications: the portletcache should not be
263                 //publicly accessible.
264                 //Alternative: we could wrap the portlet before putting
265                 //it in the cache.
266 
267                 //now compute the time it took to instantate and log it...
268                 // time in millis, sugested by Thomas Schaeck (schaeck@de.ibm.com)
269                 long milliseconds = ( System.currentTimeMillis() - begin );
270         
271                 if (logger.isDebugEnabled())
272                     logger.debug( "PortletFactory.getPortlet(): found in cache in "
273                         + milliseconds + " ms - handle: " + handle );
274 
275                 return PortletWrapper.wrap( portlet );
276             }
277 
278             // if not found in the cache, instanciate a new Portlet
279             portlet = (Portlet)portletClass.newInstance();
280 
281         }
282         catch ( Throwable t )
283         {
284             logger.error("Throwable", t);
285             throw new PortletException( t.getMessage() );
286         }
287 
288         // save the current meta-info
289         String title = null;
290         String description = null;
291         MetaData metainfo = pc.getMetainfo();
292         
293         if ( metainfo != null ) {
294             title=metainfo.getTitle();
295             description=metainfo.getDescription();
296         }
297         
298         
299         // init the portlet, it may override its PSML defined markup if
300         // it doesn't check for it
301         portlet.setID( id );
302         portlet.setName( pc.getName() );
303         portlet.setPortletConfig( pc );
304         portlet.setCreationTime( System.currentTimeMillis() );
305         portlet.init();
306 
307         //force the title and description from markup metadata
308         //in case the portlet overwrote some values
309 
310         if ( metainfo != null)
311         {
312             if (!MetaData.DEFAULT_TITLE.equals(title) )
313             {
314                 portlet.setTitle( title );
315             }
316 
317             if (!MetaData.DEFAULT_DESCRIPTION.equals(description) )
318             {
319                 portlet.setDescription( description );
320             }
321         }
322 
323         if (enableCache && (portlet instanceof Cacheable))
324         {
325             //place this portlet in a cache...
326             ((Cacheable)portlet).setHandle( handle );
327             PortletCache.addCacheable( ((Cacheable)portlet) );
328             //Expiration should be added to the portlet now, so that
329             //the watcher is created before file changes on disk.
330             ((Cacheable)portlet).getExpire();
331 
332         }
333 
334         //now compute the time it took to instantate and log it...
335         // time in millis, sugested by Thomas Schaeck (schaeck@de.ibm.com)
336         long milliseconds = ( System.currentTimeMillis() - begin );
337 
338         if (logger.isDebugEnabled())
339             logger.debug( "PortletFactory.getPortlet(): constructed in "
340                 + milliseconds + " ms - handle: " + handle );
341 
342         return PortletWrapper.wrap( portlet );
343 
344     }
345 
346     /***
347      * Given a Registry Entry, get the value of what its PortletConfig would be.
348      *
349      * @param entry the PSML Entry containing the config
350      * @param portletId the PSML entry's portlet id
351      * @return the newly created PortletConfig object
352      */
353     protected PortletConfig getPortletConfig( PortletEntry portletEntry, String id)
354     {
355         Map map = new HashMap();
356         map.putAll(portletEntry.getParameterMap());
357         
358         PortletConfig pc = new BasePortletConfig();
359         pc.setName( portletEntry.getName() );
360         addParentInitParameters(portletEntry, map);        
361         pc.setInitParameters( map );
362         pc.setMetainfo( getMetaData( portletEntry ) );
363         pc.setURL( portletEntry.getURL() );
364         pc.setCachedOnURL( portletEntry.isCachedOnURL() );
365         //pc.setSecurityRef(portletEntry.getSecurityRef());
366         pc.setSecurityRef(getSecurityReference(null, portletEntry));
367 
368         if (runDataService != null)
369         {
370             JetspeedRunData rundata = runDataService.getCurrentRunData();
371             if (rundata != null)
372             {
373                 Profile profile = rundata.getProfile();
374                 if (profile != null)
375                 {
376                     pc.setPageId(profile.getId());
377                 }
378             }
379         }
380         pc.setPortletId(id);
381 
382         return pc;
383     }
384     
385     
386     /***
387      * Fetches the parameters out of a PSML Entry
388      * 
389      * @param entry the Entry to check for parameters
390      * @return a Map containing the parameters names/values, an empty Map 
391      *         is returned if there are no parameters
392      */
393     protected static Map getParameters( Entry entry )
394     {
395         Hashtable hash = new Hashtable();
396         
397         Parameter[] props = entry.getParameter();
398         
399         for(int i = 0; i < props.length; ++i)
400         {
401             hash.put(props[i].getName(), props[i].getValue() );
402         }
403         
404         return hash;
405     }
406 
407     /***
408     Create a MetaData object from a PSML Metainfo object
409     
410     @param meta the Metainfo to copy
411 
412     @return the new MetaData object, empty if meta is null
413     */
414     protected static MetaData getMetaData(Entry entry)
415     {
416         MetaData data = new MetaData();
417         MetaInfo meta = entry.getMetaInfo();
418 
419         if ( meta != null )
420         {
421             if ( meta.getTitle() != null )
422                 data.setTitle( meta.getTitle() );
423 
424             if ( meta.getDescription() != null )
425                 data.setDescription( meta.getDescription() );
426 
427             if ( meta.getImage() != null )
428                 data.setImage( meta.getImage() );
429         }
430 
431         if ( entry.getParent() != null )
432         {
433 
434             PortletEntry parent = (PortletEntry)Registry
435                 .getEntry( Registry.PORTLET, entry.getParent() );
436 
437             if (parent != null)
438             {
439                 MetaData parentData = getMetaData( parent );
440                 parentData.merge(data);
441                 return parentData;
442             }
443             
444         }
445 
446         return data;
447 
448     }
449 
450     /***
451     Create a MetaData object from a registry Metainfo object
452     
453     @param meta the Metainfo to copy
454 
455     @return the new MetaData object, empty if meta is null
456     */
457     protected static MetaData getMetaData(PortletEntry entry)
458     {
459         MetaData data = new MetaData();
460 
461         if ( entry.getTitle() != null )
462             data.setTitle( entry.getTitle() );
463 
464         if ( entry.getDescription() != null )
465             data.setDescription( entry.getDescription() );
466             
467 		if ( entry.getMetaInfo() != null && entry.getMetaInfo().getImage() != null )
468 			data.setImage( entry.getMetaInfo().getImage() );
469             
470         return data;
471     }
472     
473     /***
474      * @param Entry entry Entry whose parent we want
475      * @return PortletEntry Parent of Entry
476      * @author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
477      */
478     protected static PortletEntry getParentEntry(PortletEntry entry)
479     {
480         PortletEntry result = null;
481         String parent = entry.getParent();
482         if (parent != null)
483         {
484             result = (PortletEntry) Registry.getEntry(Registry.PORTLET, parent);
485         }
486 
487         return result;
488     }
489     
490     /***
491      * Retruns the classname defined for this PortletEntry.
492      * If no classname was defined, the parent is queried
493      * @author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
494      */
495     protected String getClassname(PortletEntry entry)
496     {
497         String className = entry.getClassname();
498         if (className == null)
499         {
500             PortletEntry parent = getParentEntry(entry);
501             if (parent != null)
502             {
503             	// We must walk up the hierarchy just to be safe
504                 className = getClassname(parent);
505             }
506         }
507 
508         return className;
509     }
510     
511     /***
512      * Maps all parameters, not found within the <code>entry</code>, from
513      * the <code>entry</code>'s parent into the entry
514      * @author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
515      */
516     protected void addParentInitParameters(PortletEntry entry, Map hash)
517     {
518         // Now map any parameters from the parent that the child does not have
519         PortletEntry parent = getParentEntry(entry);
520         if (parent != null)
521         {
522             Map parentMap = parent.getParameterMap();
523             Iterator names = parent.getParameterNames();
524 
525             while (names.hasNext())
526             {
527                 String key = (String) names.next();
528                 if (!hash.containsKey(key))
529                 {
530                     hash.put(key, parentMap.get(key));                    
531                 }
532             }
533             
534             // Always make sure to get the entire inheritence chain
535            addParentInitParameters(parent, hash);
536         }        
537     }
538     
539     /***
540      * Figures out how to produce a security reference for
541      * this portlet.
542      */
543     protected SecurityReference getSecurityReference(Entry entry, PortletEntry pEntry)
544     {
545         // If something happended during init() that prevented this
546         if (runDataService == null)
547         {
548             this.runDataService =
549                 (JetspeedRunDataService) TurbineServices.getInstance().getService(
550                     RunDataService.SERVICE_NAME);
551         }
552         JetspeedRunData rundata = runDataService.getCurrentRunData();
553         
554         return JetspeedSecurity.getSecurityReference(entry,  rundata);
555     }
556 
557 }