/*
* Copyright 2000-2013 Enonic AS
* http://www.enonic.com/license
*/
package com.enonic.cms.core.structure.menuitem;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.jdom.Document;
import org.jdom.Element;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.enonic.vertical.engine.handlers.MenuHandler;
import com.enonic.cms.framework.util.LazyInitializedJDOMDocument;
import com.enonic.cms.core.CacheSettings;
import com.enonic.cms.core.CaseInsensitiveString;
import com.enonic.cms.core.Path;
import com.enonic.cms.core.content.ContentEntity;
import com.enonic.cms.core.content.ContentKey;
import com.enonic.cms.core.content.contenttype.ContentTypeEntity;
import com.enonic.cms.core.language.LanguageEntity;
import com.enonic.cms.core.security.group.GroupEntity;
import com.enonic.cms.core.security.group.GroupKey;
import com.enonic.cms.core.security.user.UserEntity;
import com.enonic.cms.core.structure.RunAsType;
import com.enonic.cms.core.structure.SiteEntity;
import com.enonic.cms.core.structure.menuitem.section.SectionContentEntity;
import com.enonic.cms.core.structure.menuitem.section.SectionContentTypeFilterEntity;
import com.enonic.cms.core.structure.page.PageEntity;
import com.enonic.cms.core.structure.page.template.PageTemplateEntity;
public class MenuItemEntity
implements Serializable
{
private static final long serialVersionUID = -7685294763479393483L;
private MenuItemKey key;
private String name;
private String displayName;
private Integer order;
private Date timestamp;
private String menuName;
private Boolean hidden;
private String description;
private Boolean noAuth;
private LazyInitializedJDOMDocument xmlData;
private String keywords;
private SiteEntity site;
private MenuItemType menuItemType;
private MenuItemEntity parent;
private String url;
private Boolean openNewWindowForURL;
private PageEntity page;
private UserEntity owner;
private UserEntity modifier;
private LanguageEntity language;
private Boolean shortcutForward;
private MenuItemEntity menuItemShortcut;
private Map<CaseInsensitiveString, MenuItemEntity> childrenMapByName = new LinkedHashMap<CaseInsensitiveString, MenuItemEntity>();
private Boolean section;
private Boolean orderedSection;
private RunAsType runAs;
private Set<SectionContentTypeFilterEntity> sectionContentTypeFilters = new LinkedHashSet<SectionContentTypeFilterEntity>();
private SortedSet<SectionContentEntity> sectionContents = new TreeSet<SectionContentEntity>( new SectionContentComparatorByOrder() );
private transient MenuItemData menuItemData;
/**
* PS: It is expected only one content in the set.
*/
private Set<ContentEntity> contents = new LinkedHashSet<ContentEntity>();
@SuppressWarnings({"MismatchedQueryAndUpdateOfCollection", "UnusedDeclaration"})
private Map<GroupKey, MenuItemAccessEntity> accesses;
/**
* For internal caching of the xml data document.
*/
private transient Document xmlDataAsJDOMDocument;
/**
* Default constructor.
*/
public MenuItemEntity()
{
}
/**
* Constructor that creates a new instance as a copy of another menu item.
*
* @param source The menu item to copy.
*/
public MenuItemEntity( MenuItemEntity source )
{
this();
key = source.key;
name = source.name;
order = source.order;
timestamp = source.timestamp;
menuName = source.menuName;
hidden = source.hidden;
description = source.description;
noAuth = source.noAuth;
xmlData = (LazyInitializedJDOMDocument) source.xmlData.clone();
keywords = source.keywords;
site = source.site;
menuItemType = source.menuItemType;
parent = source.parent;
url = source.url;
openNewWindowForURL = source.openNewWindowForURL;
page = source.page;
owner = source.owner;
modifier = source.modifier;
language = source.language;
shortcutForward = source.shortcutForward;
menuItemShortcut = source.menuItemShortcut;
childrenMapByName = Maps.newLinkedHashMap( source.childrenMapByName );
section = source.section;
orderedSection = source.orderedSection;
runAs = source.runAs;
sectionContentTypeFilters = Sets.newLinkedHashSet( source.sectionContentTypeFilters );
sectionContents = Sets.newTreeSet( source.sectionContents );
accesses = Maps.newHashMap( source.accesses );
}
public boolean isRenderable()
{
return menuItemType.isRenderable();
}
public MenuItemKey getKey()
{
return key;
}
public String getName()
{
return name;
}
public Integer getOrder()
{
return order;
}
public Date getTimestamp()
{
return timestamp;
}
public String getMenuName()
{
return menuName;
}
public Boolean getHidden()
{
return hidden;
}
public Boolean showInMenu()
{
return !hidden;
}
public String getDescription()
{
return description;
}
public Boolean getNoAuth()
{
return noAuth;
}
public boolean hasXmlData()
{
return xmlData != null;
}
public Document getXmlDataAsClonedJDomDocument()
{
if ( !hasXmlData() )
{
return null;
}
if ( xmlDataAsJDOMDocument == null )
{
xmlDataAsJDOMDocument = xmlData.getDocument();
}
return (Document) xmlDataAsJDOMDocument.clone();
}
public String getKeywords()
{
return keywords;
}
public SiteEntity getSite()
{
return site;
}
public MenuItemType getType()
{
return menuItemType;
}
public MenuItemEntity getParent()
{
return parent;
}
public String getUrl()
{
return url;
}
public boolean isOpenNewWindowForURL()
{
return openNewWindowForURL;
}
public PageEntity getPage()
{
return page;
}
public UserEntity getOwner()
{
return owner;
}
public UserEntity getModifier()
{
return modifier;
}
public LanguageEntity getLanguage()
{
return language;
}
public MenuItemEntity getMenuItemShortcut()
{
return menuItemShortcut;
}
public Boolean isShortcutForward()
{
return shortcutForward;
}
public boolean isSection()
{
return section != null && section;
}
public Boolean isOrderedSection()
{
return orderedSection;
}
public RunAsType getRunAs()
{
return runAs;
}
public boolean hasSectionContentTypeFilter()
{
return !this.sectionContentTypeFilters.isEmpty();
}
public Set<ContentTypeEntity> getAllowedSectionContentTypes()
{
final Set<ContentTypeEntity> contentTypeEntities = new LinkedHashSet<ContentTypeEntity>();
for ( SectionContentTypeFilterEntity sectionContentTypeFilter : sectionContentTypeFilters )
{
contentTypeEntities.add( sectionContentTypeFilter.getContentType() );
}
return contentTypeEntities;
}
public void setAllowedSectionContentTypes( Set<ContentTypeEntity> contentTypeFilter )
{
for ( ContentTypeEntity contentType : contentTypeFilter )
{
addAllowedSectionContentType( contentType );
}
}
public void addAllowedSectionContentType( ContentTypeEntity contentType )
{
final SectionContentTypeFilterEntity sectionContentTypeFilterEntity = new SectionContentTypeFilterEntity();
sectionContentTypeFilterEntity.setContentType( contentType );
sectionContentTypeFilterEntity.setSection( this );
this.sectionContentTypeFilters.add( sectionContentTypeFilterEntity );
}
public void addAllowedSectionContentType( final Collection<ContentTypeEntity> ctys )
{
for ( ContentTypeEntity cty : ctys )
{
addAllowedSectionContentType( cty );
}
}
public boolean supportsSectionContentType( ContentTypeEntity contentType )
{
return this.getAllowedSectionContentTypes().contains( contentType );
}
public void clearSectionContentTypes()
{
this.sectionContentTypeFilters.clear();
}
public Set<SectionContentTypeFilterEntity> getSectionContentTypeFilters()
{
return sectionContentTypeFilters;
}
public void setSectionContentTypeFilters( Set<SectionContentTypeFilterEntity> contentTypeFilter )
{
this.sectionContentTypeFilters = contentTypeFilter;
}
public void addSectionContent( SectionContentEntity sectionContent )
{
this.sectionContents.add( sectionContent );
}
public Set<SectionContentEntity> getSectionContents()
{
return sectionContents;
}
public void setSectionContent( Collection<SectionContentEntity> collection )
{
sectionContents.clear();
sectionContents.addAll( collection );
}
/**
* @return the children of this menu item in correct order.
*/
public Collection<MenuItemEntity> getChildren()
{
return Collections.unmodifiableCollection( childrenMapByName.values() );
}
/**
* Returns the descendants of this menu item to the given number of levels.
*
* @param levels The number of levels to go down recursively. A negative value, or a value of <code>0</code> will return an empty
* <code>List</code>.
* @return Descendants of this menu item.
*/
public List<MenuItemEntity> getDescendants( int levels )
{
List<MenuItemEntity> descendants = new ArrayList<MenuItemEntity>();
if ( levels > 0 )
{
doAddDescendantsRecursively( this, levels, descendants );
}
return descendants;
}
private void doAddDescendantsRecursively( MenuItemEntity parentMenuItem, int levels, List<MenuItemEntity> descendants )
{
if ( levels == 0 )
{
return;
}
Collection<MenuItemEntity> children = parentMenuItem.getChildren();
if ( children == null )
{
return;
}
for ( MenuItemEntity child : children )
{
descendants.add( child );
doAddDescendantsRecursively( child, levels - 1, descendants );
}
}
public List<MenuItemEntity> getDescendantSections( int levels )
{
List<MenuItemEntity> descendantSections = new ArrayList<MenuItemEntity>();
List<MenuItemEntity> descendantMenuItems = getDescendants( levels );
for ( MenuItemEntity menuItem : descendantMenuItems )
{
if ( menuItem.isSection() )
{
descendantSections.add( menuItem );
}
}
return descendantSections;
}
public void setKey( MenuItemKey key )
{
this.key = key;
}
public void setName( String name )
{
this.name = name;
}
public void setOrder( Integer order )
{
this.order = order;
}
public void setTimestamp( Date timestamp )
{
this.timestamp = timestamp;
}
public void setMenuName( String menuName )
{
this.menuName = menuName;
}
public void setHidden( Boolean hidden )
{
this.hidden = hidden;
}
public void setDescription( String description )
{
this.description = description;
}
public void setNoAuth( Boolean noAuth )
{
this.noAuth = noAuth;
}
public void setKeywords( String keywords )
{
this.keywords = keywords;
}
public void setSite( SiteEntity site )
{
this.site = site;
}
public void setType( MenuItemType type )
{
menuItemType = type;
}
public void setParent( MenuItemEntity parent )
{
this.parent = parent;
}
public void setUrl( String url )
{
this.url = url;
}
public void setOpenNewWindowForURL( boolean b )
{
openNewWindowForURL = b;
}
public void setPage( PageEntity page )
{
this.page = page;
}
public void setOwner( UserEntity owner )
{
this.owner = owner;
}
public void setModifier( UserEntity modifier )
{
this.modifier = modifier;
}
public void setLanguage( LanguageEntity language )
{
this.language = language;
}
public void setMenuItemShortcut( MenuItemEntity menuItemShortcut )
{
this.menuItemShortcut = menuItemShortcut;
}
public void setShortcutForward( Boolean shortcutForward )
{
this.shortcutForward = shortcutForward;
}
public void setSection( Boolean s )
{
if ( s == null )
{
section = false;
}
else
{
section = s;
}
if ( section )
{
if ( orderedSection == null )
{
orderedSection = false;
}
}
else
{
orderedSection = null;
}
}
public void setOrderedSection( Boolean ordered )
{
orderedSection = ordered;
}
public void setRunAs( RunAsType runAs )
{
this.runAs = runAs;
}
public void setXmlData( Document value )
{
if ( value == null )
{
this.xmlData = null;
}
else
{
this.xmlData = LazyInitializedJDOMDocument.parse( value );
}
// Invalidate the cached JDOM Document
this.xmlDataAsJDOMDocument = null;
// Invalidate the cached MenuData
this.menuItemData = null;
}
public void setContent( ContentEntity content )
{
this.contents.clear();
this.contents.add( content );
}
public void addChild( MenuItemEntity child )
{
CaseInsensitiveString childName = new CaseInsensitiveString( child.getName() );
if ( childrenMapByName.containsKey( childName ) )
{
throw new IllegalArgumentException( "Menu item already exist." );
}
this.childrenMapByName.put( childName, child );
}
public ContentEntity getContent()
{
if ( contents == null || contents.isEmpty() )
{
return null;
}
if ( contents.size() > 1 )
{
throw new IllegalStateException( "Unexpected number of contents (" + contents.size() + ") for menu item: " + getKey().toInt() );
}
return contents.iterator().next();
}
public boolean isRootPage()
{
return this.equals( getSite().getFrontPage() );
}
public boolean isErrorPage()
{
return this.equals( getSite().getErrorPage() );
}
public boolean isLoginPage()
{
return this.equals( getSite().getLoginPage() );
}
/**
* @return the level of the menu item. Counted from zero at top level.
*/
public int getLevel()
{
int level = 0;
MenuItemEntity current = this;
while ( current.getParent() != null )
{
current = current.getParent();
level++;
}
return level;
}
/**
* @return The breadcrumbspath of this menu item, with the top level parent as index 0.
*/
public List<MenuItemEntity> getMenuItemPath()
{
List<MenuItemEntity> path = new ArrayList<MenuItemEntity>();
addPath( path );
return Collections.unmodifiableList( path );
}
private void addPath( List<MenuItemEntity> path )
{
MenuItemEntity parent = getParent();
if ( parent != null )
{
parent.addPath( path );
}
path.add( this );
}
/**
* @return the path (from top level) of this menu item.
*/
public Path getPath()
{
return new Path( getPathAsStringCollection(), true );
}
/**
* @return the path (from top level) as a collection of strings.
*/
public Collection<String> getPathAsStringCollection()
{
List<String> pathElements = new ArrayList<String>();
MenuItemEntity curr = this;
do
{
pathElements.add( curr.getName() );
curr = curr.getParent();
}
while ( curr != null );
Collections.reverse( pathElements );
return pathElements;
}
/**
* @return the path (from top level) of this menu item.
*/
public String getPathAsString()
{
List<MenuItemEntity> menuItems = getMenuItemPath();
StringBuffer pathString = new StringBuffer( 25 * menuItems.size() );
pathString.append( "/" );
for ( int i = 0; i < menuItems.size(); i++ )
{
MenuItemEntity mi = menuItems.get( i );
pathString.append( mi.getName() );
if ( i < menuItems.size() - 1 )
{
pathString.append( "/" );
}
}
return pathString.toString();
}
/**
* Finds and returns a above parent of this menu item at the given level.
*
* @param level The number of levels up to the parent.
* @return The specified parent.
*/
public MenuItemEntity getParentAtLevel( int level )
{
if ( level < 0 )
{
throw new IllegalArgumentException( "Given level must be zero or more: " + level );
}
List<MenuItemEntity> path = getMenuItemPath();
if ( level >= path.size() - 1 )
{
throw new IllegalArgumentException( "Given level must be below this menu items's level:" + level );
}
return path.get( level );
}
public MenuItemEntity getTopLevelMenuItem()
{
MenuItemEntity current = this;
while ( current.getParent() != null )
{
current = current.getParent();
}
return current;
}
public boolean isAtTopLevel()
{
return getParent() == null;
}
public MenuItemEntity getChildByName( String name )
{
return childrenMapByName.get( new CaseInsensitiveString( name ) );
}
public MenuItemAccessEntity getAccess( GroupKey groupKey )
{
return accesses.get( groupKey );
}
/**
* NB! This method will not check access thru groupĀ“s memberhips.
*/
public boolean hasAccess( GroupEntity group, MenuItemAccessType typeOfAccess )
{
if ( group == null )
{
throw new IllegalArgumentException( "Given group cannot be null" );
}
MenuItemAccessEntity menuItemAccess = accesses.get( group.getGroupKey() );
if ( menuItemAccess == null )
{
return false;
}
switch ( typeOfAccess )
{
case READ:
return menuItemAccess.isReadAccess();
case ADD:
return menuItemAccess.isAddAccess();
case ADMINISTRATE:
return menuItemAccess.isAdminAccess();
case CREATE:
return menuItemAccess.isCreateAccess();
case DELETE:
return menuItemAccess.isDeleteAccess();
case PUBLISH:
return menuItemAccess.isPublishAccess();
case UPDATE:
return menuItemAccess.isUpdateAccess();
}
return false;
}
public CacheSettings getCacheSettings( int defaultSecondsToLive, PageTemplateEntity pageTemplate )
{
if ( !hasXmlData() )
{
return new CacheSettings( false, CacheSettings.TYPE_DEFAULT, 0 );
}
Element dataEl = getXmlDataAsClonedJDomDocument().getRootElement();
String cachedisabledString = dataEl.getAttributeValue( "cachedisabled" );
String cachetypeString = dataEl.getAttributeValue( "cachetype" );
String mincachetimeString = dataEl.getAttributeValue( "mincachetime", String.valueOf( defaultSecondsToLive ) );
int secondsToLive = Integer.valueOf( mincachetimeString );
boolean cacheMenuItem = !Boolean.valueOf( cachedisabledString );
return new CacheSettings( cacheMenuItem, cachetypeString, secondsToLive );
}
public Element getDocumentElementAsClonedJDOMElement()
{
if ( !hasXmlData() )
{
return null;
}
Element dataEl = getXmlDataAsClonedJDomDocument().getRootElement();
Element documentEl = dataEl.getChild( "document" );
if ( documentEl == null )
{
return null;
}
return (Element) documentEl.detach();
}
public boolean isParentOf( MenuItemEntity potentialChild )
{
return potentialChild.hasAsParent( this );
}
public boolean hasAsParent( MenuItemEntity potentialParent )
{
MenuItemEntity currentParent = this.getParent();
while ( currentParent != null )
{
if ( potentialParent.getKey().toInt() == currentParent.getKey().toInt() )
{
return true;
}
currentParent = currentParent.getParent();
}
return false;
}
@Override
public boolean equals( Object o )
{
if ( this == o )
{
return true;
}
if ( !( o instanceof MenuItemEntity ) )
{
return false;
}
MenuItemEntity that = (MenuItemEntity) o;
return !( getKey() != null ? !getKey().equals( that.getKey() ) : that.getKey() != null );
}
public int hashCode()
{
return new HashCodeBuilder( 143, 631 ).append( getKey() ).toHashCode();
}
public String toString()
{
StringBuffer s = new StringBuffer();
s.append( "key=" ).append( getKey() ).append( ", name='" ).append( getName() ).append( "'" );
return s.toString();
}
public UserEntity resolveRunAsUser( UserEntity currentUser, boolean doFirstLevelCheckOnPageTemplate, MenuHandler menuHandler )
{
if ( currentUser.isAnonymous() )
{
// Anonymous user cannot run as any other user
return currentUser;
}
RunAsType runAsType = getRunAs();
UserEntity siteRunAsUser = menuHandler.getRunAsUserForSite( getSite().getKey() );
if ( runAsType.equals( RunAsType.PERSONALIZED ) )
{
return currentUser;
}
else if ( runAsType.equals( RunAsType.DEFAULT_USER ) )
{
if ( siteRunAsUser != null )
{
return siteRunAsUser;
}
return null;
}
else if ( runAsType.equals( RunAsType.INHERIT ) )
{
if ( doFirstLevelCheckOnPageTemplate && getPage() != null )
{
PageTemplateEntity pageTemplate = getPage().getTemplate();
if ( pageTemplate != null )
{
UserEntity runAsUser = pageTemplate.resolveRunAsUser( currentUser, menuHandler );
if ( runAsUser != null )
{
return runAsUser;
}
}
}
MenuItemEntity parent = getParent();
if ( parent != null )
{
return parent.resolveRunAsUser( currentUser, false, menuHandler );
}
else
{
return siteRunAsUser != null ? siteRunAsUser : currentUser;
}
}
else
{
throw new IllegalArgumentException( "Unsopported runAsType: " + runAsType );
}
}
public MenuItemEntity getClosestParentThatIsNotSection()
{
MenuItemEntity parent = getParent();
if ( parent == null )
{
return null;
}
while ( parent != null && parent.isSection() )
{
parent = parent.getParent();
}
return parent;
}
private MenuItemData getMenuItemData()
{
if ( menuItemData == null )
{
if ( xmlData != null )
{
menuItemData = new MenuItemData( this.getXmlDataAsClonedJDomDocument() );
}
else
{
menuItemData = new MenuItemData();
}
}
return menuItemData;
}
public void removeRequestParameters()
{
MenuItemData data = getMenuItemData();
data.removeRequestParameters();
setXmlData( data.getJDOMDocument() );
}
public void addRequestParameter( final String name, final String value, final String override )
{
MenuItemData data = getMenuItemData();
data.addRequestParameter( name, value, override );
setXmlData( data.getJDOMDocument() );
}
public boolean getIncludeRequestParameters() {
return getMenuItemData().getIncludeRequestParameters();
}
public Map<String, MenuItemRequestParameter> getRequestParameters()
{
return getMenuItemData().getRequestParameters();
}
public MenuItemRequestParameter getRequestParameter( final String name )
{
return getMenuItemData().getRequestParameter( name );
}
public String getRequestParameterValue( final String name )
{
MenuItemRequestParameter menuItemRequestParameter = getMenuItemData().getRequestParameter( name );
if ( menuItemRequestParameter == null )
{
return null;
}
return menuItemRequestParameter.getValue();
}
public Document getMenuDataJDOMDocument()
{
return getMenuItemData().getJDOMDocument();
}
public Boolean getCacheDisabled()
{
return getMenuItemData().getCacheDisabled();
}
public String getCacheType()
{
return getMenuItemData().getCacheType();
}
public String getDisplayName()
{
return displayName;
}
public void setDisplayName( String displayName )
{
this.displayName = displayName;
}
/**
* This method should only be called on section type menu items.
*
* @return The timestamp of the last time the section content was updated. If the section has no content, <code>new Date(0)</code> will be returned.
*/
public Date getLastUpdatedSectionContentTimestamp()
{
assert isSection() : "This method is only valid for section type menu items.";
Date newest = new Date( 0 );
for ( SectionContentEntity sectionContent : getSectionContents() )
{
if ( sectionContent.getTimestamp().after( newest ) )
{
newest = sectionContent.getTimestamp();
}
}
return newest;
}
public SectionContentEntity getSectionContent( ContentKey contentKey )
{
assert isSection() : "This method is only valid for section type menu items.";
for ( SectionContentEntity sectionContent : getSectionContents() )
{
if ( sectionContent.getContent().getKey().equals( contentKey ) )
{
return sectionContent;
}
}
return null;
}
}