/*
* 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 );
}
}
}