/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.core.content.contentdata.custom; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.builder.HashCodeBuilder; import com.enonic.cms.core.content.ContentKey; import com.enonic.cms.core.content.binary.BinaryDataKey; import com.enonic.cms.core.content.contentdata.MissingRequiredContentDataException; import com.enonic.cms.core.content.contentdata.custom.stringbased.HtmlAreaDataEntry; import com.enonic.cms.core.content.contenttype.CtySet; import com.enonic.cms.core.content.contenttype.CtySetConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.DataEntryConfig; public abstract class AbstractDataEntrySet implements DataEntrySet { protected String name; protected String xpath; protected CtySet config; protected List<DataEntry> entries = new ArrayList<DataEntry>(); protected List<DataEntrySet> dataEntrySetEntries = new ArrayList<DataEntrySet>(); protected Map<String, DataEntry> entryMap = new HashMap<String, DataEntry>(); protected DataEntryType type; /** * A shallow copy constructor. */ protected AbstractDataEntrySet( AbstractDataEntrySet other ) { this.name = other.name; this.xpath = other.xpath; this.config = other.config; this.entries = other.entries; this.dataEntrySetEntries = other.dataEntrySetEntries; this.entryMap = other.entryMap; this.type = other.type; } protected AbstractDataEntrySet( String name, DataEntryType type, String xpath ) { this.name = name; this.type = type; this.xpath = xpath; } public String getName() { return name; } public String getXPath() { return xpath; } public DataEntryType getType() { return type; } public void setConfig( CtySet value ) { this.config = value; } public CtySet getConfig() { return config; } public void add( DataEntry entry ) { if ( entry instanceof AbstractDataEntrySet ) { CtySetConfig setConfig = getSetConfig( entry.getName() ); if ( setConfig == null ) { throw new IllegalArgumentException( "No configuration for entry with name: " + entry.getName() ); } AbstractDataEntrySet dataEntrySet = (AbstractDataEntrySet) entry; dataEntrySet.setConfig( setConfig ); dataEntrySetEntries.add( dataEntrySet ); } else if ( entry instanceof AbstractInputDataEntry ) { validateEntry( (AbstractInputDataEntry) entry ); } else { throw new IllegalArgumentException( "Unknown type of entry: " + entry.getClass().getName() ); } entries.add( entry ); entryMap.put( entry.getName(), entry ); } private void validateEntry( AbstractInputDataEntry entry ) { DataEntryConfig inputConfig = getInputConfigByRelateiveXPath( entry.getXPath() ); if ( inputConfig == null ) { throw new IllegalArgumentException( "No configuration for entry '" + entry.getName() + "' with xpath: " + entry.getXPath() ); } // Validate that entry is of same type as when you lookup in contenttype config by xpath if ( !inputConfig.getType().isCompatible( entry.getType() ) ) { throw new IllegalArgumentException( "Configuration for entry '" + entry.getName() + "' not compatible. Expected " + inputConfig.getType().getCompatibleDataEntryTypesAsCommaSeparatedString() + ", got " + entry.getType() + "." ); } } public int numberOfEntries() { return entries.size(); } public List<DataEntry> getEntries() { return entries; } public boolean hasValue() { /* Groups has allways value */ return true; } public boolean breaksRequiredContract() { // No required contract on sets (groups), so it cant break! return false; } public List<DataEntry> getEntries( DataEntryType type ) { List<DataEntry> entriesOfType = new ArrayList<DataEntry>(); for ( DataEntry entry : entries ) { if ( entry.getType() == type ) { entriesOfType.add( entry ); } } return entriesOfType; } public List<DataEntry> getNonNullEntries() { List<DataEntry> noNullEntriesOfType = new ArrayList<DataEntry>(); for ( DataEntry entry : entries ) { if ( entry.hasValue() ) { noNullEntriesOfType.add( entry ); } } return noNullEntriesOfType; } public List<DataEntry> getNonNullEntries( DataEntryType type ) { List<DataEntry> allEntriesOfType = getEntries( type ); List<DataEntry> noNullEntriesOfType = new ArrayList<DataEntry>(); for ( DataEntry entry : allEntriesOfType ) { if ( entry.hasValue() ) { noNullEntriesOfType.add( entry ); } } return noNullEntriesOfType; } public DataEntry getEntry( String name ) { DataEntry dataEntry = entryMap.get( name ); if ( dataEntry != null ) { return dataEntry; } // if not found on this level, look thru the data entry sets for ( DataEntrySet dataEntrySet : dataEntrySetEntries ) { dataEntry = dataEntrySet.getEntry( name ); if ( dataEntry != null ) { return dataEntry; } } return null; } public boolean hasGroupDataEntry( String name, int groupIndex ) { GroupDataEntry existing = getGroupDataEntry( name, groupIndex ); return existing != null; } public GroupDataEntry getGroupDataEntry( String name, int groupIndex ) { for ( DataEntry dataEntry : getEntries() ) { if ( dataEntry instanceof GroupDataEntry ) { GroupDataEntry groupDataEntry = (GroupDataEntry) dataEntry; if ( groupDataEntry.getName().equals( name ) && groupDataEntry.getGroupIndex() == groupIndex ) { return groupDataEntry; } } } return null; } public Set<ContentKey> resolveRelatedContentKeys() { Set<ContentKey> keys = new HashSet<ContentKey>(); for ( DataEntry entry : getEntries() ) { if ( entry instanceof RelationDataEntry ) { final ContentKey contentKey = ( (RelationDataEntry) entry ).getContentKey(); if ( contentKey != null ) { keys.add( contentKey ); } } else if ( entry instanceof RelationsDataEntry ) { keys.addAll( ( (RelationsDataEntry) entry ).getRelatedContentKeys() ); } else if ( entry instanceof DataEntrySet ) { keys.addAll( ( (DataEntrySet) entry ).resolveRelatedContentKeys() ); } else if ( entry instanceof HtmlAreaDataEntry ) { keys.addAll( ( (HtmlAreaDataEntry) entry ).resolveRelatedContentKeys() ); } } return keys; } public boolean hasRelatedChild( ContentKey contentKey ) { return resolveRelatedContentKeys().contains( contentKey ); } /** * marks references in XML ContentData as deleted. * <p/> * this function will be overridden in CustomContentData and called from ContentData interface * required to be here because also it is required for GroupDataEntry too * * @param key reference to content has to be removed * @return true if something is removed */ public boolean markReferencesToContentAsDeleted( ContentKey key ) { boolean marked = false; for ( final DataEntry entry : this.entries ) { // File, Image, RelatedContentDataEntry if ( entry instanceof RelationDataEntry ) { final RelationDataEntry relatedContentDataEntry = (RelationDataEntry) entry; if ( key.equals( relatedContentDataEntry.getContentKey() ) ) { if ( !relatedContentDataEntry.isMarkedAsDeleted() ) { relatedContentDataEntry.markAsDeleted(); marked = true; } } } // Files, Images, RelatedContentsDataEntry else if ( entry instanceof RelationsDataEntry ) { final RelationsDataEntry relationsDataEntry = (RelationsDataEntry) entry; marked = relationsDataEntry.markReferencesToContentAsDeleted( key ) || marked; } // Group else if ( entry instanceof AbstractDataEntrySet ) { final AbstractDataEntrySet dataEntrySet = (AbstractDataEntrySet) entry; marked = dataEntrySet.markReferencesToContentAsDeleted( key ) || marked; } } return marked; } public List<BinaryDataEntry> getBinaryDataEntryList() { List<BinaryDataEntry> entries = new ArrayList<BinaryDataEntry>(); for ( DataEntry entry : getEntries() ) { if ( entry instanceof BinaryDataEntry ) { entries.add( (BinaryDataEntry) entry ); } else if ( entry instanceof DataEntrySet ) { entries.addAll( ( (DataEntrySet) entry ).getBinaryDataEntryList() ); } } return entries; } public boolean hasBinaryDataEntry( BinaryDataEntry subject ) { for ( DataEntry entry : getEntries() ) { if ( entry instanceof BinaryDataEntry ) { BinaryDataEntry binaryDataEntry = (BinaryDataEntry) entry; if ( binaryDataEntry.hasExistingBinaryKey() && binaryDataEntry.getExistingBinaryKey().equals( subject.getExistingBinaryKey() ) ) { return true; } } else if ( entry instanceof DataEntrySet ) { if ( ( (DataEntrySet) entry ).hasBinaryDataEntry( subject ) ) { return true; } } } return false; } public void replaceBinaryKeyPlaceholders( List<BinaryDataKey> binaryDatas ) { for ( DataEntry entry : getEntries() ) { if ( entry instanceof BinaryDataEntry ) { BinaryDataEntry binaryDataEntry = (BinaryDataEntry) entry; if ( binaryDataEntry.hasBinaryKeyPlaceholder() ) { String placeHolder = binaryDataEntry.getBinaryKeyPlaceholder(); int index = Integer.valueOf( placeHolder.substring( 1 ) ); BinaryDataKey key = binaryDatas.get( index ); binaryDataEntry.setExistingBinaryKey( key.toInt() ); } } else if ( entry instanceof DataEntrySet ) { ( (DataEntrySet) entry ).replaceBinaryKeyPlaceholders( binaryDatas ); } } } public void turnBinaryKeysIntoPlaceHolders( Map<BinaryDataKey, Integer> indexByBinaryDataKey ) { for ( DataEntry entry : getEntries() ) { if ( entry instanceof BinaryDataEntry ) { BinaryDataEntry binaryDataEntry = (BinaryDataEntry) entry; if ( binaryDataEntry.hasExistingBinaryKey() ) { BinaryDataKey binaryDataKey = new BinaryDataKey( binaryDataEntry.getExistingBinaryKey() ); Integer index = indexByBinaryDataKey.get( binaryDataKey ); if ( index != null ) { binaryDataEntry.setExistingBinaryKey( null ); binaryDataEntry.setBinaryKeyPlaceholder( "%" + index ); } } } else if ( entry instanceof DataEntrySet ) { ( (DataEntrySet) entry ).turnBinaryKeysIntoPlaceHolders( indexByBinaryDataKey ); } } } public List<GroupDataEntry> getGroupDataSets( final String name ) { final List<GroupDataEntry> groupDatasets = new ArrayList<GroupDataEntry>(); for ( final DataEntry entry : entries ) { if ( entry instanceof GroupDataEntry ) { final GroupDataEntry groupDataEntry = (GroupDataEntry) entry; if ( name.equals( groupDataEntry.getName() ) ) { groupDatasets.add( groupDataEntry ); } } } return groupDatasets; } public DataEntryConfig getInputConfig( String name ) { return config.getInputConfig( name ); } public DataEntryConfig getInputConfigByRelateiveXPath( String relativeXpath ) { return config.getInputConfigByRelativeXPath( relativeXpath ); } public CtySet getContentTypeConfig() { return config; } public CtySetConfig getSetConfig( String name ) { return config.getSetConfig( name ); } @Override public boolean equals( Object o ) { if ( this == o ) { return true; } if ( !( o instanceof AbstractDataEntrySet ) ) { return false; } AbstractDataEntrySet that = (AbstractDataEntrySet) o; if ( !equalsEntriesOrder( this.getNonNullEntries( DataEntryType.GROUP ), that.getNonNullEntries( DataEntryType.GROUP ) ) ) { return false; } if ( !equalsEntriesIgnoreOrder( this.getNonNullEntries(), that.getNonNullEntries() ) ) { return false; } return true; } private boolean equalsEntriesOrder( List<DataEntry> entriesA, List<DataEntry> entriesB ) { if ( entriesA.size() != entriesB.size() ) { return false; } int i = 0; for ( DataEntry entryA : entriesA ) { final DataEntry entryB = entriesB.get( i++ ); if ( !entryA.equals( entryB ) ) { return false; } } return true; } private boolean equalsEntriesIgnoreOrder( List<DataEntry> entriesA, List<DataEntry> entriesB ) { if ( entriesA.size() != entriesB.size() ) { return false; } for ( DataEntry entryA : entriesA ) { if ( !entriesB.contains( entryA ) ) { return false; } } return true; } @Override public int hashCode() { final HashCodeBuilder builder = new HashCodeBuilder( 241, 459 ).appendSuper( super.hashCode() ); for ( DataEntry entry : entries ) { builder.append( entry ); } return builder.toHashCode(); } protected void validateRequiredDataEntry( final DataEntryConfig dataEntryConfig, final DataEntry dataEntry ) { if ( dataEntry == null ) { throw MissingRequiredContentDataException.missingDataEntry( dataEntryConfig ); } else if ( !dataEntry.hasValue() ) { throw MissingRequiredContentDataException.missingDataEntryValue( dataEntryConfig ); } else if ( dataEntry.breaksRequiredContract() ) { throw MissingRequiredContentDataException.missingDataEntryValue( dataEntryConfig ); } } }