DocumentVariantImpl.java :  » Content-Management-System » daisy » org » outerj » daisy » repository » commonimpl » Java Open Source

Java Open Source » Content Management System » daisy 
daisy » org » outerj » daisy » repository » commonimpl » DocumentVariantImpl.java
/*
 * Copyright 2004 Outerthought bvba and Schaubroeck nv
 *
 * Licensed 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.outerj.daisy.repository.commonimpl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.outerj.daisy.repository.ByteArrayPartDataSource;
import org.outerj.daisy.repository.ChangeType;
import org.outerj.daisy.repository.DocumentCollection;
import org.outerj.daisy.repository.DocumentCollections;
import org.outerj.daisy.repository.DocumentTypeInconsistencyException;
import org.outerj.daisy.repository.Field;
import org.outerj.daisy.repository.FieldNotFoundException;
import org.outerj.daisy.repository.Fields;
import org.outerj.daisy.repository.HierarchyPath;
import org.outerj.daisy.repository.Link;
import org.outerj.daisy.repository.Links;
import org.outerj.daisy.repository.LockInfo;
import org.outerj.daisy.repository.LockType;
import org.outerj.daisy.repository.Part;
import org.outerj.daisy.repository.PartDataSource;
import org.outerj.daisy.repository.PartNotFoundException;
import org.outerj.daisy.repository.Parts;
import org.outerj.daisy.repository.RepositoryException;
import org.outerj.daisy.repository.RepositoryRuntimeException;
import org.outerj.daisy.repository.ValueType;
import org.outerj.daisy.repository.VariantKey;
import org.outerj.daisy.repository.Version;
import org.outerj.daisy.repository.VersionKey;
import org.outerj.daisy.repository.VersionNotFoundException;
import org.outerj.daisy.repository.VersionState;
import org.outerj.daisy.repository.Versions;
import org.outerj.daisy.repository.acl.AccessDetails;
import org.outerj.daisy.repository.acl.AclDetailPermission;
import org.outerj.daisy.repository.commonimpl.schema.CommonRepositorySchema;
import org.outerj.daisy.repository.commonimpl.variant.CommonVariantManager;
import org.outerj.daisy.repository.schema.DocumentType;
import org.outerj.daisy.repository.schema.FieldType;
import org.outerj.daisy.repository.schema.FieldTypeUse;
import org.outerj.daisy.repository.schema.PartType;
import org.outerj.daisy.repository.schema.PartTypeUse;
import org.outerx.daisy.x10.DocumentDocument;

/**
 * Encapsulates all variant-specific data of a document.
 * An instance of this class is contained by {@link DocumentImpl}.
 */
public class DocumentVariantImpl {
    private DocumentImpl ownerDocument;
    private DocumentStrategy documentStrategy;
    private CommonRepository repository;
    private AuthenticatedUser currentUser;
    /** The parts mapped by PartType ID. */
    private Map<Long, Part> parts = new HashMap<Long, Part>();
    private boolean partChanges = false;
    private long documentTypeId;
    private boolean documentTypeChanged = false;
    private String name;
    private boolean nameUpdated = false;
    private long branchId;
    private long languageId;
    private boolean isNew;
    private Date lastModified;
    private long lastModifier = -1;
    private boolean retired = false;
    private boolean retiredChanged = false;
    private VersionImpl[] versions;
    /** Cached reference to the live version. */
    private Version liveVersion;
    /** Indicates if the variable liveVersion has been initialiased. */
    private boolean liveVersionLoaded = false;
    /** Cached reference to the last version. */
    private Version lastVersion;
    /** Indicates if the variable lastVersion has been initialiased. */
    private boolean lastVersionLoaded = false;
    private long lastVersionId = -1;
    private long liveVersionId = -1;
    /** The fields mapped by FieldType ID. */
    private Map<Long, Field> fields = new HashMap<Long, Field>();
    private boolean fieldChanges = false;
    private List<Link> links = new ArrayList<Link>(3);
    private boolean linkChanges = false;
    private Map<String, String> customFields = new HashMap<String, String>();
    private boolean customFieldChanges = false;
    private Map<Long, DocumentCollection> documentCollections = new HashMap<Long, DocumentCollection>();
    private boolean documentCollectionChanges = false;
    private LockInfoImpl lockInfo = new LockInfoImpl();
    private VersionState versionState = VersionState.PUBLISH;
    private String summary;
    private long updateCount = 0;
    private IntimateAccess intimateAccess = new IntimateAccess();
    private long createdFromBranchId = -1;
    private long createdFromLanguageId = -1;
    private long createdFromVersionId = -1;
    // the variables below are values we need to store to be able to pass them on
    // from the remote implementation
    private boolean validateOnSave = true;
    private boolean documentTypeChecksEnabled = true;
    private long startBranchId = -1;
    private long startLanguageId = -1;
    private VersionKey syncedWith;
    private ChangeType changeType = ChangeType.MAJOR;
    private String changeComment;
    private long lastMajorChangeVersionId = -1;
    private long liveMajorChangeVersionId = -1;

    public static final String ERROR_ACCESSING_REPOSITORY_SCHEMA = "Error accessing repository schema information.";
    private static final String READ_ONLY_MESSAGE = "This document object is read-only.";

    public DocumentVariantImpl(DocumentImpl ownerDocument, DocumentStrategy documentStrategy, CommonRepository repository, AuthenticatedUser currentUser, long documentTypeId, long branchId, long languageId) {
        this.ownerDocument = ownerDocument;
        this.documentStrategy = documentStrategy;
        this.repository = repository;
        this.currentUser = currentUser;
        this.documentTypeId = documentTypeId;
        this.branchId = branchId;
        this.languageId = languageId;
        this.isNew = true;
    }

    public DocumentVariantImpl.IntimateAccess getIntimateAccess(DocumentStrategy strategy) {
        if (this.documentStrategy == strategy)
            return intimateAccess;
        return null;
    }

    public long getBranchId() {
        return branchId;
    }

    public long getLanguageId() {
        return languageId;
    }

    public boolean isNew() {
        return isNew;
    }

    public long getDocumentTypeId() {
        return documentTypeId;
    }

    public String getDocumentId() {
        return ownerDocument.getId();
    }

    public long getDocSeqId() {
        return ownerDocument.getSeqId();
    }

    public String getDocNamespace() {
        return ownerDocument.getNamespace();
    }

    public VariantKey getKey() {
        if (ownerDocument.isNew())
            throw new IllegalStateException("Cannot get variant key: document is new and thus has no ID assigned just yet.");
        return new VariantKey(ownerDocument.getId(), getBranchId(), getLanguageId());
    }

    public void setValidateOnSave(boolean validateOnSave) {
        this.validateOnSave = validateOnSave;
    }

    public void changeDocumentType(long documentTypeId) throws RepositoryException {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (documentTypeId != this.documentTypeId) {
            // fetch the document type to be sure it exists
            // the method below will throw an exception if it doesn't exist
            repository.getRepositorySchema().getDocumentTypeById(documentTypeId, false, currentUser);

            this.documentTypeId = documentTypeId;
            this.documentTypeChanged = true;
        }
    }

    public void changeDocumentType(String documentTypeName) throws RepositoryException {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        DocumentType documentType = repository.getRepositorySchema().getDocumentTypeByName(documentTypeName, false, currentUser);
        changeDocumentType(documentType.getId());
    }

    public Field getField(String name) throws FieldNotFoundException {
        FieldType fieldType;
        try {
            fieldType = repository.getRepositorySchema().getFieldTypeByName(name, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }
        return getField(fieldType.getId());
    }

    public Field getField(long fieldTypeId) throws FieldNotFoundException {
        Field field = fields.get(fieldTypeId);
        if (field == null)
            throw new FieldNotFoundException(fieldTypeId);
        else
            return field;
    }

    public boolean hasField(long fieldTypeId) {
        Field field = fields.get(fieldTypeId);
        return field != null;
    }

    public boolean hasField(String fieldTypeName) {
        FieldType fieldType;
        try {
            fieldType = repository.getRepositorySchema().getFieldTypeByName(fieldTypeName, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }
        return hasField(fieldType.getId());
    }

    public Fields getFields() {
        return new FieldsImpl(fields.values().toArray(new Field[0]));
    }

    public Fields getFieldsInOrder() {
        return new FieldsImpl(orderFields(fields.values().toArray(new Field[0])));
    }

    public void setField(String name, Object value) throws DocumentTypeInconsistencyException {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        FieldType fieldType;
        try {
            fieldType = repository.getRepositorySchema().getFieldTypeByName(name, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }
        setField(fieldType.getId(), value);
    }

    public void setField(long fieldTypeId, Object value) throws DocumentTypeInconsistencyException {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (value == null)
            throw new NullPointerException("Field value cannot be null.");

        DocumentType documentType;
        try {
            documentType = repository.getRepositorySchema().getDocumentTypeById(documentTypeId, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }

        FieldType fieldType;
        try {
            fieldType = repository.getRepositorySchema().getFieldTypeById(fieldTypeId, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }

        if (documentTypeChecksEnabled && !documentType.hasFieldType(fieldTypeId))
            throw new DocumentTypeInconsistencyException("The FieldType \"" + fieldType.getName() + "\" (ID: " + fieldTypeId + ") is not allowed on this document.");

        // For multivalue fields, make sure the array is a clone so that it's unmodifiable from the outside
        if (value instanceof Object[])
            value = ((Object[])value).clone();

        value = checkAndNormalizeFieldValue(fieldType, value);

        FieldImpl field = (FieldImpl)fields.get(new Long(fieldTypeId));

        // if the value didn't change, don't update it
        if (field != null && (fieldType.isMultiValue() ? Arrays.equals((Object[])field.getValue(), (Object[])value) : field.getValue().equals(value)))
            return;

        field = new FieldImpl(intimateAccess, fieldTypeId, value);
        fields.put(new Long(fieldTypeId), field);
        fieldChanges = true;
    }

    private Object checkAndNormalizeFieldValue(FieldType fieldType, Object value) throws DocumentTypeInconsistencyException {
        if (fieldType.isMultiValue()) {
            if (!value.getClass().isArray())
                throw new DocumentTypeInconsistencyException("The value for the multivalue-field \"" + fieldType.getName() + "\" should be an array.");
            Object[] values = (Object[])value;
            if (values.length == 0)
                throw new DocumentTypeInconsistencyException("The value supplied for the multivalue field \"" + fieldType.getName() + "\" is a zero-length array, it should be an array of at least one element.");
            for (int i = 0; i < values.length; i++) {
                values[i] = checkAndNormalizeHierarchyValue(fieldType, values[i], i);
            }
            return values;
        } else {
            return checkAndNormalizeHierarchyValue(fieldType, value, -1);
        }
    }

    private Object checkAndNormalizeHierarchyValue(FieldType fieldType, Object value, int multiValueIndex) throws DocumentTypeInconsistencyException {
        if (fieldType.isHierarchical()) {
            if (!(value instanceof HierarchyPath))
                throw new DocumentTypeInconsistencyException("The supplied value for hierarchical field \"" + fieldType.getName() + "\" should be a HierarchyPath object.");
            HierarchyPath hierarchyPath = (HierarchyPath)value;
            Object[] values = hierarchyPath.getElements();
            if (values.length == 0)
                throw new DocumentTypeInconsistencyException("The supplied HierarchyPath object for hierarchical field \"" + fieldType.getName() + "\" should contain at least one element.");
            for (int i = 0; i < values.length; i++) {
                values[i] = checkAndNormalizePrimitiveValue(fieldType, values[i], multiValueIndex, i);
            }
            return new HierarchyPath(values);
        } else {
            return checkAndNormalizePrimitiveValue(fieldType, value, multiValueIndex, -1);
        }
    }

    private Object checkAndNormalizePrimitiveValue(FieldType fieldType, Object value, int multiValueIndex, int hierarchyPathIndex) throws DocumentTypeInconsistencyException {
        ValueType valueType = fieldType.getValueType();
        if (!valueType.getTypeClass().isAssignableFrom(value.getClass()))
            throw new DocumentTypeInconsistencyException("The supplied value for the field \"" + fieldType.getName() + "\" is not of the correct type. Expected was a " + valueType.toString() + " (" + valueType.getTypeClass().getName() + ") but got a " + value.getClass().getName() + getFieldValuePositionDetails(multiValueIndex, hierarchyPathIndex));

        // perform normalization of values for certain value types
        if (valueType == ValueType.DATE || valueType == ValueType.DATETIME) {
            boolean keepTime = fieldType.getValueType() == ValueType.DATETIME;
            value = DateUtil.getNormalizedDate((Date)value, keepTime);
        } else if (valueType == ValueType.LINK) {
            VariantKey variantKey = (VariantKey)value;
            value = new VariantKey(repository.normalizeDocumentId(variantKey.getDocumentId()), variantKey.getBranchId(), variantKey.getLanguageId());
        }

        return value;
    }

    private String getFieldValuePositionDetails(int multiValueIndex, int hierarchyPathIndex) {
        StringBuilder builder = new StringBuilder();
        if (multiValueIndex != -1 || hierarchyPathIndex != -1)
            builder.append(" (");
        if (multiValueIndex != -1)
            builder.append("multi value index: ").append(multiValueIndex);
        if (hierarchyPathIndex != -1) {
            if (multiValueIndex != -1)
                builder.append(", ");
            builder.append("hierarchy path index: ").append(hierarchyPathIndex);
        }
        if (builder.length() > 0)
            builder.append(")");
        return builder.toString();
    }

    public void deleteField(String name) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        FieldType fieldType;
        try {
            fieldType = repository.getRepositorySchema().getFieldTypeByName(name, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }
        deleteField(fieldType.getId());
    }

    public void deleteField(long fieldTypeId) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        Long key = new Long(fieldTypeId);
        if (fields.containsKey(key)) {
            fields.remove(new Long(fieldTypeId));
            fieldChanges = true;
        }
    }

    public LockInfo getLockInfo(boolean fresh) throws RepositoryException {
        LockInfoImpl lockInfo = this.lockInfo;
        if ((fresh || lockInfo == null) && !isNew) {
            synchronized(this) {
                lockInfo = documentStrategy.getLockInfo(this);
                this.lockInfo = lockInfo;
            }
        }
        return lockInfo;
    }

    // This method is by purpose not in the Document interface. It is used to clear lock
    // info in cached document objects. If it would be in the interface, then the method
    // getLockInfo should cover the case where lockInfo is null and id is -1.
    public void clearLockInfo() {
        this.lockInfo = null;
    }

    public boolean lock(long duration, LockType lockType) throws RepositoryException {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (isNew)
            throw new RepositoryException("Can't take a lock on a new, non-saved document variant.");

        lockInfo = documentStrategy.lock(this, duration, lockType);

        if (!lockInfo.hasLock())
            return false;

        return lockInfo.getUserId() == currentUser.getId();
    }

    public boolean releaseLock() throws RepositoryException {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (isNew)
            return true;

        lockInfo = documentStrategy.releaseLock(this);
        return !lockInfo.hasLock();
    }

    public void addXml(DocumentDocument.Document documentXml, AccessDetails accessDetails) throws RepositoryException {
        addNonVersionedDataToXml(documentXml, accessDetails);

        documentXml.setDataVersionId(-1); // -1 indicates "current data in object, whether it's saved or not"
        documentXml.setName(name);
        documentXml.setFields(VersionedDataAccessWrapper.filterFields(getFieldsInOrder(), accessDetails).getXml().getFields());
        documentXml.setParts(VersionedDataAccessWrapper.filterParts(getPartsInOrder(), accessDetails).getXml().getParts());
        documentXml.setLinks(getLinks().getXml().getLinks());
        documentXml.setValidateOnSave(validateOnSave);
    }

    public void addXml(DocumentDocument.Document documentXml, long versionId, AccessDetails accessDetails) throws RepositoryException {
        Version version = getVersion(versionId);

        addNonVersionedDataToXml(documentXml, accessDetails);
        documentXml.setDataVersionId(versionId);
        documentXml.setName(version.getDocumentName());
        documentXml.setFields(VersionedDataAccessWrapper.filterFields(version.getFieldsInOrder(), accessDetails).getXml().getFields());
        documentXml.setParts(VersionedDataAccessWrapper.filterParts(version.getPartsInOrder(), accessDetails).getXml().getParts());
        documentXml.setLinks(version.getLinks().getXml().getLinks());
    }

    public void addNonVersionedDataToXml(DocumentDocument.Document documentXml, AccessDetails accessDetails) {
        if (!isNew) {
            GregorianCalendar lastModified = new GregorianCalendar();
            lastModified.setTime(this.lastModified);
            documentXml.setVariantLastModified(lastModified);
            documentXml.setVariantLastModifier(lastModifier);
            if (liveVersionId != -1)
                documentXml.setLiveVersionId(liveVersionId);
        }

        documentXml.setBranchId(branchId);
        documentXml.setLanguageId(languageId);
        documentXml.setTypeId(documentTypeId);
        documentXml.setLastVersionId(lastVersionId);
        documentXml.setRetired(retired);
        documentXml.setNewVersionState(DocumentDocument.Document.NewVersionState.Enum.forString(versionState.toString()));
        if (syncedWith != null) {
            documentXml.setNewSyncedWithLanguageId(syncedWith.getLanguageId());
            documentXml.setNewSyncedWithVersionId(syncedWith.getVersionId());
        }
        documentXml.setNewChangeType(DocumentDocument.Document.NewChangeType.Enum.forString(changeType.toString()));
        documentXml.setNewChangeComment(changeComment);
        documentXml.setVariantUpdateCount(updateCount);
        if ((accessDetails == null || accessDetails.isGranted(AclDetailPermission.SUMMARY)) && summary != null)
            documentXml.setSummary(summary);
        documentXml.setCreatedFromBranchId(createdFromBranchId);
        documentXml.setCreatedFromLanguageId(createdFromLanguageId);
        documentXml.setCreatedFromVersionId(createdFromVersionId);
        documentXml.setDocumentTypeChecksEnabled(documentTypeChecksEnabled);
        documentXml.setLastMajorChangeVersionId(lastMajorChangeVersionId);
        documentXml.setLiveMajorChangeVersionId(liveMajorChangeVersionId);


        DocumentDocument.Document.CustomFields customFieldsXml = documentXml.addNewCustomFields();
        for (Map.Entry<String, String> customField : customFields.entrySet()) {
            DocumentDocument.Document.CustomFields.CustomField customFieldXml = customFieldsXml.addNewCustomField();
            customFieldXml.setName(customField.getKey());
            customFieldXml.setValue(customField.getValue());
        }

        LockInfo lockInfo;
        try {
            lockInfo = getLockInfo(false);
        } catch (RepositoryException e) {
            throw new RuntimeException(e);
        }
        documentXml.setLockInfo(lockInfo.getXml().getLockInfo());

        long[] collectionIds = new long[documentCollections.size()];
        Iterator collectionsIt = documentCollections.values().iterator();
        int i = 0;
        while (collectionsIt.hasNext()) {
            DocumentCollection collection = (DocumentCollection)collectionsIt.next();
            collectionIds[i] = collection.getId();
            i++;
        }
        documentXml.addNewCollectionIds().setCollectionIdArray(collectionIds);
    }

    public void setName(String name) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (name == null)
            throw new NullPointerException("name may not be null.");

        if (!name.equals(this.name)) {
            this.name = name;
            nameUpdated = true;
        }
    }

    public String getName() {
        return name;
    }

    public void setPart(String partTypeName, String mimeType, byte[] data) throws DocumentTypeInconsistencyException {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (data == null)
            throw new NullPointerException("data argument cannot be null.");

        setPart(partTypeName, mimeType, new ByteArrayPartDataSource(data));

    }

    public void setPart(long partTypeId, String mimeType, byte[] data) throws DocumentTypeInconsistencyException {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (data == null)
            throw new NullPointerException("data argument cannot be null.");

        setPart(partTypeId, mimeType, new ByteArrayPartDataSource(data));
    }

    public void setPart(String partTypeName, String mimeType, PartDataSource partDataSource) throws DocumentTypeInconsistencyException {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        PartType partType;
        try {
            partType = repository.getRepositorySchema().getPartTypeByName(partTypeName, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }

        setPart(partType.getId(), mimeType, partDataSource);
    }

    public void setPart(long partTypeId, String mimeType, PartDataSource partDataSource) throws DocumentTypeInconsistencyException {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        // first check with the DocumentType if this part is allowed
        DocumentType documentType;
        try {
            documentType = repository.getRepositorySchema().getDocumentTypeById(documentTypeId, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }

        PartType partType;
        try {
            partType = repository.getRepositorySchema().getPartTypeById(partTypeId, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }

        if (mimeType == null || mimeType.length() == 0)
            throw new NullPointerException("mimeType argument cannot be null or an empty string");

        if (partDataSource == null)
            throw new NullPointerException("partDataSource argument cannot be null.");

        if (documentTypeChecksEnabled && !documentType.hasPartType(partTypeId))
            throw new DocumentTypeInconsistencyException("The PartType \"" + partType.getName() + "\" (ID: " + partTypeId + ") is not allowed on this document.");

        if (documentTypeChecksEnabled && !partType.mimeTypeAllowed(mimeType))
            throw new DocumentTypeInconsistencyException("The mime-type \"" + mimeType + "\" isn't part of the allowed mime types (" + partType.getMimeTypes() + ") required by the PartType \"" + partType.getName() + "\" (ID: " + partTypeId + ").");

        PartImpl part = new PartImpl(intimateAccess, partTypeId, lastVersionId, -1);
        PartImpl.IntimateAccess partInt = part.getIntimateAccess(documentStrategy);
        partInt.setMimeType(mimeType);
        partInt.setData(partDataSource);
        partInt.setNewOrUpdated(true, true);

        parts.put(new Long(partTypeId), part);
        partChanges = true;
    }

    public void setPartFileName(String partTypeName, String fileName) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        PartType partType;
        try {
            partType = repository.getRepositorySchema().getPartTypeByName(partTypeName, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }

        setPartFileName(partType.getId(), fileName);
    }

    public void setPartFileName(long partTypeId, String fileName) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        PartImpl part = (PartImpl)parts.get(new Long(partTypeId));
        if (part == null)
            throw new RepositoryRuntimeException("The document does not have a part for part type ID " + partTypeId);

        if ((part.getFileName() == null && fileName == null) || (part.getFileName() != null && part.getFileName().equals(fileName)))
            return;

        PartImpl.IntimateAccess partInt = part.getIntimateAccess(documentStrategy);
        partInt.setFileName(fileName);
        partInt.setNewOrUpdated(true, partInt.isDataUpdated());
        partChanges = true;
    }

    public void setPartMimeType(String partTypeName, String mimeType) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        PartType partType;
        try {
            partType = repository.getRepositorySchema().getPartTypeByName(partTypeName, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }

        setPartMimeType(partType.getId(), mimeType);
    }

    public void setPartMimeType(long partTypeId, String mimeType) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (mimeType == null)
            throw new IllegalArgumentException("Part mime-type cannot be set to null value.");

        PartImpl part = (PartImpl)parts.get(new Long(partTypeId));
        if (part == null)
            throw new RepositoryRuntimeException("The document does not have a part for part type ID " + partTypeId);

        if (part.getMimeType().equals(mimeType))
            return;

        PartImpl.IntimateAccess partInt = part.getIntimateAccess(documentStrategy);
        partInt.setMimeType(mimeType);
        partInt.setNewOrUpdated(true, partInt.isDataUpdated());
        partChanges = true;
    }

    public Parts getParts() {
        return new PartsImpl(parts.values().toArray(new Part[0]));
    }

    public Parts getPartsInOrder() {
        return new PartsImpl(orderParts(parts.values().toArray(new Part[0])));
    }

    private Part[] orderParts(Part[] parts) {
        Part[] resultList = new Part[parts.length];
        boolean[] handled = new boolean[parts.length];

        DocumentType documentType;
        try {
            documentType = repository.getRepositorySchema().getDocumentTypeById(documentTypeId, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }
        PartTypeUse[] partTypeUses = documentType.getPartTypeUses();

        int resultListPos = -1;
        for (PartTypeUse partTypeUse : partTypeUses) {
            long id = partTypeUse.getPartType().getId();
            for (int k = 0; k < parts.length; k++) {
                if (parts[k].getTypeId() == id) {
                    handled[k] = true;
                    resultListPos++;
                    resultList[resultListPos] = parts[k];
                    break;
                }
            }
        }

        for (int i = 0; i < handled.length; i++) {
            if (!handled[i]) {
                resultListPos++;
                resultList[resultListPos] = parts[i];
            }
        }

        return resultList;
    }

    private Field[] orderFields(Field[] fields) {
        Field[] resultList = new Field[fields.length];
        boolean[] handled = new boolean[fields.length];

        DocumentType documentType;
        try {
            documentType = repository.getRepositorySchema().getDocumentTypeById(documentTypeId, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }
        FieldTypeUse[] fieldTypeUses = documentType.getFieldTypeUses();

        int resultListPos = -1;
        for (FieldTypeUse fieldTypeUse : fieldTypeUses) {
            long id = fieldTypeUse.getFieldType().getId();
            for (int k = 0; k < fields.length; k++) {
                if (fields[k].getTypeId() == id) {
                    handled[k] = true;
                    resultListPos++;
                    resultList[resultListPos] = fields[k];
                    break;
                }
            }
        }

        for (int i = 0; i < handled.length; i++) {
            if (!handled[i]) {
                resultListPos++;
                resultList[resultListPos] = fields[i];
            }
        }

        return resultList;
    }

    public void deletePart(long partTypeId) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        Long key = new Long(partTypeId);
        if (parts.containsKey(key)) {
            parts.remove(key);
            partChanges = true;
        }
    }

    public void deletePart(String name) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        PartType partType;
        try {
            partType = repository.getRepositorySchema().getPartTypeByName(name, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }
        deletePart(partType.getId());
    }

    public Part getPart(long partTypeId) throws PartNotFoundException {
        Part part = parts.get(new Long(partTypeId));
        if (part == null)
            throw new PartNotFoundException(partTypeId);
        else
            return part;
    }

    public Part getPart(String name) throws PartNotFoundException {
        PartType partType;
        try {
            partType = repository.getRepositorySchema().getPartTypeByName(name, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }
        return getPart(partType.getId());
    }

    public boolean hasPart(long partTypeId) {
        Part part = parts.get(new Long(partTypeId));
        return part != null;
    }

    public boolean hasPart(String name) {
        PartType partType;
        try {
            partType = repository.getRepositorySchema().getPartTypeByName(name, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA, e);
        }
        return hasPart(partType.getId());
    }

    public void setCustomField(String name, String value) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (name == null)
            throw new RuntimeException("name argument cannot be null.");
        if (value == null)
            throw new RuntimeException("value argument cannot be null.");

        customFields.put(name, value);
        customFieldChanges = true;
    }

    public void deleteCustomField(String name) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (name == null)
            throw new RuntimeException("name argument cannot be null.");

        customFields.remove(name);
        customFieldChanges = true;
    }

    public void clearCustomFields() {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        customFields.clear();
        customFieldChanges = true;
    }

    public void clearCollections() {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        documentCollections.clear();
        documentCollectionChanges = true;
    }

    public Map<String, String> getCustomFields() {
        return new HashMap<String, String>(customFields);
    }

    public String getCustomField(String name) {
        if (name == null)
            throw new NullPointerException("name argument cannot be null.");

        return customFields.get(name);
    }

    public boolean hasCustomField(String name) {
        if (name == null)
            throw new NullPointerException("name argument cannot be null.");

        return customFields.containsKey(name);
    }

    public Links getLinks() {
        return new LinksImpl(links.toArray(new Link[links.size()]));
    }

    public void addLink(String title, String target) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        links.add(new LinkImpl(title, target));
        linkChanges = true;
    }

    public void deleteLink(int index) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        links.remove(index);
        linkChanges = true;
    }

    public void clearLinks() {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        links.clear();
        linkChanges = true;
    }

    public void validate() throws DocumentTypeInconsistencyException {
        fullConsistencyCheck();
    }

    public void setNewVersionState(VersionState versionState) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (versionState == null)
            throw new NullPointerException("versionState argument cannot be null.");

        this.versionState = versionState;
    }

    public VersionState getNewVersionState() {
        return versionState;
    }

    public synchronized Version getVersion(long versionId) throws RepositoryException {
        if (isNew)
            throw new RepositoryException("A new document variant has no versions.");
        if (versionId < 1 || versionId > lastVersionId)
            throw new VersionNotFoundException(String.valueOf(versionId), ownerDocument.getId(), String.valueOf(branchId), String.valueOf(languageId));

        checkVersionsArray();

        int arrayPos = (int)versionId - 1;
        if (versions[arrayPos] == null) {
            versions[arrayPos] = documentStrategy.loadVersion(this, versionId);
        }

        return versions[arrayPos];
    }

    public Version getLastVersion() throws RepositoryException {
        if (isNew)
            return null;

        if (lastVersionLoaded)
            return lastVersion;

        Version[] versions = getVersions().getArray();
        lastVersion = versions[versions.length - 1];
        lastVersionLoaded = true;
        return lastVersion;
    }

    public Version getLiveVersion() throws RepositoryException {
        if (isNew || liveVersionId == -1)
            return null;

        if (liveVersionLoaded)
            return liveVersion;

        checkVersionsArray();
        int liveVersionPos = (int)liveVersionId - 1; // position in the versions array
        if (versions[(int)liveVersionId - 1] != null) {
            liveVersion = versions[liveVersionPos];
        } else {
            VersionImpl newLiveVersion = documentStrategy.loadVersion(this, liveVersionId);
            versions[liveVersionPos] = newLiveVersion;
            liveVersion = newLiveVersion;
        }
        liveVersionLoaded = true;
        return liveVersion;
    }

    public long getLiveVersionId() {
        return liveVersionId;
    }

    public synchronized Versions getVersions() throws RepositoryException {
        if (isNew)
            return new VersionsImpl(new Version[0]);

        loadVersions();

        return new VersionsImpl(versions.clone());
    }

    private void loadVersions() throws RepositoryException {
        checkVersionsArray();

        VersionImpl[] loadedVersions = null;

        // check if all versions are loaded, if not load them
        // note that we don't simply replace the whole version array because some
        // versions may already be loaded fully, and otherwise that work would be lost.
        for (int i = 0; i < versions.length; i++) {
            if (versions[i] == null) {
                if (loadedVersions == null)
                    loadedVersions = documentStrategy.loadShallowVersions(this);
                versions[i] = loadedVersions[i];
            }
        }
    }

    public long getLastVersionId() {
        return lastVersionId;
    }

    private void checkVersionsArray() {
        if (versions == null) {
            versions = new VersionImpl[(int)lastVersionId];
        } else if (versions.length != lastVersionId) {
            VersionImpl[] oldVersions = versions;
            versions = new VersionImpl[(int)lastVersionId];
            System.arraycopy(oldVersions, 0, versions, 0, oldVersions.length);
        }
    }

    private void fullConsistencyCheck() throws DocumentTypeInconsistencyException {
        DocumentType documentType;
        try {
            documentType = repository.getRepositorySchema().getDocumentTypeById(documentTypeId, false, currentUser);
        } catch (RepositoryException e) {
            throw new RuntimeException(ERROR_ACCESSING_REPOSITORY_SCHEMA);
        }

        // check the parts
        PartTypeUse[] partTypeUses = documentType.getPartTypeUses();
        boolean[] hasPartType = new boolean[partTypeUses.length];

        Part[] parts = getParts().getArray();
        for (Part part : parts) {
            long partTypeId = part.getTypeId();
            int partTypeIndex = -1;
            for (int j = 0; j < partTypeUses.length; j++) {
                if (partTypeUses[j].getPartType().getId() == partTypeId) {
                    partTypeIndex = j;
                }
            }
            if (partTypeIndex == -1)
                throw new DocumentTypeInconsistencyException("The document contains a not-allowed part (PartType ID: " + partTypeId + ").");
            hasPartType[partTypeIndex] = true;
            if (!partTypeUses[partTypeIndex].getPartType().mimeTypeAllowed(part.getMimeType()))
                throw new DocumentTypeInconsistencyException("The mime-type for the part \"" + partTypeUses[partTypeIndex].getPartType().getName() + "\" isn't part of the allowed mime types (PartType ID: " + partTypeId + ").");
        }

        for (int i = 0; i < partTypeUses.length; i++) {
            if (partTypeUses[i].isRequired() && !hasPartType[i])
                throw new DocumentTypeInconsistencyException("The document doesn't have the required part \"" + partTypeUses[i].getPartType().getName() + "\" (ID: " + partTypeUses[i].getPartType().getId() + ").");
        }

        // check the fields
        FieldTypeUse[] fieldTypeUses = documentType.getFieldTypeUses();
        boolean[] hasFieldType = new boolean[fieldTypeUses.length];

        Field[] fields = getFields().getArray();
        for (Field field : fields) {
            long fieldTypeId = field.getTypeId();
            int fieldTypeIndex = -1;
            for (int j = 0; j < fieldTypeUses.length; j++) {
                if (fieldTypeUses[j].getFieldType().getId() == fieldTypeId) {
                    fieldTypeIndex = j;
                }
            }
            if (fieldTypeIndex == -1)
                throw new DocumentTypeInconsistencyException("The document contains a not-allowed field (FieldType ID: " + fieldTypeId + ").");
            hasFieldType[fieldTypeIndex] = true;
        }

        for (int i = 0; i < fieldTypeUses.length; i++) {
            if (fieldTypeUses[i].isRequired() && !hasFieldType[i])
                throw new DocumentTypeInconsistencyException("The document doesn't have the required field \"" + fieldTypeUses[i].getFieldType().getName() + "\" (ID: " + fieldTypeUses[i].getFieldType().getId() + ").");
        }
    }

    public Date getLastModified() {
        if (lastModified != null)
            return (Date)lastModified.clone();
        else
            return lastModified;
    }

    public long getLastModifier() {
        return lastModifier;
    }

    public boolean isRetired() {
        return retired;
    }

    public void setRetired(boolean retired) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (retired != this.retired) {
            this.retired = retired;
            this.retiredChanged = true;
        }
    }

    public DocumentCollections getCollections() {
        return new DocumentCollectionsImpl(documentCollections.values().toArray(new DocumentCollection[0]));
    }

    public boolean inCollection(DocumentCollection collection) {
        return documentCollections.containsKey(new Long(collection.getId()));
    }

    public boolean inCollection(long collectionId) {
        return documentCollections.containsKey(new Long(collectionId));
    }

    public void addToCollection(DocumentCollection c) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (c.getId() == -1)
            throw new IllegalArgumentException("The specified collection has not yet been saved.");

        this.documentCollectionChanges = true;
        this.documentCollections.put(new Long(c.getId()), c);
    }

    public void removeFromCollection(DocumentCollection c) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        this.documentCollectionChanges = true;
        documentCollections.remove(new Long(c.getId()));
    }

    public String getSummary() {
        return summary == null ? "" : summary;
    }

    public long getLastMajorChangeVersionId() {
        return lastMajorChangeVersionId;
    }

    public long getLiveMajorChangeVersionId() {
        return liveMajorChangeVersionId;
    }

    public long getUpdateCount() {
        return updateCount;
    }

    public long getCreatedFromBranchId() {
        return createdFromBranchId;
    }

    public long getCreatedFromLanguageId() {
        return createdFromLanguageId;
    }

    public long getCreatedFromVersionId() {
        return createdFromVersionId;
    }

    public void setDocumentTypeChecksEnabled(boolean documentTypeChecksEnabled) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        this.documentTypeChecksEnabled = documentTypeChecksEnabled;
    }

    public void setNewSyncedWithVersion(long syncedWithLanguageId, long syncedWithVersionId) throws RepositoryException {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        // NOTE: similar code to VersionImpl.setSyncedWithVersion

        if (syncedWithLanguageId == -1 || syncedWithVersionId == -1) {
            if (syncedWithLanguageId != -1 || syncedWithVersionId != -1)
                throw new IllegalArgumentException("The languageId and versionId arguments should both be -1 or not be -1 at all.");

            syncedWith = null;
            return;
        }

        if (syncedWithLanguageId == this.languageId) {
            throw new IllegalArgumentException("You can not make a document synced with a version in the same language");
        }

        // Check the language exists
        repository.getVariantManager().getLanguage(languageId, false, currentUser);

        syncedWith = new VersionKey(this.getDocumentId(), branchId, syncedWithLanguageId, syncedWithVersionId);
    }

    public VersionKey getNewSyncedWith() {
        return syncedWith;
    }

    public void setNewChangeType(ChangeType changeType) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (changeType == null)
            throw new IllegalArgumentException("Null argument: changeType");

        this.changeType = changeType;
    }

    public ChangeType getNewChangeType() {
        return changeType;
    }

    public void setNewChangeComment(String changeComment) {
        if (ownerDocument.isReadOnly())
            throw new RuntimeException(READ_ONLY_MESSAGE);

        if (changeComment != null) {
            changeComment = changeComment.trim();
            if (changeComment.length() == 0)
                changeComment = null;
        }
        
        this.changeComment = changeComment;
    }

    public String getNewChangeComment() {
        return changeComment;
    }

    /**
     * Checks whether this document needs a new version. This is the case when:
     */
    public boolean needsNewVersion() {
        // a new document always needs an initial version
        if (isNew)
            return true;

        return nameUpdated || fieldChanges || linkChanges || partChanges;
    }

    public boolean needsSaving() {
        boolean nonVersionedChanges = documentCollectionChanges || customFieldChanges || documentTypeChanged || retiredChanged;
        if (nonVersionedChanges)
            return true;
        else
            return needsNewVersion();
    }
    
    public class IntimateAccess {

        private IntimateAccess() {
        }

        public void setLockInfo(LockInfoImpl lockInfo) {
            DocumentVariantImpl.this.lockInfo = lockInfo;
        }

        public long getLastVersionId() {
            return lastVersionId;
        }

        public boolean hasCustomFieldChanges() {
            return customFieldChanges;
        }

        public void setIsNew(boolean isNew) {
            DocumentVariantImpl.this.isNew = isNew;
        }

        public boolean isNameUpdated() {
            return nameUpdated;
        }

        public boolean hasFieldChanges() {
            return fieldChanges;
        }

        public boolean hasLinkChanges() {
            return linkChanges;
        }

        public boolean hasPartChanges() {
            return partChanges;
        }

        public boolean hasCollectionChanges() {
            return documentCollectionChanges;
        }

        /**
         * Intialises required fields when loading an existing document variant.
         */
        public void load(long documentTypeId, boolean retired, long lastVersionId, long liveVersionId,
                Date lastModified, long lastModifier, long createdFromBranchId, long createdFromLanguageId,
                long createdFromVersionId, long lastMajorChangeVersionId, long liveMajorChangeVersionId, long updateCount) {
            DocumentVariantImpl.this.documentTypeId = documentTypeId;
            DocumentVariantImpl.this.retired = retired;
            DocumentVariantImpl.this.lastVersionId = lastVersionId;
            DocumentVariantImpl.this.liveVersionId = liveVersionId;
            DocumentVariantImpl.this.lastModified = lastModified;
            DocumentVariantImpl.this.lastModifier = lastModifier;
            DocumentVariantImpl.this.createdFromBranchId = createdFromBranchId;
            DocumentVariantImpl.this.createdFromLanguageId = createdFromLanguageId;
            DocumentVariantImpl.this.createdFromVersionId = createdFromVersionId;
            DocumentVariantImpl.this.lastMajorChangeVersionId = lastMajorChangeVersionId;
            DocumentVariantImpl.this.liveMajorChangeVersionId = liveMajorChangeVersionId;
            DocumentVariantImpl.this.updateCount = updateCount;
            isNew = false;
        }

        /**
         * Updates the state of this Document object after saving it, also resets
         * all 'dirty' flags.
         */
        public void saved(long lastVersionId, long liveVersionId, Date lastModified, String summary, long updateCount) {
            DocumentVariantImpl.this.lastVersionId = lastVersionId;
            DocumentVariantImpl.this.liveVersionId = liveVersionId;
            DocumentVariantImpl.this.lastModified = lastModified;
            DocumentVariantImpl.this.lastModifier = currentUser.getId();
            DocumentVariantImpl.this.updateCount = updateCount;
            DocumentVariantImpl.this.summary = summary;

            nameUpdated = false;
            partChanges = false;
            linkChanges = false;
            documentCollectionChanges = false;

            PartImpl[] parts = getPartImpls();
            for (PartImpl part : parts) {
                PartImpl.IntimateAccess partInt = part.getIntimateAccess(documentStrategy);
                partInt.setVersionId(lastVersionId);
                if (partInt.isDataUpdated())
                    partInt.setDataChangedInVersion(lastVersionId);
                partInt.setNewOrUpdated(false, false);
                partInt.setData(null);
            }
            fieldChanges = false;
            customFieldChanges = false;

            // last/live version might have changed
            liveVersion = null;
            liveVersionLoaded = false;
            lastVersion = null;
            lastVersionLoaded = false;
            isNew = false;
            retiredChanged = false;
            documentTypeChanged = false;
        }

        public DocumentStrategy getDocumentStrategy() {
            return documentStrategy;
        }

        public AuthenticatedUser getCurrentUser() {
            return currentUser;
        }

        /**
         * Sets a user field without marking the user fields as modified.
         */
        public void setCustomField(String name, String value) {
            customFields.put(name, value);
        }

        public PartImpl[] getPartImpls() {
            return parts.values().toArray(new PartImpl[0]);
        }

        public DocumentCollectionImpl[] getDocumentCollectionImpls() {
            return documentCollections.values().toArray(new DocumentCollectionImpl[0]);
        }


        public void addPart(PartImpl part) {
            parts.put(new Long(part.getTypeId()), part);
        }

        /**
         * Adds a link without marking the links as being modified.
         */
        public void addLink(LinkImpl link) {
            links.add(link);
        }

        /**
         * Sets the name of the document without altering the "name dirty" flag.
         */
        public void setName(String name) {
            if (name == null)
                throw new NullPointerException("name may not be null.");
            DocumentVariantImpl.this.name = name;
        }

        /**
         * Adds the given field. This method will not change the flag indicating
         * whether there were field changes.
         */
        public void addField(FieldImpl field) {
            fields.put(new Long(field.getTypeId()), field);
        }

        public DocumentImpl getDocument() {
            return ownerDocument;
        }

        public DocId getDocId() {
            return ownerDocument.getIntimateAccess(documentStrategy).getDocId();
        }

        public CommonRepositorySchema getRepositorySchema() {
            return repository.getRepositorySchema();
        }

        public CommonVariantManager getVariantManager() {
            return repository.getVariantManager();
        }

        /**
         * Adds the specified collection. This method will not change the flag indicating
         * whether there were collection changes.
         */
        public void addCollection(DocumentCollectionImpl collection) {
            documentCollections.put(new Long(collection.getId()), collection);
        }

        public Part[] orderParts(Part[] parts) {
            return DocumentVariantImpl.this.orderParts(parts);
        }

        public Field[] orderFields(Field[] fields) {
            return DocumentVariantImpl.this.orderFields(fields);
        }

        public void setSummary(String summary) {
            DocumentVariantImpl.this.summary = summary;
        }

        public DocumentVariantImpl getVariant() {
            return DocumentVariantImpl.this;
        }

        public void setCreatedFrom(long branchId, long languageId, long versionId) {
            createdFromBranchId = branchId;
            createdFromLanguageId = languageId;
            createdFromVersionId = versionId;
        }

        public void setStartFrom(long branchId, long languageId) {
            startBranchId = branchId;
            startLanguageId = languageId;
        }

        public long getStartBranchId() {
            return startBranchId;
        }

        public long getStartLanguageId() {
            return startLanguageId;
        }
    }

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.