/*
* Copyright (c) 2012, 2016 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.emf.cdo.server.internal.lissome.file;
import org.eclipse.emf.cdo.common.branch.CDOBranch;
import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
import org.eclipse.emf.cdo.common.branch.CDOBranchVersion;
import org.eclipse.emf.cdo.common.commit.CDOCommitInfo;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.model.EMFUtil;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.server.IStoreAccessor;
import org.eclipse.emf.cdo.server.internal.lissome.LissomeFile;
import org.eclipse.emf.cdo.server.internal.lissome.LissomeFileHandle;
import org.eclipse.emf.cdo.server.internal.lissome.LissomeFileOperation;
import org.eclipse.emf.cdo.server.internal.lissome.LissomeStore;
import org.eclipse.emf.cdo.server.internal.lissome.bundle.OM;
import org.eclipse.emf.cdo.server.internal.lissome.optimizer.CommitTransactionTask;
import org.eclipse.emf.cdo.spi.common.branch.InternalCDOBranchManager.BranchLoader.BranchInfo;
import org.eclipse.emf.cdo.spi.common.commit.InternalCDOCommitInfoManager;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageRegistry;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit;
import org.eclipse.emf.cdo.spi.common.revision.DetachedCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta;
import org.eclipse.emf.cdo.spi.server.InternalCommitContext;
import org.eclipse.net4j.util.io.ExtendedDataInputStream;
import org.eclipse.net4j.util.io.IORuntimeException;
import org.eclipse.net4j.util.io.IOUtil;
import org.eclipse.net4j.util.om.monitor.OMMonitor;
import org.eclipse.net4j.util.om.trace.ContextTracer;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Eike Stepper
*/
public class Journal extends LissomeFile
{
public static final String EXTENSION = "journal";
public static final byte PACKAGE_UNITS_BLOCK = 1;
public static final byte COMMIT_TRANSACTION_BLOCK = 2;
public static final byte CREATE_BRANCH_BLOCK = 3;
private static final ContextTracer TRACER = new ContextTracer(OM.JOURNAL, Journal.class);
private static final long serialVersionUID = 1L;
private static final boolean ZIP_PACKAGE_BYTES = true;
private Map<String, Long> ePackagePointers = new HashMap<String, Long>();
private long commitPointer;
private long newCommitPointer;
private long packageUnitPointer;
private long newPackageUnitPointer;
private CommitTransactionTask commitTransactionTask;
private final LissomeFileHandle writer;
public Journal(LissomeStore store) throws FileNotFoundException
{
super(store, store.getRepository().getName() + "." + EXTENSION);
writer = openWriter();
}
@Override
protected LissomeFileHandle openHandle(String mode)
{
try
{
return new LissomeFileHandle(this, mode)
{
@Override
public CDORevision getRevision(long pointer)
{
return super.getRevision(-pointer);
}
};
}
catch (FileNotFoundException ex)
{
throw new IORuntimeException(ex);
}
}
public void firstStart()
{
try
{
writer.seek(0);
writer.writeLong(commitPointer);
writer.writeLong(packageUnitPointer);
}
catch (IOException ex)
{
throw new IORuntimeException(ex);
}
finally
{
cleanUp();
}
}
public void reStart()
{
try
{
writer.seek(0);
commitPointer = writer.readLong();
packageUnitPointer = writer.readLong();
}
catch (IOException ex)
{
throw new IORuntimeException(ex);
}
finally
{
cleanUp();
}
}
public Collection<InternalCDOPackageUnit> readPackageUnits()
{
LissomeFileHandle reader = openReader();
try
{
List<InternalCDOPackageUnit> result = new ArrayList<InternalCDOPackageUnit>();
InternalCDOPackageRegistry packageRegistry = getStore().getRepository().getPackageRegistry();
ResourceSet resourceSet = new ResourceSetImpl();
resourceSet.setPackageRegistry(packageRegistry);
long filePointer = packageUnitPointer;
while (filePointer != 0)
{
reader.seek(filePointer);
filePointer = reader.readLong();
int size = reader.readInt();
for (int i = 0; i < size; i++)
{
InternalCDOPackageUnit packageUnit = (InternalCDOPackageUnit)reader.readCDOPackageUnit(resourceSet);
packageUnit.setPackageRegistry(packageRegistry);
result.add(packageUnit);
long ePackagePointer = reader.readLong();
ePackagePointers.put(packageUnit.getID(), ePackagePointer);
EPackage ePackage = packageUnit.getTopLevelPackageInfo().getEPackage();
mapPackage(ePackage, reader);
}
}
return result;
}
catch (IOException ex)
{
throw new IORuntimeException(ex);
}
finally
{
IOUtil.close(reader);
}
}
public EPackage[] loadPackageUnit(InternalCDOPackageUnit packageUnit)
{
LissomeFileHandle reader = openReader();
try
{
long ePackagePointer = ePackagePointers.get(packageUnit.getID());
reader.seek(ePackagePointer);
byte[] bytes = reader.readByteArray();
EPackage ePackage = createEPackage(packageUnit, bytes);
return EMFUtil.getAllPackages(ePackage);
}
catch (IOException ex)
{
throw new IORuntimeException(ex);
}
finally
{
IOUtil.close(reader);
}
}
protected void checkMetaObject(ENamedElement element, String name) throws IOException
{
if (!element.getName().equals(name))
{
throw new IOException("Name '" + name + "' expected: " + element);
}
}
protected void checkListSize(EList<?> list, int size) throws IOException
{
if (list.size() != size)
{
throw new IOException("Size " + size + " expected: " + list);
}
}
protected void mapPackage(EPackage ePackage, LissomeFileHandle reader) throws IOException
{
LissomeStore store = getStore();
checkMetaObject(ePackage, reader.readString());
store.mapMetaObject(ePackage, reader.readInt());
EList<EClassifier> eClassifiers = ePackage.getEClassifiers();
checkListSize(eClassifiers, reader.readInt());
for (EClassifier eClassifier : eClassifiers)
{
checkMetaObject(eClassifier, reader.readString());
store.mapMetaObject(eClassifier, reader.readInt());
if (eClassifier instanceof EClass)
{
EClass eClass = (EClass)eClassifier;
EList<EStructuralFeature> eStructuralFeatures = eClass.getEStructuralFeatures();
checkListSize(eStructuralFeatures, reader.readInt());
for (EStructuralFeature eStructuralFeature : eStructuralFeatures)
{
checkMetaObject(eStructuralFeature, reader.readString());
store.mapMetaObject(eStructuralFeature, reader.readInt());
}
}
}
EList<EPackage> eSubpackages = ePackage.getESubpackages();
checkListSize(eSubpackages, reader.readInt());
for (EPackage eSubpackage : eSubpackages)
{
mapPackage(eSubpackage);
}
}
protected void mapPackage(EPackage ePackage) throws IOException
{
LissomeStore store = getStore();
writer.writeString(ePackage.getName());
writer.writeInt(store.mapMetaObject(ePackage));
EList<EClassifier> eClassifiers = ePackage.getEClassifiers();
writer.writeInt(eClassifiers.size());
for (EClassifier eClassifier : eClassifiers)
{
writer.writeString(eClassifier.getName());
writer.writeInt(store.mapMetaObject(eClassifier));
if (eClassifier instanceof EClass)
{
EClass eClass = (EClass)eClassifier;
EList<EStructuralFeature> eStructuralFeatures = eClass.getEStructuralFeatures();
writer.writeInt(eStructuralFeatures.size());
for (EStructuralFeature eStructuralFeature : eStructuralFeatures)
{
writer.writeString(eStructuralFeature.getName());
writer.writeInt(store.mapMetaObject(eStructuralFeature));
}
}
}
EList<EPackage> eSubpackages = ePackage.getESubpackages();
writer.writeInt(eSubpackages.size());
for (EPackage eSubpackage : eSubpackages)
{
mapPackage(eSubpackage);
}
}
private EPackage createEPackage(InternalCDOPackageUnit packageUnit, byte[] bytes)
{
ResourceSet resourceSet = EMFUtil.newEcoreResourceSet(getStore().getRepository().getPackageRegistry());
return EMFUtil.createEPackage(packageUnit.getID(), bytes, ZIP_PACKAGE_BYTES, resourceSet, false);
}
private byte[] getEPackageBytes(InternalCDOPackageUnit packageUnit)
{
EPackage ePackage = packageUnit.getTopLevelPackageInfo().getEPackage();
return EMFUtil.getEPackageBytes(ePackage, ZIP_PACKAGE_BYTES, getStore().getRepository().getPackageRegistry());
}
public void writePackageUnits(final InternalCDOPackageUnit[] packageUnits, final OMMonitor monitor)
{
if (TRACER.isEnabled())
{
TRACER.format("writePackageUnits: {0}", Arrays.asList(packageUnits)); //$NON-NLS-1$
}
monitor.begin(packageUnits.length);
try
{
writer.append(new LissomeFileOperation()
{
public void execute(LissomeFileHandle writer) throws IOException
{
writer.writeByte(PACKAGE_UNITS_BLOCK);
writePackageUnits(writer, packageUnits, monitor);
}
});
}
finally
{
monitor.done();
}
}
protected void writePackageUnits(LissomeFileHandle writer, InternalCDOPackageUnit[] packageUnits, OMMonitor monitor) throws IOException
{
writer.writeInt(packageUnits.length);
for (InternalCDOPackageUnit packageUnit : packageUnits)
{
long ePackagePointer = writer.getFilePointer();
ePackagePointers.put(packageUnit.getID(), ePackagePointer);
byte[] bytes = getEPackageBytes(packageUnit);
writer.writeByteArray(bytes);
monitor.worked(3);
}
newPackageUnitPointer = writer.getFilePointer();
writer.writeLong(packageUnitPointer);
writer.writeInt(packageUnits.length);
for (InternalCDOPackageUnit packageUnit : packageUnits)
{
writer.writeCDOPackageUnit(packageUnit, false);
long ePackagePointer = ePackagePointers.get(packageUnit.getID());
writer.writeLong(ePackagePointer);
EPackage ePackage = packageUnit.getTopLevelPackageInfo().getEPackage();
mapPackage(ePackage);
monitor.worked(1);
}
}
public long createBranch(final int branchID, final BranchInfo branchInfo)
{
if (TRACER.isEnabled())
{
TRACER.format("createBranch: {0}, {1}", branchID, branchInfo); //$NON-NLS-1$
}
return writer.append(new LissomeFileOperation()
{
public void execute(LissomeFileHandle writer) throws IOException
{
writer.writeByte(CREATE_BRANCH_BLOCK);
writer.writeInt(branchID);
writer.writeString(branchInfo.getName());
writer.writeInt(branchInfo.getBaseBranchID());
writer.writeLong(branchInfo.getBaseTimeStamp());
}
});
}
public void write(final InternalCommitContext context, final OMMonitor monitor)
{
if (TRACER.isEnabled())
{
TRACER.format("write: {0}", context); //$NON-NLS-1$
}
try
{
final CDOBranchPoint branchPoint = context.getBranchPoint();
final long previousTimeStamp = context.getPreviousTimeStamp();
final String userID = context.getUserID();
final String commitComment = context.getCommitComment();
final InternalCDOPackageUnit[] newPackageUnits = context.getNewPackageUnits();
final InternalCDORevision[] newObjects = context.getNewObjects();
final CDOID[] detachedObjects = context.getDetachedObjects();
final Map<CDOID, EClass> detachedObjectTypes = context.getDetachedObjectTypes();
final CDOBranchVersion[] detachedObjectVersions = context.getDetachedObjectVersions();
final InternalCDORevisionDelta[] dirtyObjectDeltas = context.getDirtyObjectDeltas();
int revisionWork = detachedObjects.length + 3 * newObjects.length + 2 * dirtyObjectDeltas.length;
monitor.begin(1 + 4 * newPackageUnits.length + revisionWork);
commitTransactionTask = new CommitTransactionTask(context);
newCommitPointer = writer.append(new LissomeFileOperation()
{
public void execute(LissomeFileHandle writer) throws IOException
{
writer.writeByte(COMMIT_TRANSACTION_BLOCK);
writer.writeLong(commitPointer);
writer.writeCDOBranchPoint(branchPoint);
writer.writeLong(previousTimeStamp);
writer.writeString(userID);
writer.writeString(commitComment);
monitor.worked();
// New package units
if (newPackageUnits.length != 0)
{
writePackageUnits(writer, newPackageUnits, monitor);
}
InternalCDORevision[] detachedRevisions = null;
if (detachedObjects.length != 0 && detachedObjectTypes != null && detachedObjectVersions != null)
{
detachedRevisions = new InternalCDORevision[detachedObjects.length];
commitTransactionTask.setDetachedRevisions(detachedRevisions);
}
// Detached objects
CDOBranch transactionBranch = branchPoint.getBranch();
long timeStamp = branchPoint.getTimeStamp();
writer.writeBoolean(detachedObjectTypes != null);
writer.writeBoolean(detachedObjectVersions != null);
writer.writeInt(detachedObjects.length);
for (int i = 0; i < detachedObjects.length; i++)
{
CDOID id = detachedObjects[i];
EClass eClass = detachedObjectTypes != null ? detachedObjectTypes.get(id) : null;
CDOBranchVersion branchVersion = detachedObjectVersions != null ? detachedObjectVersions[i] : null;
if (detachedRevisions != null)
{
int version = branchVersion.getBranch() == transactionBranch ? branchVersion.getVersion() + 1 : CDOBranchVersion.FIRST_VERSION;
detachedRevisions[i] = new DetachedCDORevision(eClass, id, transactionBranch, version, timeStamp);
}
if (eClass != null)
{
int cid = getStore().getMetaID(eClass);
writer.writeInt(cid);
}
if (branchVersion != null)
{
int version = branchVersion.getVersion();
if (branchVersion.getBranch() == transactionBranch)
{
writer.writeInt(version);
}
else
{
writer.writeInt(-version);
writer.writeCDOBranch(branchVersion.getBranch());
}
}
monitor.worked();
}
// New objects
Map<CDORevision, Long> newObjectPointers = commitTransactionTask.getNewObjectPointers();
writer.writeInt(newObjects.length);
for (InternalCDORevision revision : newObjects)
{
long pointer = writer.getFilePointer();
newObjectPointers.put(revision, pointer);
writer.writeCDORevision(revision, CDORevision.UNCHUNKED);
monitor.worked();
}
// Dirty object deltas
writer.writeInt(dirtyObjectDeltas.length);
for (InternalCDORevisionDelta revisionDelta : dirtyObjectDeltas)
{
writer.writeCDORevisionDelta(revisionDelta);
monitor.worked();
}
// Large objects
ExtendedDataInputStream in = context.getLobs();
if (in != null)
{
int count = in.readInt();
for (int i = 0; i < count; i++)
{
byte[] id = in.readByteArray();
commitTransactionTask.getLobs().add(id);
long size = in.readLong();
if (size > 0)
{
writeBlob(id, size, in);
}
else
{
writeClob(id, -size, new InputStreamReader(in));
}
}
}
}
});
commitTransactionTask.setNewCommitPointer(newCommitPointer);
}
finally
{
monitor.done();
}
}
protected void writeBlob(byte[] id, long size, InputStream inputStream) throws IOException
{
// TODO: implement LissomeStoreAccessor.writeBlob(id, size, inputStream)
throw new UnsupportedOperationException();
}
protected void writeClob(byte[] id, long size, Reader reader) throws IOException
{
// TODO: implement LissomeStoreAccessor.writeClob(id, size, reader)
throw new UnsupportedOperationException();
}
protected void removeLob(byte[] id)
{
// TODO: implement LissomeStoreAccessor.removeLob(id)
throw new UnsupportedOperationException();
}
public CommitTransactionTask commit(OMMonitor monitor)
{
try
{
if (newCommitPointer != commitPointer || newPackageUnitPointer != packageUnitPointer)
{
commitPointer = newCommitPointer;
packageUnitPointer = newPackageUnitPointer;
writer.seek(0L);
writer.writeLong(commitPointer);
writer.writeLong(packageUnitPointer);
}
return commitTransactionTask;
}
catch (IOException ex)
{
throw new IORuntimeException(ex);
}
finally
{
cleanUp();
}
}
public void rollback(IStoreAccessor.CommitContext commitContext)
{
try
{
List<byte[]> lobs = commitTransactionTask.getLobs();
for (byte[] id : lobs)
{
removeLob(id);
}
}
finally
{
cleanUp();
}
}
protected void cleanUp()
{
commitTransactionTask = null;
newCommitPointer = commitPointer;
newPackageUnitPointer = packageUnitPointer;
}
public CDOCommitInfo readCommitInfo(LissomeFileHandle reader, long pointer)
{
try
{
reader.seek(pointer);
reader.readByte(); // COMMIT_TRANSACTION_BLOCK
reader.readLong(); // commitPointer
CDOBranchPoint branchPoint = reader.readCDOBranchPoint();
CDOBranch branch = branchPoint.getBranch();
long timeStamp = branchPoint.getTimeStamp();
long previousTimeStamp = reader.readLong();
String userID = reader.readString();
String comment = reader.readString();
InternalCDOCommitInfoManager commitInfoManager = getStore().getRepository().getCommitInfoManager();
return commitInfoManager.createCommitInfo(branch, timeStamp, previousTimeStamp, userID, comment, null, null);
}
catch (IOException ex)
{
throw new IORuntimeException(ex);
}
}
}