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.portal.portlets;
18  
19  //jetspeed
20  import org.apache.jetspeed.capability.CapabilityMap;
21  import org.apache.jetspeed.capability.CapabilityMapFactory;
22  import org.apache.jetspeed.om.registry.MediaTypeEntry;
23  import org.apache.jetspeed.om.registry.PortletEntry;
24  import org.apache.jetspeed.portal.BasePortletConfig;
25  import org.apache.jetspeed.portal.expire.Expire;
26  import org.apache.jetspeed.portal.expire.ExpireFactory;
27  import org.apache.jetspeed.portal.Portlet;
28  import org.apache.jetspeed.portal.PortletConfig;
29  import org.apache.jetspeed.portal.PortletException;
30  import org.apache.jetspeed.portal.PortletState;
31  import org.apache.jetspeed.services.persistence.PersistenceManager;
32  import org.apache.jetspeed.services.persistence.PortalPersistenceException;
33  import org.apache.jetspeed.portal.PortletInstance;
34  import org.apache.jetspeed.services.portletcache.Cacheable;
35  import org.apache.jetspeed.services.Registry;
36  import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
37  import org.apache.jetspeed.services.logging.JetspeedLogger;
38  import org.apache.jetspeed.util.JetspeedException;
39  import org.apache.jetspeed.util.MetaData;
40  import org.apache.jetspeed.util.MimeType;
41  
42  //ecs
43  import org.apache.jetspeed.util.JetspeedClearElement;
44  import org.apache.ecs.ConcreteElement;
45  
46  //turbine stuff
47  import org.apache.turbine.services.cache.CachedObject;
48  import org.apache.turbine.services.cache.Refreshable;
49  import org.apache.turbine.util.RunData;
50  
51  //java stuff
52  import java.util.Hashtable;
53  import java.util.Iterator;
54  /***
55  <p>
56  Should be used by most Portlets that wish to conform to default behavior
57  </p>
58  
59  <p>
60  PERFORMANCE NOTE:
61  
62  getContent returns a StringElement that was generated on setContent().  This is
63  used so that performance is increased since ECS does not have to work overtime
64  to generate output.
65  </p>
66  
67  @author <A HREF="mailto:burton@apache.org">Kevin A. Burton</A>
68  @author <A HREF="mailto:raphael@apache.org">Raphaël Luta</A>
69  @author <A HREF="mailto:sgala@apache.org">Santiago Gala</A>
70  @author <A HREF="mailto:paulsp@apache.org">Paul Spencer</A>
71  @author <A HREF="mailto:morciuch@apache.org">Mark Orciuch</A>
72  @version $Id: AbstractPortlet.java,v 1.65 2004/03/29 21:38:42 taylor Exp $
73  */
74  public abstract class AbstractPortlet implements Portlet, PortletState, Cacheable, Refreshable
75  {
76  
77      /***
78       * Static initialization of the logger for this class
79       */    
80      private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(AbstractPortlet.class.getName());    
81      
82      private boolean         cacheable = true;
83      private PortletConfig   pc = null;
84  
85      /***
86      Provide a required name for this Portlet
87      */
88      private String        name            = null;
89  
90      /***
91      Provide a Unique Portlet ID
92      */
93      private String id = null;
94  
95      /***
96      Cache handle for this object.
97      */
98      private String        handle          = "";
99  
100     /***
101     Expiration time of object in milliseconds since the standard base time
102     known as "the epoch", namely January 1, 1970, 00:00:00 GMT.
103     */
104     private Long          expirationMillis = null;
105 
106     /***
107     Holds instances of ConcreteElements (Portlet output/content)
108     based on its current CapabilityMap.
109     */
110     protected Hashtable     content         = new Hashtable();
111 
112     /***
113     The time this portlet was created.
114     */
115     private long creationTime;
116 
117     /***
118      * Handle to cached object
119      */
120     private CachedObject cachedObject = null;
121 
122     /***
123     */
124     protected void clearContent() {
125         this.content.clear();
126     }
127 
128     /***
129      */
130     protected void setContent( ConcreteElement content ) {
131         this.setContent( content,
132                          CapabilityMapFactory.getDefaultCapabilityMap() );
133     }
134 
135     /***
136     */
137     protected void setContent( String content ) {
138         this.setContent( new JetspeedClearElement( content ),
139                          CapabilityMapFactory.getDefaultCapabilityMap() );
140     }
141 
142     /***
143     */
144     protected void setContent( ConcreteElement content,
145                                CapabilityMap map )
146                           throws IllegalArgumentException
147     {
148         CapabilityMap mymap = map;
149         if ( mymap == null ) {
150             mymap = CapabilityMapFactory.getDefaultCapabilityMap();
151         }
152 
153         ConcreteElement buffer = new JetspeedClearElement( content.toString( ) );
154         this.content.put( mymap.toString(), buffer );
155     }
156 
157 
158     /*
159      * Implement methods required by Refreshable
160      */
161 
162     /***
163      * Usually called by caching system when portlet is marked as expired, but
164      * has not be idle longer then TimeToLive.
165      *
166      * This method should be implement in cachable portlets
167      */
168     public void refresh() {
169         /*
170          * The following message is here to add in debugging.  It is
171          * expected the any portlet type that is refreshable will
172          * implement this method.
173          */
174         logger.debug( "AbstractPortlet - Refreshing " + this.getName() );
175     }
176 
177     /*
178      * Implement methods required by Cacheable
179      */
180 
181     /***
182      * Is this portlet cacheable.  It is the portlet's responsability to
183      * cache the content.
184      *
185      * @return <CODE>true</CODE> Cachable<BR>
186      * <CODE>false</CODE> Not cachable
187      */
188     public boolean isCacheable() {
189         return this.cacheable;
190     }
191 
192     /***
193      * Set cachable.  This should only be called in the portlet's init().
194      *
195      * @param cacheable <CODE>true</CODE> Portlet is cachable<BR>
196      * <CODE>false</CODE> Portlet is NOT cachable
197      */
198     public void setCacheable(boolean cacheable) {
199         this.cacheable = cacheable;
200     }
201 
202 
203     /***
204      * Used by a Cacheable object to determine when it should expire itself from the cache.
205      *
206      * @return Expire
207      */
208     public Expire getExpire() {
209         try {
210             return ExpireFactory.getExpire( this, ExpireFactory.NO_EXPIRE );
211         } catch ( JetspeedException e ) {
212             logger.error("Exception",  e);
213             return null;
214         }
215     }
216 
217     /***
218      * <p>Used by the cache to get a unique reference on what you want to add
219      * and then retrieve in the future from the cache</p>
220      *
221      * <p>Most implementations should just call the CacheHandleManager with
222      * the given params within the implementation and just return this.</p>
223      *
224      * @return Cache handle (key)
225      */
226     public final String getHandle() {
227         return this.handle;
228     }
229 
230     /***
231      * Used by a Cacheable object to determine when it should
232      * expire itself from the cache.
233      *
234      * @param handle Cache Handle
235      *
236      * @deprecated cacheable classes should now implement a static getHandle(config) method
237      */
238     public final void setHandle( String handle ) {
239         this.handle = handle;
240     }
241 
242     /***
243      * Set the expiration time in milliseconds.
244      *
245      * @return Expiration time in milliseconds since epoch, or null if the expiration was not set.
246      */
247     public Long getExpirationMillis() {
248       return this.expirationMillis;
249     }
250 
251     /***
252      * Sets the cache expiration time.  When the portlet is stale (expired),
253      * the refresh() will be called if the portlet has not been untouched
254      * longer then then it's TimeToLive.
255      *
256      * @param expirationMillis setExpirationMillis Expiration in milliseconds since epoch
257      */
258     public void setExpirationMillis( long expirationMillis) {
259       this.expirationMillis = new Long(expirationMillis);
260 
261       if (cachedObject != null)  {
262           long expirationInterval = this.expirationMillis.longValue() - cachedObject.getCreated();
263           if (expirationInterval > 0) {
264               cachedObject.setExpires(expirationInterval);
265           } else {
266               cachedObject.setStale(true);
267           }
268       }
269     }
270 
271     /***
272      * Builds a new cache handle for this cacheable class with the specified
273      * config object.
274      *
275      * @param config The configuration object to use for building the handle
276      *
277      * @return A cache handle
278      */
279     public static Object getHandle(Object config)
280     {
281         //this implementation expects a PortletConfig object as its
282         // configuration
283         PortletConfig pc = null;
284 
285         if (!(config instanceof PortletConfig))
286         {
287             return null;
288 
289         }
290 
291         // By default, only take into account the init parameters
292         pc = (PortletConfig)config;
293         StringBuffer handle = new StringBuffer(256);
294 
295         if (pc.getURL()!=null && pc.isCachedOnURL())
296         {
297             handle.append(String.valueOf(pc.getURL().hashCode()));
298         }
299 
300         Iterator i = pc.getInitParameterNames();
301         while(i.hasNext())
302         {
303             String name = (String)i.next();
304             String value = pc.getInitParameter(name);
305 
306             if (value!=null)
307             {
308                 handle.append("|").append(name).append("-").append(value);
309             }
310         }
311 
312         return handle.toString();
313     }
314 
315     /***
316      * Set this portlet's cached object.
317      *
318      * @param cachedObject Cached Object associated to this portlet
319      */
320      public void setCachedObject(CachedObject cachedObject) {
321         this.cachedObject = cachedObject;
322     }
323 
324     /*
325      * Implement methods required by Portlet
326      */
327 
328     /***
329      * Get the portlet's name
330      *
331      * @return Name of the portlet
332      */
333     public String getName()
334     {
335 
336         if ( name == null )
337         {
338             if (getPortletConfig()!=null)
339             {
340                 if (getPortletConfig().getName()!=null)
341                 {
342                     return getPortletConfig().getName();
343                 }
344                 else
345                 {
346                     return this.getClass().getName();
347                 }
348             }
349         }
350 
351         return name;
352 
353     }
354 
355     /***
356      * Set the name of the portlet
357      *
358      * @param name Name of the portlet
359      */
360     public void setName( String name ) {
361         this.name = name;
362     }
363 
364     /***
365      * Get the config of this servlet
366      *
367      * @return PortletConfig Portlet
368      */
369     public PortletConfig getPortletConfig() {
370         return this.pc;
371     }
372 
373     /***
374      * Set's the configuration of this servlet.
375      */
376     public void setPortletConfig( PortletConfig pc ) {
377         this.pc = pc;
378     }
379 
380     /***
381      * @param rundata The RunData object for the current request
382      */
383     public ConcreteElement getContent( RunData rundata ) {
384 
385         return getContent( rundata, null , true );
386     }
387 
388     public ConcreteElement getContent( RunData rundata, CapabilityMap map ) {
389         CapabilityMap mymap = map;
390         if ( mymap == null ) mymap = CapabilityMapFactory.getCapabilityMap( rundata );
391 
392         return (ConcreteElement)content.get( mymap.toString() );
393     }
394 
395     /***
396      * @param rundata The RunData object for the current request
397      */
398     public ConcreteElement getContent( RunData rundata,
399                                        CapabilityMap map,
400                                        boolean allowRecurse ) {
401 
402         CapabilityMap mymap = map;
403         if ( mymap == null ) mymap = CapabilityMapFactory.getCapabilityMap( rundata );
404 
405         ConcreteElement element = (ConcreteElement)content.get( mymap.toString() );
406 
407         if ( element == null ) {
408             if ( allowRecurse ) {
409                 try {
410                     // init will put content under default cmap
411                     init( );
412                     element = getContent( rundata, mymap, false );
413                     if( element != null ) {
414                         // now we put it under our cmap
415                         this.setContent( element, mymap );
416                     }
417                 } catch (Exception e) {
418                     element = new JetspeedClearElement("Error when retrieving Portlet contents");
419                     if( logger.isDebugEnabled() ) {
420                         logger.debug( "Error when retrieving Portlet contents", e );
421                     }
422                 }
423             } else {
424                 if( element == null ) {
425                     //FIXME: Let's asume that the contents under "default" map are good
426                     mymap = CapabilityMapFactory.getDefaultCapabilityMap();
427                     element = (ConcreteElement)content.get( mymap.toString() );
428                     if( element == null ) {
429                         element = new JetspeedClearElement("Unknown Problem getting Contents");
430                     }
431                 }
432             }
433         }
434 
435         return element;
436 
437     }
438 
439     /***
440      * Provide a description within PML if the user has specified one.
441      *
442      * @return a null entry if the user hasn't defined anything
443      */
444     public String getDescription() {
445         if (getPortletConfig()!=null)
446             if (getPortletConfig().getMetainfo()!=null)
447                 return getPortletConfig().getMetainfo().getDescription();
448 
449         return null;
450     }
451 
452     /***
453      * Provide a Description within PML if the user has specified one.
454      *
455      * @return a null if entry AND portlet have not defined a description
456      */
457     public String getDescription(String instanceDescription)
458     {
459       if (instanceDescription != null)
460           return instanceDescription;
461       return getDescription();
462     }
463 
464     /***
465      */
466     public void setDescription( String description ) {
467         PortletConfig pc = getPortletConfig();
468         if (pc==null) {
469             pc = new BasePortletConfig();
470             setPortletConfig(pc);
471         }
472 
473         MetaData meta = pc.getMetainfo();
474         if (meta==null) {
475             meta = new MetaData();
476             pc.setMetainfo(meta);
477         }
478 
479         meta.setDescription(description);
480     }
481 
482     //provide default titles so that the user can define them in PML
483 
484     /***
485      * Provide a title within PML if the user has specified one.
486      *
487      * @return a null entry if the user hasn't defined anything
488      */
489     public String getTitle()
490     {
491         if (getPortletConfig()!=null)
492             if (getPortletConfig().getMetainfo()!=null)
493                 return getPortletConfig().getMetainfo().getTitle();
494 
495         return null;
496     }
497 
498     /***
499      * Provide a title within PML if the user has specified one.
500      *
501      * @return a null if entry AND portlet have not defined a title
502      */
503     public String getTitle(String instanceTitle)
504     {
505       if (instanceTitle != null)
506           return instanceTitle;
507       return getTitle();
508     }
509 
510     /***
511      * Set the title for this Portlet.
512      * @param title Portlet title.
513      */
514     public void setTitle( String title ) {
515         PortletConfig pc = getPortletConfig();
516         if (pc==null) {
517             pc = new BasePortletConfig();
518             setPortletConfig(pc);
519         }
520 
521         MetaData meta = pc.getMetainfo();
522         if (meta==null) {
523             meta = new MetaData();
524             pc.setMetainfo(meta);
525         }
526 
527         meta.setTitle(title);
528     }
529 
530 
531     /***
532      * Getter for property image.
533      * @return Name of portlet image, icon.  The name is expected to be in the form of a URL.
534      */
535     public String getImage()
536     {
537         if (getPortletConfig()!=null)
538             if (getPortletConfig().getMetainfo()!=null)
539                 return getPortletConfig().getMetainfo().getImage();
540 
541         return null;
542     }
543 
544     /***
545      * Getter for property image.
546      * @return a null if entry AND portlet have not defined an icon.
547      */
548     public String getImage(String instanceImage)
549     {
550       if (instanceImage != null)
551           return instanceImage;
552       return getImage();
553     }
554 
555     public void setImage( String image )
556     {
557         PortletConfig pc = getPortletConfig();
558         if (pc==null) {
559             pc = new BasePortletConfig();
560             setPortletConfig(pc);
561         }
562 
563         MetaData meta = pc.getMetainfo();
564         if (meta==null) {
565             meta = new MetaData();
566             pc.setMetainfo(meta);
567         }
568 
569         meta.setImage(image);
570     }
571 
572     /***
573      * Is the portled editable/customizeable.
574      * @param rundata The RunData object for the current request
575      * @return <CODE>true</CODE> Editing is allow
576      * <CODE>false</CODE> Editing is NOT alowed
577      */
578     public boolean getAllowEdit( RunData rundata )
579     {
580         return allowCustomize(rundata);
581     }
582 
583     /***
584      * Is the portled viewable.
585      * @param rundata The RunData object for the current request
586      * @return <CODE>true</CODE> Viewing is allow
587      * <CODE>false</CODE> Viewing is NOT alowed
588      * 
589      * Override this method to control your own View behavior
590      */
591     public boolean getAllowView( RunData rundata )
592     {
593         return allowView( rundata );
594     }
595     
596     /***
597      * Can this portlet be maximized
598      * @param rundata The RunData object for the current request
599      * @return <CODE>true</CODE> Portlet can be maximized<br>
600      * <CODE>false</CODE> Portlet can NOT be maximized
601      */
602     public boolean getAllowMaximize( RunData rundata )
603     {
604         return allowMaximize( rundata );
605     }
606 
607     /***
608      * By default don't provide any initialization
609      */
610     public void init( ) throws PortletException
611     {
612         // make sure to clean all content
613         clearContent();
614     }
615 
616     /***
617      */
618     public long getCreationTime() {
619         return this.creationTime;
620     }
621 
622     /***
623      */
624     public void setCreationTime( long creationTime ) {
625         this.creationTime = creationTime;
626     }
627 
628     /***
629      */
630     public boolean supportsType( MimeType mimeType )
631     {
632         PortletEntry entry = (PortletEntry)Registry.getEntry(Registry.PORTLET, getName() );
633         String baseType = mimeType.toString();
634         if (entry!=null)
635         {
636             Iterator i = entry.listMediaTypes();
637 
638             while(i.hasNext())
639             {
640                 String name = (String)i.next();
641                 MediaTypeEntry media = (MediaTypeEntry)Registry.getEntry(Registry.MEDIA_TYPE, name);
642                 if (media != null)
643                 {
644                     if (baseType.equals(media.getMimeType())) return true;
645                 }
646             }
647         }
648 
649         return MimeType.HTML.equals( mimeType );
650     }
651 
652     /*
653      * Implement methods required by PortletState
654      */
655 
656     /***
657      * Implements the default close behavior:
658      * security permissions will be checked.
659      *
660      * @param rundata The RunData object for the current request
661      */
662     public boolean allowClose( RunData rundata )
663     {
664         //Security will not allow this call to succeed if there are
665         //not enough permissions
666         return !isClosed( rundata );
667     }
668 
669     /***
670      * Returns true if this portlet is currently closed
671      *
672      * @param rundata The RunData object for the current request
673      */
674     public boolean isClosed(RunData rundata)
675     {
676         return this.getAttribute("_display", "normal", rundata ).equals("closed");
677     }
678 
679     /***
680      * Toggles the portlet state between closed and normal
681      *
682      * @param minimized the new portlet state
683      * @param rundata The RunData object for the current request
684      */
685     public void setClosed(boolean close, RunData rundata)
686     {
687         if( allowClose( rundata ) )
688         {
689             this.setAttribute("_display", close ? "closed" : "normal", rundata );
690         }
691     }
692 
693     /***
694      * Implements the default info behavior:
695      * security permissions will be checked.
696      *
697      * @param rundata The RunData object for the current request
698      */
699     public boolean allowInfo( RunData rundata )
700     {
701         //Security will not allow this call to succeed if there are
702         //not enough permissions
703         return true;
704     }
705 
706     /***
707      * Implements the default customize behavior:
708      * security permissions will be checked.
709      *
710      * @param rundata The RunData object for the current request
711      */
712     public boolean allowCustomize( RunData rundata )
713     {
714         //Security will not allow this call to succeed if there are
715         //not enough permissions
716         return true;
717     }
718 
719     /***
720      * Implements the default maximize behavior:
721      * security permissions will be checked.
722      *
723      * @param rundata The RunData object for the current request
724      */
725     public boolean allowMaximize( RunData rundata )
726     {
727         //Security will not allow this call to succeed if there are
728         //not enough permissions
729         return true;
730     }
731 
732     /***
733      * Implements the default info behavior:
734      * security permissions will be checked.
735      *
736      * @param rundata The RunData object for the current request
737      */
738     public boolean allowMinimize( RunData rundata )
739     {
740         //Security will not allow this call to succeed if there are
741         //not enough permissions
742         return true;
743     }
744 
745     /***
746      * Implements the default view behavior:
747      * security permissions will be checked.
748      *
749      * @param rundata The RunData object for the current request
750      */
751     public boolean allowView( RunData rundata )
752     {
753         //Security will not allow this call to succeed if there are
754         //not enough permissions
755         return true;
756     }
757     
758     /***
759      * Implements the default print friendly format behavior:
760      * security permissions will be checked.
761      *
762      * @param rundata The RunData object for the current request
763      */
764     public boolean allowPrintFriendly( RunData rundata )
765     {
766         //Security will not allow this call to succeed if there are
767         //not enough permissions
768         return true;
769     }
770 
771     /***
772      * Returns true if this portlet is currently minimized
773      *
774      * @param rundata The RunData object for the current request
775      */
776     public boolean isMinimized(RunData rundata)
777     {
778         return this.getAttribute("_display", "normal", rundata ).equals("minimized");
779     }
780 
781     /***
782      * Change the portlet visibility state ( minimized <-> normal )
783      *
784      * @param minimize True if the portlet change to minimized
785      * @param rundata The RunData object for the current request
786      */
787     public void setMinimized( boolean minimize, RunData rundata )
788     {
789         if( allowMinimize( rundata ) )
790         {
791             this.setAttribute("_display", minimize ? "minimized" : "normal", rundata );
792         }
793     }
794 
795     /***
796      * Returns TRUE if the title bar in should be displayed. The title bar includes
797      * the portlet title and action buttons.  This
798      *
799      * @param rundata The RunData object for the current request
800      */
801     public boolean isShowTitleBar(RunData rundata)
802     {
803         if (getPortletConfig()!=null)
804         {
805             // Parameter can exist in PSML or <portlet-entry>
806             return Boolean.valueOf(getPortletConfig().getInitParameter("_showtitlebar","true")).booleanValue();
807         }
808         return this.getAttribute("_showtitlebar", "true", rundata ).equals("true");
809     }
810     // utility methods
811 
812     /***
813      * Retrieve a portlet attribute from persistent storage
814      *
815      * @param attrName The attribute to retrieve
816      * @param attrDefValue The value if the attr doesn't exists
817      * @param rundata The RunData object for the current request
818      * @return The attribute value
819      */
820     public String getAttribute( String attrName, String attrDefValue, RunData rundata )
821     {
822         String attrValue = null ;
823 
824         PortletInstance instance = PersistenceManager.getInstance(this, rundata);
825         attrValue = instance.getAttribute(attrName, attrDefValue);
826 
827         return attrValue;
828     }
829 
830     /***
831      * Stores a portlet attribute in persistent storage
832      *
833      * @param attrName The attribute to retrieve
834      * @paarm attrValue The value to store
835      * @param rundata The RunData object for the current request
836      */
837     public void setAttribute( String attrName, String attrValue, RunData rundata )
838     {
839         try
840         {
841             PortletInstance instance = PersistenceManager.getInstance(this, rundata);
842             instance.setAttribute(attrName, attrValue);
843             PersistenceManager.store(instance);
844         }
845         catch (PortalPersistenceException e)
846         {
847             logger.error("Exception while setting attribute "+attrName+" for portlet "+getName(), e);
848         }
849     }
850 
851     /***
852      * Gets the portlet instance associated with this portlet.
853      *
854      * @param rundata The RunData object for the current request
855      * @return PortletInstance
856      */
857     public PortletInstance getInstance(RunData rundata)
858     {
859         return PersistenceManager.getInstance(this, rundata);
860     }
861 
862     //
863     // DST: Shouldn't getID and setID be deprecated and added to PortletInstance...
864     //
865     public String getID()
866     {
867         return id;
868     }
869 
870     public void setID(String id)
871     {
872         this.id = id;
873     }
874 
875     /***
876     * @return true if the portlet does its own customization
877     */
878     public boolean providesCustomization()
879     {
880         return false;
881     }
882 
883 }