blob: 829f9f63ea5d70af646c0ff072a79a6ff69458f3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.server.core.shared.txn.logedit;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import org.apache.directory.server.core.api.partition.Partition;
import org.apache.directory.server.core.api.partition.index.MasterTable;
import org.apache.directory.server.core.api.partition.index.UUIDComparator;
import org.apache.directory.server.core.api.txn.logedit.AbstractLogEdit;
import org.apache.directory.server.core.api.txn.logedit.DataChange;
import org.apache.directory.server.core.api.txn.logedit.EntryModification;
import org.apache.directory.server.core.api.txn.logedit.IndexModification;
import org.apache.directory.shared.ldap.model.entry.Entry;
import org.apache.directory.shared.ldap.model.name.Dn;
/**
* A container for index and entry changes. If any entry change is contained, then they are
* for a single entry. All changes contained for an entry belong to a single version and
* can be atomically applied to the entry in question.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class DataChangeContainer extends AbstractLogEdit
{
/** id of the entry if the container contains a change for an entry */
private UUID entryID;
/** Partition stored for fast access */
private transient Partition partition;
/** partition this change applies to */
private Dn partitionDn;
/** List of data changes */
private List<DataChange> changes = new LinkedList<DataChange>();
/** The type of change */
private enum ChangeType
{
INDEX_MODIFICATION,
ENTRY_ADD_DELETE,
ENTRY_CHANGE,
ENTRY_REPLACE
}
//For externalizable
public DataChangeContainer()
{
super( Long.MIN_VALUE );
}
public DataChangeContainer( Partition partition )
{
super( Long.MIN_VALUE );
this.partitionDn = partition.getSuffixDn();
this.partition = partition;
}
public Dn getPartitionDn()
{
return partitionDn;
}
public Partition getPartition()
{
return partition;
}
public void setPartition( Partition partition )
{
this.partition = partition;
}
public UUID getEntryID()
{
return entryID;
}
public void setEntryID( UUID id )
{
entryID = id;
}
public List<DataChange> getChanges()
{
return changes;
}
public void addChange( DataChange change )
{
changes.add( change );
}
/**
* {@inheritDoc}
*/
@Override
public void apply( boolean recovery ) throws Exception
{
long changeLsn = getLogAnchor().getLogLSN();
Entry curEntry = null;
boolean entryExisted = false;
Entry originalEntry = null;
// TODO find the partition from the dn if changeContainer doesn't have it.
if ( entryID != null )
{
MasterTable master = partition.getMasterTable();
curEntry = master.get( entryID );
if ( curEntry != null )
{
originalEntry = curEntry = curEntry.clone();
entryExisted = true;
}
}
for ( DataChange change : changes )
{
if ( ( change instanceof EntryModification ) )
{
EntryModification entryModification = ( EntryModification ) change;
curEntry = entryModification.applyModification( partition, curEntry, entryID, changeLsn, recovery );
}
else
{
IndexModification indexModification = ( IndexModification ) change;
indexModification.applyModification( partition, recovery );
}
}
if ( curEntry != null )
{
MasterTable master = partition.getMasterTable();
if ( !curEntry.isSchemaAware() )
{
curEntry.apply( partition.getSchemaManager() );
}
master.put( entryID, curEntry );
}
else
{
if ( entryExisted )
{
MasterTable master = partition.getMasterTable();
master.remove( entryID, originalEntry );
}
}
}
/**
* Applies the updates made by this log edit to the entry identified by the entryID
* and partition dn.
*
* @param entryPartitionDn dn of the partition of the entry
* @param id id of the entry
* @param curEntry entry to be merged
* @param needToCloneOnChange true if entry should be cloned while applying a change.
* @return entry after it is merged with the updates in the txn.
*/
public Entry mergeUpdates( Dn entryPartitionDn, UUID id, Entry curEntry, boolean needToCloneOnChange )
{
/**
* Check if the container has changes for the entry
* and the version says we need to apply this change
*/
boolean applyChanges = false;
if ( entryID != null )
{
/*
* Container has changes for an entry. Check if the entry change
* affects out entry by comparing id and partitionDn.
*/
Comparator<UUID> idComp = UUIDComparator.INSTANCE;
if ( entryPartitionDn.equals( partitionDn )
&& ( idComp.compare( id, entryID ) == 0 ) )
{
applyChanges = true;
}
}
if ( applyChanges )
{
long changeLsn = getLogAnchor().getLogLSN();
for ( DataChange change : changes )
{
if ( change instanceof EntryModification )
{
EntryModification entryModification = ( EntryModification ) change;
if ( needToCloneOnChange )
{
if ( curEntry != null )
{
curEntry = curEntry.clone();
}
needToCloneOnChange = false;
}
curEntry = entryModification.applyModification( partition, curEntry, id, changeLsn, true );
}
}
}
return curEntry;
}
/**
* Read back a DataChange container from a stream. The format is : <br/>
* <ul>
* <li>entryId if we have stored an Entry change, null if it's an Index change</li>
* <li>the transaction id</li>
* <li>the partition DN</li>
* <li>the changes if any</li>
* </ul>
*/
@Override
public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
{
boolean hasID = in.readBoolean();
if ( hasID )
{
entryID = UUID.fromString( in.readUTF() );
}
else
{
entryID = null;
}
txnID = in.readLong();
partitionDn = new Dn();
partitionDn.readExternal( in );
// Here, we must find a way to find the Partition from its Dn
DataChange change = null;
int numChanges = in.readInt();
for ( int idx = 0; idx < numChanges; idx++ )
{
ChangeType changeType = ChangeType.values()[in.read()];
switch ( changeType )
{
case ENTRY_ADD_DELETE:
change = new EntryAddDelete();
break;
case ENTRY_CHANGE:
change = new EntryChange();
break;
case ENTRY_REPLACE:
change = new EntryReplace();
break;
case INDEX_MODIFICATION:
change = new IndexChange();
break;
}
change.readExternal( in );
changes.add( change );
}
}
@Override
public void writeExternal( ObjectOutput out ) throws IOException
{
DataChange change;
out.write(EditType.DATA_CHANGE.ordinal());
if ( entryID != null )
{
out.writeBoolean( true );
out.writeUTF( entryID.toString() );
}
else
{
out.writeBoolean( false );
}
out.writeLong( txnID );
partitionDn.writeExternal( out );
out.writeInt( changes.size() );
Iterator<DataChange> it = changes.iterator();
while ( it.hasNext() )
{
change = it.next();
if ( change instanceof IndexModification )
{
out.write( ChangeType.INDEX_MODIFICATION.ordinal() );
}
else if ( change instanceof EntryAddDelete )
{
out.write( ChangeType.ENTRY_ADD_DELETE.ordinal() );
}
else if ( change instanceof EntryChange )
{
out.write( ChangeType.ENTRY_CHANGE.ordinal() );
}
else if ( change instanceof EntryReplace )
{
out.write( ChangeType.ENTRY_REPLACE.ordinal() );
}
change.writeExternal( out );
}
}
/**
* @see Object#toString()
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "DataChangeContainer(" );
// The partition
sb.append( partitionDn ).append( ", " );
// The entry UUID
sb.append( entryID ).append( ", " );
// The txn ID
sb.append( txnID ).append( ")\n{\n" );
for ( DataChange change : changes )
{
sb.append( " " );
sb.append( change );
sb.append( '\n' );
}
sb.append( "}" );
return sb.toString();
}
}