/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) 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:
* Florent Guillaume
*/
package org.eclipse.ecr.core.storage.sql.coremodel;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.core.NXCore;
import org.eclipse.ecr.core.api.DocumentException;
import org.eclipse.ecr.core.api.Lock;
import org.eclipse.ecr.core.api.model.DocumentPart;
import org.eclipse.ecr.core.api.model.Property;
import org.eclipse.ecr.core.lifecycle.LifeCycle;
import org.eclipse.ecr.core.lifecycle.LifeCycleException;
import org.eclipse.ecr.core.lifecycle.LifeCycleService;
import org.eclipse.ecr.core.model.Document;
import org.eclipse.ecr.core.model.DocumentIterator;
import org.eclipse.ecr.core.model.EmptyDocumentIterator;
import org.eclipse.ecr.core.model.Repository;
import org.eclipse.ecr.core.model.Session;
import org.eclipse.ecr.core.schema.DocumentType;
import org.eclipse.ecr.core.schema.types.ComplexType;
import org.eclipse.ecr.core.schema.types.CompositeType;
import org.eclipse.ecr.core.storage.sql.Model;
import org.eclipse.ecr.core.storage.sql.Node;
import org.eclipse.ecr.core.storage.sql.coremodel.SQLDocumentVersion.VersionNotModifiableException;
/**
* @author Florent Guillaume
*/
public class SQLDocumentLive extends SQLComplexProperty implements SQLDocument {
private static final Log log = LogFactory.getLog(SQLDocumentLive.class);
/** Mixin types, updated when facets change. */
protected final List<CompositeType> mixinTypes;
protected SQLDocumentLive(Node node, ComplexType type,
List<CompositeType> mixinTypes, SQLSession session, boolean readonly) {
super(node, type, session, readonly);
this.mixinTypes = mixinTypes;
}
/*
* ----- SQLDocument -----
*/
// getNode in SQLComplexProperty
// checkWritable in SQLBaseProperty
@Override
public org.eclipse.ecr.core.model.Property getACLProperty()
throws DocumentException {
return session.makeACLProperty(getNode());
}
/*
* ----- org.eclipse.ecr.core.model.Document -----
*/
// public String getName(); from SQLComplexProperty
//
@Override
public DocumentType getType() {
return (DocumentType) type;
}
@Override
public Session getSession() {
return session;
}
@Override
public boolean isFolder() {
return type == null // null document
|| ((DocumentType) type).isFolder();
}
@Override
public String getUUID() {
return getNode().getId().toString();
}
@Override
public Document getParent() throws DocumentException {
return session.getParent(getNode());
}
@Override
public String getPath() throws DocumentException {
return session.getPath(getNode());
}
@Override
public Calendar getLastModified() {
throw new UnsupportedOperationException("unused");
}
@Override
public boolean isProxy() {
return false;
}
@Override
public Repository getRepository() {
return session.getRepository();
}
@Override
public void remove() throws DocumentException {
session.remove(getNode());
}
@Override
public void save() throws DocumentException {
session.save();
}
/**
* Reads into the {@link DocumentPart} the values from this
* {@link SQLDocument}.
*/
@Override
public void readDocumentPart(DocumentPart dp) throws Exception {
for (Property property : dp) {
property.init((Serializable) getPropertyValue(property.getName()));
}
}
@Override
public org.eclipse.ecr.core.model.Property getProperty(String name)
throws DocumentException {
return session.makeProperty(getNode(), name, (ComplexType) type,
mixinTypes, readonly);
}
/**
* Writes into this {@link SQLDocument} the values from the
* {@link DocumentPart}.
*/
@Override
public void writeDocumentPart(DocumentPart dp) throws Exception {
for (Property property : dp) {
String name = property.getName();
Serializable value = property.getValueForWrite();
try {
setPropertyValue(name, value);
} catch (VersionNotModifiableException e) {
// workaround: only dublincore is allowed to change and
// it contains only scalars and arrays
// cf also SQLSimpleProperty.VERSION_WRITABLE_PROPS
if (!name.startsWith("dc:")) {
throw e;
}
// ignore if value is unchanged
Object oldValue = getPropertyValue(name);
if (same(value, oldValue)) {
continue;
}
if (value == null || oldValue == null
|| !sameArray(value, oldValue)) {
throw e;
}
}
}
clearDirtyFlags(dp);
}
protected static boolean same(Object a, Object b) {
if (a == null) {
return b == null;
} else {
return a.equals(b);
}
}
protected static boolean sameArray(Object a, Object b) {
Class<?> acls = a.getClass();
Class<?> bcls = b.getClass();
if (!acls.isArray() || !bcls.isArray()
|| Array.getLength(a) != Array.getLength(b)) {
return false;
}
for (int i = 0; i < Array.getLength(a); i++) {
if (!same(Array.get(a, i), Array.get(b, i))) {
return false;
}
}
return true;
}
protected static void clearDirtyFlags(Property property) {
if (property.isContainer()) {
for (Property p : property) {
clearDirtyFlags(p);
}
}
property.clearDirtyFlags();
}
protected static final Map<String, String> systemPropNameMap;
static {
systemPropNameMap = new HashMap<String, String>();
systemPropNameMap.put(BINARY_TEXT_SYS_PROP,
Model.FULLTEXT_BINARYTEXT_PROP);
systemPropNameMap.put(FULLTEXT_JOBID_SYS_PROP,
Model.FULLTEXT_JOBID_PROP);
}
@Override
public <T extends Serializable> void setSystemProp(String name, T value)
throws DocumentException {
String propertyName = systemPropNameMap.get(name);
if (propertyName == null) {
throw new DocumentException("Unknown system property: " + name);
}
getProperty(propertyName).setValue(value);
}
@Override
@SuppressWarnings("unchecked")
public <T extends Serializable> T getSystemProp(String name, Class<T> type)
throws DocumentException {
String propertyName = systemPropNameMap.get(name);
if (propertyName == null) {
throw new DocumentException("Unknown system property: " + name);
}
Object value = getProperty(propertyName).getValue();
if (value == null) {
if (type == Boolean.class) {
value = Boolean.FALSE;
} else if (type == Long.class) {
value = Long.valueOf(0);
}
}
return (T) value;
}
/*
* ----- LifeCycle -----
*/
@Override
public String getLifeCyclePolicy() throws LifeCycleException {
try {
return getString(Model.MISC_LIFECYCLE_POLICY_PROP);
} catch (DocumentException e) {
throw new LifeCycleException("Failed to get policy", e);
}
}
@Override
public void setLifeCyclePolicy(String policy) throws LifeCycleException {
try {
setString(Model.MISC_LIFECYCLE_POLICY_PROP, policy);
} catch (DocumentException e) {
throw new LifeCycleException("Failed to set policy", e);
}
}
@Override
public String getLifeCycleState() throws LifeCycleException {
try {
return getString(Model.MISC_LIFECYCLE_STATE_PROP);
} catch (DocumentException e) {
throw new LifeCycleException("Failed to get state", e);
}
}
@Override
public void setCurrentLifeCycleState(String state)
throws LifeCycleException {
try {
setString(Model.MISC_LIFECYCLE_STATE_PROP, state);
} catch (DocumentException e) {
throw new LifeCycleException("Failed to set state", e);
}
}
@Override
public boolean followTransition(String transition)
throws LifeCycleException {
LifeCycleService service = NXCore.getLifeCycleService();
if (service == null) {
throw new LifeCycleException("LifeCycleService not available");
}
service.followTransition(this, transition);
return true;
}
@Override
public Collection<String> getAllowedStateTransitions()
throws LifeCycleException {
LifeCycleService service = NXCore.getLifeCycleService();
if (service == null) {
throw new LifeCycleException("LifeCycleService not available");
}
LifeCycle lifeCycle = service.getLifeCycleFor(this);
if (lifeCycle == null) {
return Collections.emptyList();
}
return lifeCycle.getAllowedStateTransitionsFrom(getLifeCycleState());
}
/*
* ----- org.eclipse.ecr.core.model.Lockable -----
*/
@Override
public Lock getLock() throws DocumentException {
return session.getLock(getNode());
}
@Override
public Lock setLock(Lock lock) throws DocumentException {
return session.setLock(getNode(), lock);
}
@Override
public Lock removeLock(String owner) throws DocumentException {
return session.removeLock(getNode(), owner);
}
/*
* ----- org.eclipse.ecr.core.versioning.VersionableDocument -----
*/
@Override
public boolean isVersion() {
return false;
}
@Override
public Document getBaseVersion() throws DocumentException {
if (isCheckedOut()) {
return null;
}
String id = getString(Model.MAIN_BASE_VERSION_PROP);
if (id == null) {
// shouldn't happen
return null;
}
return session.getDocumentByUUID(id);
}
@Override
public String getVersionSeriesId() throws DocumentException {
return getUUID();
}
@Override
public Document getSourceDocument() throws DocumentException {
return this;
}
@Override
public Document checkIn(String label, String checkinComment)
throws DocumentException {
return session.checkIn(getNode(), label, checkinComment);
}
@Override
public void checkOut() throws DocumentException {
session.checkOut(getNode());
}
@Override
public boolean isCheckedOut() throws DocumentException {
return !getBoolean(Model.MAIN_CHECKED_IN_PROP);
}
@Override
public boolean isMajorVersion() throws DocumentException {
return false;
}
@Override
public boolean isLatestVersion() throws DocumentException {
return false;
}
@Override
public boolean isLatestMajorVersion() throws DocumentException {
return false;
}
@Override
public boolean isVersionSeriesCheckedOut() throws DocumentException {
return isCheckedOut();
}
@Override
public String getVersionLabel() throws DocumentException {
return getString(Model.VERSION_LABEL_PROP);
}
@Override
public String getCheckinComment() throws DocumentException {
return getString(Model.VERSION_DESCRIPTION_PROP);
}
@Override
public Document getWorkingCopy() throws DocumentException {
return this;
}
@Override
public Calendar getVersionCreationDate() throws DocumentException {
return (Calendar) getProperty(Model.VERSION_CREATED_PROP).getValue();
}
@Override
public void restore(Document version) throws DocumentException {
if (!version.isVersion()) {
throw new DocumentException("Cannot restore a non-version: "
+ version);
}
session.restore(getNode(), ((SQLDocument) version).getNode());
}
@Override
public List<String> getVersionsIds() throws DocumentException {
String versionSeriesId = getVersionSeriesId();
Collection<Document> versions = session.getVersions(versionSeriesId);
List<String> ids = new ArrayList<String>(versions.size());
for (Document version : versions) {
ids.add(version.getUUID());
}
return ids;
}
@Override
public Document getVersion(String label) throws DocumentException {
String versionSeriesId = getVersionSeriesId();
return session.getVersionByLabel(versionSeriesId, label);
}
@Override
public List<Document> getVersions() throws DocumentException {
String versionSeriesId = getVersionSeriesId();
return session.getVersions(versionSeriesId);
}
@Override
public Document getLastVersion() throws DocumentException {
String versionSeriesId = getVersionSeriesId();
return session.getLastVersion(versionSeriesId);
}
@Override
public boolean hasVersions() throws DocumentException {
log.error("hasVersions unimplemented, returning false");
return false;
// XXX TODO
// throw new UnsupportedOperationException();
}
/*
* ----- org.eclipse.ecr.core.model.DocumentContainer -----
*/
@Override
public Document resolvePath(String path) throws DocumentException {
if (path == null) {
throw new IllegalArgumentException();
}
if (path.length() == 0) {
return this;
}
// this API doesn't take absolute paths
if (path.startsWith("/")) {
// TODO log warning
path = path.substring(1);
}
return session.resolvePath(getNode(), path);
}
@Override
public Document getChild(String name) throws DocumentException {
return session.getChild(getNode(), name);
}
@Override
public Iterator<Document> getChildren() throws DocumentException {
return getChildren(0);
}
@Override
public DocumentIterator getChildren(int start) throws DocumentException {
if (!isFolder()) {
return EmptyDocumentIterator.INSTANCE;
}
List<Document> children = session.getChildren(getNode());
if (start < 0) {
throw new IllegalArgumentException(String.valueOf(start));
}
if (start >= children.size()) {
return EmptyDocumentIterator.INSTANCE;
}
return new SQLDocumentListIterator(children.subList(start,
children.size()));
}
@Override
public List<String> getChildrenIds() throws DocumentException {
if (!isFolder()) {
return Collections.emptyList();
}
// not optimized as this method doesn't seem to be used
List<Document> children = session.getChildren(getNode());
List<String> ids = new ArrayList<String>(children.size());
for (Document child : children) {
ids.add(child.getUUID());
}
return ids;
}
@Override
public boolean hasChild(String name) throws DocumentException {
if (!isFolder()) {
return false;
}
return session.hasChild(getNode(), name);
}
@Override
public boolean hasChildren() throws DocumentException {
if (!isFolder()) {
return false;
}
return session.hasChildren(getNode());
}
@Override
public Document addChild(String name, String typeName)
throws DocumentException {
if (!isFolder()) {
throw new IllegalArgumentException("Not a folder");
}
return session.addChild(getNode(), name, null, typeName);
}
@Override
public void orderBefore(String src, String dest) throws DocumentException {
SQLDocument srcDoc = (SQLDocument) getChild(src);
if (srcDoc == null) {
throw new DocumentException("Document " + this + " has no child: "
+ src);
}
SQLDocument destDoc;
if (dest == null) {
destDoc = null;
} else {
destDoc = (SQLDocument) getChild(dest);
if (destDoc == null) {
throw new DocumentException("Document " + this
+ " has no child: " + dest);
}
}
session.orderBefore(getNode(), srcDoc.getNode(), destDoc == null ? null
: destDoc.getNode());
}
@Override
public void removeChild(String name) throws DocumentException {
if (!isFolder()) {
return; // ignore non folder documents XXX urgh
}
Document doc = getChild(name);
doc.remove();
}
@Override
public Set<String> getAllFacets() {
return getNode().getAllMixinTypes();
}
@Override
public String[] getFacets() {
return getNode().getMixinTypes();
}
@Override
public boolean hasFacet(String facet) {
return getNode().hasMixinType(facet);
}
@Override
public boolean addFacet(String facet) throws DocumentException {
try {
boolean added = getNode().addMixinType(facet);
if (added) {
mixinTypes.add(session.getTypeManager().getFacet(facet));
}
return added;
} catch (IllegalArgumentException e) {
throw new DocumentException(e);
}
}
@Override
public boolean removeFacet(String facet) throws DocumentException {
boolean removed = getNode().removeMixinType(facet);
if (removed) {
for (Iterator<CompositeType> it = mixinTypes.iterator(); it.hasNext();) {
if (it.next().getName().equals(facet)) {
it.remove();
break;
}
}
}
return removed;
}
/*
* ----- PropertyContainer inherited from SQLComplexProperty -----
*/
/*
* ----- toString/equals/hashcode -----
*/
@Override
public String toString() {
return getClass().getSimpleName() + '(' + getName() + ',' + getUUID()
+ ')';
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other instanceof SQLDocumentLive) {
return equals((SQLDocumentLive) other);
}
return false;
}
private boolean equals(SQLDocumentLive other) {
return getNode().equals(other.getNode());
}
@Override
public int hashCode() {
return getNode().hashCode();
}
}
class SQLDocumentListIterator implements DocumentIterator {
private final int size;
private final Iterator<Document> iterator;
public SQLDocumentListIterator(List<Document> list) {
size = list.size();
iterator = list.iterator();
}
@Override
public long getSize() {
return size;
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Document next() {
return iterator.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}