Coverage report

  %line %branch
org.apache.jetspeed.decoration.DecorationFactoryImpl
0% 
0% 

 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.decoration;
 18  
 
 19  
 import java.io.IOException;
 20  
 import java.io.InputStream;
 21  
 import java.util.Collections;
 22  
 import java.util.HashMap;
 23  
 import java.util.HashSet;
 24  
 import java.util.Iterator;
 25  
 import java.util.LinkedList;
 26  
 import java.util.List;
 27  
 import java.util.Locale;
 28  
 import java.util.Map;
 29  
 import java.util.Properties;
 30  
 import java.util.Set;
 31  
 
 32  
 import javax.servlet.ServletContext;
 33  
 
 34  
 import org.apache.commons.logging.Log;
 35  
 import org.apache.commons.logging.LogFactory;
 36  
 import org.apache.jetspeed.components.portletregistry.PortletRegistry;
 37  
 import org.apache.jetspeed.decoration.caches.SessionPathResolverCache;
 38  
 import org.apache.jetspeed.om.common.portlet.MutablePortletApplication;
 39  
 import org.apache.jetspeed.om.common.portlet.PortletDefinitionComposite;
 40  
 import org.apache.jetspeed.om.page.Fragment;
 41  
 import org.apache.jetspeed.om.page.Page;
 42  
 import org.apache.jetspeed.request.RequestContext;
 43  
 import org.apache.jetspeed.util.Path;
 44  
 import org.apache.jetspeed.desktop.JetspeedDesktop;
 45  
 import org.springframework.web.context.ServletContextAware;
 46  
 
 47  
 /**
 48  
  *
 49  
  * @author <href a="mailto:weaver@apache.org">Scott T. Weaver</a>
 50  
  * @author <a href="mailto:smilek@apache.org">Steve Milek</a>
 51  
  * @see org.apache.jetspeed.decoration.DecorationFactory
 52  
  */
 53  
 public class DecorationFactoryImpl implements DecorationFactory, ServletContextAware
 54  
 {
 55  0
     private static final Log log = LogFactory.getLog(DecorationFactoryImpl.class);
 56  
 
 57  
     private final Path decorationsPath;
 58  
     private final Path portletDecorationsPath;
 59  
     private final Path layoutDecorationsPath;
 60  
     private final String portletDecorationsPathStr;
 61  
     private final String layoutDecorationsPathStr;
 62  
     
 63  
     private final ResourceValidator validator;
 64  
     private final PortletRegistry registry;
 65  
     
 66  
     private ServletContext servletContext;
 67  
 
 68  0
     private String defaultDesktopLayoutDecoration = null;
 69  0
     private String defaultDesktopPortletDecoration = null;
 70  
     
 71  0
     private Set layoutDecorationsDir = Collections.EMPTY_SET;
 72  0
     private Set portletDecorationsDir = Collections.EMPTY_SET;
 73  0
     private Set desktopLayoutDecorationsDir = Collections.EMPTY_SET;
 74  0
     private Set desktopPortletDecorationsDir = Collections.EMPTY_SET;
 75  
     
 76  0
     private Set layoutDecorationsList = Collections.EMPTY_SET;
 77  0
     private Set portletDecorationsList = Collections.EMPTY_SET;
 78  0
     private Set desktopLayoutDecorationsList = Collections.EMPTY_SET;
 79  0
     private Set desktopPortletDecorationsList = Collections.EMPTY_SET;
 80  
     
 81  0
     private Map portletDecoratorProperties = new HashMap();
 82  0
     private Map layoutDecoratorProperties = new HashMap();
 83  
 
 84  
     public DecorationFactoryImpl( String decorationsPath, 
 85  
                                   ResourceValidator validator )
 86  
     {
 87  0
         this( null, decorationsPath, validator );
 88  0
     }
 89  
 
 90  
     public DecorationFactoryImpl( PortletRegistry registry,
 91  
             String decorationsPath, 
 92  
             ResourceValidator validator )
 93  0
     {
 94  0
         this.registry =  registry;
 95  0
         this.decorationsPath = new Path( decorationsPath );
 96  0
         this.layoutDecorationsPath = getBasePath( Fragment.LAYOUT );
 97  0
         this.layoutDecorationsPathStr = class="keyword">this.layoutDecorationsPath.toString();
 98  0
         this.portletDecorationsPath = getBasePath( Fragment.PORTLET );
 99  0
         this.portletDecorationsPathStr = class="keyword">this.portletDecorationsPath.toString();
 100  0
         this.validator = validator;
 101  0
     }
 102  
         
 103  
     public ResourceValidator getResourceValidator()
 104  
     {
 105  0
         return validator;
 106  
     }    
 107  
 
 108  
     public Theme getTheme( Page page, RequestContext requestContext )
 109  
     {
 110  0
         return new PageTheme(page, this, requestContext);
 111  
     }
 112  
     
 113  
     public Decoration getDecoration( Page page, Fragment fragment, RequestContext requestContext )
 114  
     {
 115  0
         String decorationName = getDefaultDecorationName( fragment, page );
 116  
         Decoration decoration;
 117  
 
 118  
         // use layout decoration for top level layout root fragments
 119  
         //    and use portlet decoration for all other fragments
 120  0
         if ( fragment.getType().equals( Fragment.LAYOUT ) )
 121  
         {
 122  0
             decoration = getLayoutDecoration( decorationName, requestContext );
 123  
         }
 124  
         else
 125  
         {
 126  0
             decoration = getPortletDecoration( decorationName, requestContext );
 127  
         }
 128  
 
 129  0
         if ( isDesktopEnabled( requestContext ) )
 130  
         {   // assure that selected decoration supports /desktop
 131  
             //    otherwise get default desktop decoration for fragment type
 132  0
             if ( decoration == null || ! decoration.supportsDesktop() )
 133  
             {
 134  0
                 String defaultDecoration = null;
 135  0
                 if (fragment.getType().equals( Fragment.LAYOUT ) )
 136  
                 {
 137  0
                     defaultDecoration = getDefaultDesktopLayoutDecoration();
 138  0
                     decoration = getLayoutDecoration( defaultDecoration, requestContext );
 139  
                 }
 140  
                 else
 141  
                 {
 142  0
                     defaultDecoration = getDefaultDesktopPortletDecoration();
 143  0
                     decoration = getPortletDecoration( defaultDecoration, requestContext );
 144  
                 }
 145  0
                 if ( decoration == null )
 146  
                 {
 147  0
                     String errMsg = "Cannot locate default desktop " + fragment.getType() + " decoration " + ( defaultDecoration == null ? "null" : ("\"" + defaultDecoration + "\"") ) + " (decoration " + ( defaultDecoration == null ? "null" : ("\"" + decorationName + "\"") ) + " specified for page could either not be located or does not support desktop). No desktop compatible " + fragment.getType() + " decoration is available.";
 148  0
                     log.warn( errMsg );
 149  
                 }
 150  
             }
 151  
         }
 152  0
         return decoration;
 153  
     }
 154  
 
 155  
     public PortletDecoration getPortletDecoration( String name, RequestContext requestContext )
 156  
     {
 157  0
         Path basePath = getPortletDecorationBasePath( name );
 158  0
         Path baseClientPath = createClientPath( name, basePath, requestContext, Fragment.PORTLET );
 159  0
         Properties configuration = getConfiguration( name, Fragment.PORTLET );
 160  0
         SessionPathResolverCache sessionPathResolver = new SessionPathResolverCache( requestContext.getRequest().getSession() );
 161  0
         return new PortletDecorationImpl( configuration, validator, basePath, baseClientPath, sessionPathResolver );
 162  
     }
 163  
 
 164  
     public LayoutDecoration getLayoutDecoration( String name, RequestContext requestContext )
 165  
     {
 166  0
         Path basePath = getLayoutDecorationBasePath( name );
 167  0
         Path baseClientPath = createClientPath( name, basePath, requestContext, Fragment.LAYOUT );
 168  0
         Properties configuration = getConfiguration( name, Fragment.LAYOUT );
 169  0
         SessionPathResolverCache sessionPathResolver = new SessionPathResolverCache( requestContext.getRequest().getSession() );
 170  0
         return new LayoutDecorationImpl( configuration, validator, basePath, baseClientPath, sessionPathResolver );
 171  
     }    
 172  
     
 173  
     public boolean isDesktopEnabled( RequestContext requestContext )
 174  
     {
 175  0
         Boolean desktopEnabled = (Boolean)requestContext.getAttribute( JetspeedDesktop.DESKTOP_ENABLED_REQUEST_ATTRIBUTE );
 176  0
         return ( desktopEnabled != null && desktopEnabled.booleanValue() ? true : false );
 177  
     }
 178  
 
 179  
     public void setServletContext(ServletContext servletContext)
 180  
     {
 181  0
         this.servletContext = servletContext;
 182  
 
 183  0
     }
 184  
 
 185  
     /**
 186  
      * Gets the configuration (decorator.properties) object for the decoration.
 187  
      * @param name Name of the Decoration.
 188  
      * @return <code>java.util.Properties</code> representing the configuration
 189  
      * object.
 190  
      */
 191  
     public Properties getConfiguration( String name, String type )
 192  
     {
 193  0
         Properties props = null;
 194  0
         if ( type.equals( Fragment.PORTLET ) )
 195  
         {
 196  0
             props = (Properties)this.portletDecoratorProperties.get( name );
 197  
             
 198  
         }
 199  
         else
 200  
         {
 201  0
             props = (Properties)this.layoutDecoratorProperties.get( name );
 202  
         }        
 203  0
         if ( props != null )
 204  
         {
 205  0
             return props;
 206  
         }
 207  
         
 208  0
         props = new Properties();
 209  0
         InputStream is = null;
 210  
         
 211  
         // load Decoration.CONFIG_FILE_NAME (decorator.properties)
 212  
         try
 213  
         {
 214  0
             is = servletContext.getResourceAsStream( decorationsPath + "/" + type + "/" + name + "/" + Decoration.CONFIG_FILE_NAME );
 215  0
             if (is != null)
 216  
             {                
 217  0
                 props.load( is );
 218  
             }
 219  
             else
 220  
             {
 221  0
                 log.warn( "Could not locate the " + Decoration.CONFIG_FILE_NAME + " configuration file for decoration \"" + name + "\".  This decoration may not exist." );
 222  0
                 props.setProperty( "id", name );
 223  0
                 props.setProperty( "name", name );
 224  
             }
 225  0
         }
 226  0
         catch ( Exception e )
 227  
         {
 228  0
             log.warn( "Failed to load the " + Decoration.CONFIG_FILE_NAME + " configuration file for decoration \"" + name + "\".", e );
 229  0
             props.setProperty( "id", name );
 230  0
             props.setProperty( "name", name );
 231  0
         }
 232  
         finally
 233  
         {
 234  0
             if ( is != null )
 235  
             {
 236  
                 try
 237  
                 {
 238  0
                     is.close();
 239  
                 }
 240  0
                 catch (IOException e)
 241  
                 {
 242  0
                     log.warn("Failed to close decoration configuration.", e);
 243  0
                 }
 244  
             }
 245  0
             String decorationIdPropVal = props.getProperty( "id" );
 246  0
             String decorationNamePropVal = props.getProperty( "name" );
 247  0
             if ( decorationIdPropVal == null )
 248  
             {
 249  0
                 if ( decorationNamePropVal != null )
 250  
                 {
 251  0
                     decorationIdPropVal = decorationNamePropVal;
 252  
                 }
 253  
                 else
 254  
                 {
 255  0
                     decorationIdPropVal = name;
 256  
                 }
 257  0
                 props.setProperty( "id", decorationIdPropVal );
 258  
             }
 259  
             
 260  0
             if ( decorationNamePropVal == null )
 261  
             {
 262  0
                 props.setProperty( "name", decorationIdPropVal );
 263  
             }
 264  0
         }
 265  
         
 266  
         // load Decoration.CONFIG_DESKTOP_FILE_NAME (decoratordesktop.properties)
 267  
         try
 268  
         {
 269  0
             is = servletContext.getResourceAsStream( decorationsPath + "/" + type + "/" + name + "/" + Decoration.CONFIG_DESKTOP_FILE_NAME );
 270  0
             if ( is != null )
 271  
             {                
 272  0
                 props.load( is );
 273  0
                 if ( props.getProperty( Decoration.DESKTOP_SUPPORTED_PROPERTY ) == null )
 274  
                 {
 275  0
                     props.setProperty( Decoration.DESKTOP_SUPPORTED_PROPERTY, "true" );
 276  
                 }
 277  
             }
 278  
             else
 279  
             {
 280  0
                 log.debug( "Could not locate the " + Decoration.CONFIG_DESKTOP_FILE_NAME + " configuration file for decoration \"" + name + "\".  This decoration may not exist." );
 281  
             }
 282  
         }
 283  0
         catch ( Exception e )
 284  
         {
 285  0
             log.warn( "Failed to load the " + Decoration.CONFIG_DESKTOP_FILE_NAME + " configuration file for decoration \"" + name + "\".", e );
 286  
         }
 287  
         finally
 288  
         {
 289  0
             if ( is != null )
 290  
             {
 291  
                 try
 292  
                 {
 293  0
                     is.close();
 294  
                 }
 295  0
                 catch ( IOException e )
 296  
                 {
 297  0
                     log.warn( "Failed to close decoration desktop configuration.", e );
 298  0
                 }
 299  
             }
 300  0
             if ( props.getProperty( Decoration.DESKTOP_SUPPORTED_PROPERTY ) == null )
 301  
             {
 302  0
                 props.setProperty( Decoration.DESKTOP_SUPPORTED_PROPERTY, "false" );
 303  
             }
 304  
         }
 305  
 
 306  0
         if ( type.equals( Fragment.PORTLET ) )
 307  
         {
 308  0
             this.portletDecoratorProperties.put( name, props );
 309  
         }
 310  
         else
 311  
         {
 312  0
             this.layoutDecoratorProperties.put( name, props );
 313  
         }
 314  0
         return props;
 315  
     }
 316  
     
 317  
     /**
 318  
      * Creates a <code>org.apache.jetspeed.util.Path</code> object based
 319  
      * off of the user's client browser and locale.
 320  
      *
 321  
      * @param name Decroator's name
 322  
      * @param requestContext  Current portal request.
 323  
      * @param decorationType Type of decoration, either <code>layout</code>
 324  
      * or <code>portlet</code>
 325  
      * @return
 326  
      *
 327  
      * @see Path
 328  
      * @see RequestContext
 329  
      */
 330  
     protected Path createClientPath( String name, RequestContext requestContext, String decorationType )
 331  
     {
 332  0
         return createClientPath( name, null, requestContext, decorationType );
 333  
     }   
 334  
     
 335  
     private Path createClientPath( String name, Path basePath, RequestContext requestContext, String decorationType )
 336  
     {
 337  0
         if ( basePath == null )
 338  0
             basePath = getBasePath( name, decorationType );
 339  0
         String mediaType = requestContext.getMediaType();
 340  0
         Locale locale = requestContext.getLocale();
 341  0
         String language = locale.getLanguage();
 342  0
         String country = locale.getCountry();
 343  0
         String variant = locale.getVariant();
 344  
 
 345  0
         basePath.addSegment( mediaType ).addSegment( language );
 346  
 
 347  0
         if ( country != null )
 348  
         {
 349  0
             basePath.addSegment( country );
 350  
         }
 351  
 
 352  0
         if (variant != null)
 353  
         {
 354  0
             basePath.addSegment( variant );
 355  
         }
 356  0
         return basePath;
 357  
     }
 358  
 
 359  
     /**
 360  
      * Returns a the default decoration name for the specific Fragment type.
 361  
      *
 362  
      * @param fragment Fragment whose default decroation has been requested
 363  
      * @param page Page this fragment belongs to.
 364  
      * @return Default decorator name.
 365  
      *
 366  
      * @see Page
 367  
      * @see Fragment
 368  
      */
 369  
     protected String getDefaultDecorationName(Fragment fragment, Page page)
 370  
     {
 371  
         // get specified decorator
 372  0
         String decoration = fragment.getDecorator();
 373  0
         if (decoration == null)
 374  
         {
 375  0
             if (fragment.getType().equals(Fragment.LAYOUT))
 376  
             {
 377  0
                 if (fragment.equals(page.getRootFragment()))
 378  
                 {
 379  
                     // use page specified layout decorator name
 380  0
                     decoration = page.getEffectiveDefaultDecorator(Fragment.LAYOUT);
 381  
                 }
 382  
                 else
 383  
                 {
 384  
                     // use default nested layout portlet decorator name
 385  0
                     decoration = DEFAULT_NESTED_LAYOUT_PORTLET_DECORATOR;
 386  
                 }
 387  
             }
 388  
             else
 389  
             {
 390  
                 // use page specified default portlet decorator name
 391  0
                 decoration = page.getEffectiveDefaultDecorator(Fragment.PORTLET);
 392  
             }
 393  
         }
 394  
 
 395  0
         return decoration;
 396  
     }
 397  
 
 398  
     public void clearCache(RequestContext requestContext)
 399  
     {
 400  0
         new SessionPathResolverCache(requestContext.getRequest().getSession()).clear();
 401  0
     }
 402  
 
 403  
     protected Path getBasePath( String decorationType )
 404  
     {
 405  0
         return decorationsPath.addSegment(decorationType);
 406  
     }
 407  
     
 408  
     protected Path getBasePath( String name, String decorationType )
 409  
     {
 410  0
         return decorationsPath.addSegment(decorationType).addSegment(name);
 411  
     }
 412  
     
 413  
     protected Path getLayoutDecorationBasePath( String name )
 414  
     {
 415  0
         return layoutDecorationsPath.addSegment(name);
 416  
     }
 417  
     protected Path getPortletDecorationBasePath( String name )
 418  
     {
 419  0
         return portletDecorationsPath.addSegment(name);
 420  
     }
 421  
     
 422  
     public String getLayoutDecorationsBasePath()
 423  
     {
 424  0
         return this.layoutDecorationsPathStr;
 425  
     }
 426  
     
 427  
     public String getPortletDecorationsBasePath()
 428  
     {
 429  0
         return this.portletDecorationsPathStr;
 430  
     }
 431  
 
 432  
     /**
 433  
      * Get the portal-wide list of page decorations.
 434  
      *
 435  
      * @return A list of page decorations of type <code>Decoration</code>
 436  
      */
 437  
     public Set getPageDecorations( RequestContext request )
 438  
     {
 439  0
         Set decorations = servletContext.getResourcePaths( decorationsPath.toString() + "/layout" );
 440  0
         if( ! layoutDecorationsDir.equals( decorations ) )
 441  
         {
 442  0
             layoutDecorationsList = getListing( decorations, Decoration.CONFIG_FILE_NAME );
 443  0
             layoutDecorationsDir = decorations;
 444  
             
 445  
         }
 446  0
         return layoutDecorationsList;
 447  
     }
 448  
     
 449  
     /**
 450  
      * Get the portal-wide list of available desktop page decorations.
 451  
      * 
 452  
      * @return A list of desktop skins of type <code>String</code>
 453  
      */        
 454  
     public Set getDesktopPageDecorations( RequestContext request )
 455  
     {
 456  0
         Set decorations = servletContext.getResourcePaths( decorationsPath.toString() + "/layout" );
 457  0
         if( ! desktopLayoutDecorationsDir.equals( decorations ) )
 458  
         {
 459  0
             desktopLayoutDecorationsList = getListing( decorations, Decoration.CONFIG_DESKTOP_FILE_NAME );
 460  0
             desktopLayoutDecorationsDir = decorations;
 461  
             
 462  
         }
 463  0
         return desktopLayoutDecorationsList;
 464  
     }
 465  
 
 466  
     /**
 467  
      * Get the portal-wide list of portlet decorations.
 468  
      *
 469  
      * @return A list of portlet decorations of type <code>String</code>
 470  
      */
 471  
     public Set getPortletDecorations( RequestContext request )
 472  
     {
 473  0
         Set decorations = servletContext.getResourcePaths( decorationsPath.toString() + "/portlet" );
 474  0
         if( ! portletDecorationsDir.equals( decorations ) )
 475  
         {
 476  0
             portletDecorationsList = getListing( decorations, Decoration.CONFIG_FILE_NAME );
 477  0
             portletDecorationsDir = decorations;
 478  
             
 479  
         }
 480  0
         return portletDecorationsList;
 481  
     }
 482  
     
 483  
     /**
 484  
      * Get the portal-wide list of desktop portlet decorations.
 485  
      *
 486  
      * @return A list of portlet decorations of type <code>String</code>
 487  
      */
 488  
     public Set getDesktopPortletDecorations( RequestContext request )
 489  
     {
 490  0
         Set decorations = servletContext.getResourcePaths( decorationsPath.toString() + "/portlet" );
 491  0
         if( ! desktopPortletDecorationsDir.equals( decorations ) )
 492  
         {
 493  0
             desktopPortletDecorationsList = getListing( decorations, Decoration.CONFIG_DESKTOP_FILE_NAME );
 494  0
             desktopPortletDecorationsDir = decorations;
 495  
             
 496  
         }
 497  0
         return desktopPortletDecorationsList;
 498  
     }
 499  
     
 500  
     /**
 501  
      * Get the portal-wide list of available layouts.
 502  
      *
 503  
      * @return A list of layout portlets of type <code>PortletDefinitionComposite</code>
 504  
      */
 505  
     public List getLayouts( RequestContext request )
 506  
     {
 507  0
         List list = new LinkedList();
 508  0
         Iterator portlets = registry.getAllPortletDefinitions().iterator();
 509  0
         while ( portlets.hasNext() )
 510  
         {
 511  0
             PortletDefinitionComposite portlet = (PortletDefinitionComposite)portlets.next();
 512  0
             MutablePortletApplication muta = (MutablePortletApplication)portlet.getPortletApplicationDefinition();
 513  0
             String appName = muta.getName();
 514  0
             if ( appName == null )
 515  0
                 continue;
 516  0
             if ( ! appName.equals( "jetspeed-layouts" ) )
 517  0
                 continue;
 518  
 
 519  0
             String uniqueName = appName + "::" + portlet.getName();
 520  0
             list.add( new LayoutInfoImpl( uniqueName,
 521  
                       portlet.getDisplayNameText( request.getLocale() ),
 522  
                       portlet.getDescriptionText( request.getLocale() ) ) );
 523  
 
 524  0
         }
 525  0
         return list;
 526  
     }
 527  
     
 528  
     protected Set getListing(Set rawList, String propsFile)
 529  
     {
 530  0
         Iterator itr = rawList.iterator();
 531  0
         Set filteredList = new HashSet();
 532  0
         while(itr.hasNext())
 533  
         {
 534  0
             Path path = new Path((String) itr.next());
 535  0
             if(path.getFileName() == null && validator.resourceExists(path.toString() + propsFile))
 536  
             {
 537  0
                 int offset = path.length() - 1;
 538  0
                 filteredList.add(path.getSegment(offset));
 539  
             }
 540  0
         }
 541  0
         return filteredList;
 542  
     }
 543  
 
 544  
     public String getDefaultDesktopLayoutDecoration()
 545  
     {
 546  0
         synchronized ( this )
 547  
         {
 548  0
             return this.defaultDesktopLayoutDecoration;
 549  0
         }
 550  
     }
 551  
     public void setDefaultDesktopLayoutDecoration( String newOne )
 552  
     {
 553  0
         synchronized ( this )
 554  
         {
 555  0
             this.defaultDesktopLayoutDecoration = newOne;
 556  0
         }
 557  0
     }
 558  
     public String getDefaultDesktopPortletDecoration()
 559  
     {
 560  0
         synchronized ( this )
 561  
         {
 562  0
             return this.defaultDesktopPortletDecoration;
 563  0
         }
 564  
     }
 565  
     public void setDefaultDesktopPortletDecoration( String newOne )
 566  
     {
 567  0
         synchronized ( this )
 568  
         {
 569  0
             this.defaultDesktopPortletDecoration = newOne;
 570  0
         }
 571  0
     }
 572  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.