blob: 9961f70b78b3a254c9b8bced8776c38eda4c793c [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.chemistry.opencmis.client.runtime;
import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNotEmpty;
import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNullOrEmpty;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.chemistry.opencmis.client.api.ObjectFactory;
import org.apache.chemistry.opencmis.client.api.ObjectId;
import org.apache.chemistry.opencmis.client.api.ObjectType;
import org.apache.chemistry.opencmis.client.api.OperationContext;
import org.apache.chemistry.opencmis.client.api.Policy;
import org.apache.chemistry.opencmis.client.api.Property;
import org.apache.chemistry.opencmis.client.api.Relationship;
import org.apache.chemistry.opencmis.client.api.Rendition;
import org.apache.chemistry.opencmis.client.api.SecondaryType;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.Ace;
import org.apache.chemistry.opencmis.commons.data.Acl;
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.RenditionData;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
import org.apache.chemistry.opencmis.commons.enums.Action;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.ExtensionLevel;
import org.apache.chemistry.opencmis.commons.enums.Updatability;
import org.apache.chemistry.opencmis.commons.spi.CmisBinding;
import org.apache.chemistry.opencmis.commons.spi.Holder;
/**
* Base class for all persistent session object impl classes.
*/
public abstract class AbstractCmisObject implements CmisObject, Serializable {
private static final long serialVersionUID = 1L;
private SessionImpl session;
private ObjectType objectType;
private List<SecondaryType> secondaryTypes;
private Map<String, Property<?>> properties;
private AllowableActions allowableActions;
private List<Rendition> renditions;
private Acl acl;
private List<Policy> policies;
private List<Relationship> relationships;
private Map<ExtensionLevel, List<CmisExtensionElement>> extensions;
private OperationContext creationContext;
private long refreshTimestamp;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
/**
* Initializes the object.
*/
protected void initialize(SessionImpl session, ObjectType objectType, ObjectData objectData,
OperationContext context) {
if (session == null) {
throw new IllegalArgumentException("Session must be set!");
}
if (objectType == null) {
throw new IllegalArgumentException("Object type must be set!");
}
if ((objectType.getPropertyDefinitions() == null) || objectType.getPropertyDefinitions().size() < 9) {
// there must be at least the 9 standard properties that all objects
// have
throw new IllegalArgumentException("Object type must have property definitions!");
}
this.session = session;
this.objectType = objectType;
this.secondaryTypes = null;
this.extensions = new EnumMap<ExtensionLevel, List<CmisExtensionElement>>(ExtensionLevel.class);
this.creationContext = new OperationContextImpl(context);
this.refreshTimestamp = System.currentTimeMillis();
ObjectFactory of = getObjectFactory();
if (objectData != null) {
// handle properties
if (objectData.getProperties() != null) {
// get secondary types
if (objectData.getProperties().getProperties() != null
&& objectData.getProperties().getProperties()
.containsKey(PropertyIds.SECONDARY_OBJECT_TYPE_IDS)) {
@SuppressWarnings("unchecked")
List<String> stids = (List<String>) objectData.getProperties().getProperties()
.get(PropertyIds.SECONDARY_OBJECT_TYPE_IDS).getValues();
if (isNotEmpty(stids)) {
secondaryTypes = new ArrayList<SecondaryType>();
for (String stid : stids) {
if (stid != null) {
ObjectType type = session.getTypeDefinition(stid);
if (type instanceof SecondaryType) {
secondaryTypes.add((SecondaryType) type);
}
}
}
}
}
this.properties = of.convertProperties(objectType, secondaryTypes, objectData.getProperties());
extensions.put(ExtensionLevel.PROPERTIES, objectData.getProperties().getExtensions());
}
// handle allowable actions
if (objectData.getAllowableActions() != null) {
this.allowableActions = objectData.getAllowableActions();
extensions.put(ExtensionLevel.ALLOWABLE_ACTIONS, objectData.getAllowableActions().getExtensions());
}
// handle renditions
if (objectData.getRenditions() != null) {
this.renditions = new ArrayList<Rendition>();
for (RenditionData rd : objectData.getRenditions()) {
this.renditions.add(of.convertRendition(getId(), rd));
}
}
// handle ACL
if (objectData.getAcl() != null) {
acl = objectData.getAcl();
extensions.put(ExtensionLevel.ACL, objectData.getAcl().getExtensions());
if (objectData.isExactAcl() != null) {
final Acl objectAcl = objectData.getAcl();
final Boolean isExact = objectData.isExactAcl();
acl = new Acl() {
@Override
public void setExtensions(List<CmisExtensionElement> extensions) {
objectAcl.setExtensions(extensions);
}
@Override
public List<CmisExtensionElement> getExtensions() {
return objectAcl.getExtensions();
}
@Override
public Boolean isExact() {
return isExact;
}
@Override
public List<Ace> getAces() {
return objectAcl.getAces();
}
};
}
}
// handle policies
if ((objectData.getPolicyIds() != null) && (objectData.getPolicyIds().getPolicyIds() != null)) {
policies = new ArrayList<Policy>();
for (String pid : objectData.getPolicyIds().getPolicyIds()) {
CmisObject policy = session.getObject(pid);
if (policy instanceof Policy) {
policies.add((Policy) policy);
}
}
extensions.put(ExtensionLevel.POLICIES, objectData.getPolicyIds().getExtensions());
}
// handle relationships
if (objectData.getRelationships() != null) {
relationships = new ArrayList<Relationship>();
for (ObjectData rod : objectData.getRelationships()) {
CmisObject relationship = of.convertObject(rod, this.creationContext);
if (relationship instanceof Relationship) {
relationships.add((Relationship) relationship);
}
}
}
extensions.put(ExtensionLevel.OBJECT, objectData.getExtensions());
}
}
/**
* Acquires a write lock.
*/
protected void writeLock() {
lock.writeLock().lock();
}
/**
* Releases a write lock.
*/
protected void writeUnlock() {
lock.writeLock().unlock();
}
/**
* Acquires a read lock.
*/
protected void readLock() {
lock.readLock().lock();
}
/**
* Releases a read lock.
*/
protected void readUnlock() {
lock.readLock().unlock();
}
/**
* Returns the session object.
*/
protected SessionImpl getSession() {
return session;
}
/**
* Returns the repository id.
*/
protected String getRepositoryId() {
return getSession().getRepositoryId();
}
/**
* Returns the object type.
*/
protected ObjectType getObjectType() {
readLock();
try {
return objectType;
} finally {
readUnlock();
}
}
/**
* Returns the binding object.
*/
protected CmisBinding getBinding() {
return getSession().getBinding();
}
/**
* Returns the object factory.
*/
protected ObjectFactory getObjectFactory() {
return getSession().getObjectFactory();
}
/**
* Returns the id of this object or throws an exception if the id is
* unknown.
*/
protected String getObjectId() {
String objectId = getId();
if (objectId == null) {
throw new IllegalStateException("Object Id is unknown!");
}
return objectId;
}
/**
* Returns the {@link OperationContext} that was used to create this object.
*/
protected OperationContext getCreationContext() {
return creationContext;
}
/**
* Returns the query name of a property.
*/
protected String getPropertyQueryName(String propertyId) {
readLock();
try {
PropertyDefinition<?> propDef = objectType.getPropertyDefinitions().get(propertyId);
if (propDef == null) {
return null;
}
return propDef.getQueryName();
} finally {
readUnlock();
}
}
// --- delete ---
public void delete() {
delete(true);
}
public void delete(boolean allVersions) {
readLock();
try {
getSession().delete(this, allVersions);
} finally {
readUnlock();
}
}
// --- update properties ---
public CmisObject updateProperties(Map<String, ?> properties) {
ObjectId objectId = updateProperties(properties, true);
if (objectId == null) {
return null;
}
if (!getObjectId().equals(objectId.getId())) {
return getSession().getObject(objectId, getCreationContext());
}
return this;
}
public ObjectId updateProperties(Map<String, ?> properties, boolean refresh) {
if (isNullOrEmpty(properties)) {
throw new IllegalArgumentException("Properties must not be empty!");
}
readLock();
String newObjectId = null;
try {
String objectId = getObjectId();
Holder<String> objectIdHolder = new Holder<String>(objectId);
String changeToken = getChangeToken();
Holder<String> changeTokenHolder = new Holder<String>(changeToken);
Set<Updatability> updatebility = EnumSet.noneOf(Updatability.class);
updatebility.add(Updatability.READWRITE);
// check if checked out
Boolean isCheckedOut = getPropertyValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT);
if (Boolean.TRUE.equals(isCheckedOut)) {
updatebility.add(Updatability.WHENCHECKEDOUT);
}
// it's time to update
getBinding().getObjectService().updateProperties(
getRepositoryId(),
objectIdHolder,
changeTokenHolder,
getObjectFactory()
.convertProperties(properties, this.objectType, this.secondaryTypes, updatebility), null);
newObjectId = objectIdHolder.getValue();
// remove the object from the cache, it has been changed
getSession().removeObjectFromCache(objectId);
} finally {
readUnlock();
}
if (refresh) {
refresh();
}
if (newObjectId == null) {
return null;
}
return getSession().createObjectId(newObjectId);
}
public CmisObject rename(String newName) {
if (newName == null || newName.length() == 0) {
throw new IllegalArgumentException("New name must not be empty!");
}
Map<String, Object> prop = new HashMap<String, Object>();
prop.put(PropertyIds.NAME, newName);
return updateProperties(prop);
}
public ObjectId rename(String newName, boolean refresh) {
if (newName == null || newName.length() == 0) {
throw new IllegalArgumentException("New name must not be empty!");
}
Map<String, Object> prop = new HashMap<String, Object>();
prop.put(PropertyIds.NAME, newName);
return updateProperties(prop, refresh);
}
// --- properties ---
public ObjectType getBaseType() {
BaseTypeId baseTypeId = getBaseTypeId();
if (baseTypeId == null) {
return null;
}
return getSession().getTypeDefinition(baseTypeId.value());
}
public BaseTypeId getBaseTypeId() {
String baseType = getPropertyValue(PropertyIds.BASE_TYPE_ID);
if (baseType == null) {
return null;
}
return BaseTypeId.fromValue(baseType);
}
public String getChangeToken() {
return getPropertyValue(PropertyIds.CHANGE_TOKEN);
}
public String getCreatedBy() {
return getPropertyValue(PropertyIds.CREATED_BY);
}
public GregorianCalendar getCreationDate() {
return getPropertyValue(PropertyIds.CREATION_DATE);
}
public String getId() {
return getPropertyValue(PropertyIds.OBJECT_ID);
}
public GregorianCalendar getLastModificationDate() {
return getPropertyValue(PropertyIds.LAST_MODIFICATION_DATE);
}
public String getLastModifiedBy() {
return getPropertyValue(PropertyIds.LAST_MODIFIED_BY);
}
public String getName() {
return getPropertyValue(PropertyIds.NAME);
}
public String getDescription() {
return getPropertyValue(PropertyIds.DESCRIPTION);
}
public List<Property<?>> getProperties() {
readLock();
try {
return Collections.unmodifiableList(new ArrayList<Property<?>>(this.properties.values()));
} finally {
readUnlock();
}
}
@SuppressWarnings("unchecked")
public <T> Property<T> getProperty(String id) {
readLock();
try {
return (Property<T>) this.properties.get(id);
} finally {
readUnlock();
}
}
@SuppressWarnings("unchecked")
public <T> T getPropertyValue(String id) {
Property<T> property = getProperty(id);
if (property == null) {
return null;
}
// explicit cast needed by the Sun compiler
return (T) property.getValue();
}
public ObjectType getType() {
readLock();
try {
return this.objectType;
} finally {
readUnlock();
}
}
public List<SecondaryType> getSecondaryTypes() {
readLock();
try {
return this.secondaryTypes;
} finally {
readUnlock();
}
}
public List<ObjectType> findObjectType(String id) {
List<ObjectType> result = null;
readLock();
try {
if (objectType.getPropertyDefinitions().containsKey(id)) {
result = new ArrayList<ObjectType>();
result.add(objectType);
}
if (secondaryTypes != null) {
for (SecondaryType secondaryType : secondaryTypes) {
if (secondaryType.getPropertyDefinitions() != null
&& secondaryType.getPropertyDefinitions().containsKey(id)) {
if (result == null) {
result = new ArrayList<ObjectType>();
}
result.add(secondaryType);
}
}
}
} finally {
readUnlock();
}
return result;
}
// --- allowable actions ---
public AllowableActions getAllowableActions() {
readLock();
try {
return this.allowableActions;
} finally {
readUnlock();
}
}
public boolean hasAllowableAction(Action action) {
if (action == null) {
throw new IllegalArgumentException("Action must be set!");
}
AllowableActions currentAllowableActions = getAllowableActions();
if (currentAllowableActions == null || currentAllowableActions.getAllowableActions() == null) {
throw new IllegalStateException("Allowable Actions are not available!");
}
return currentAllowableActions.getAllowableActions().contains(action);
}
// --- renditions ---
public List<Rendition> getRenditions() {
readLock();
try {
return this.renditions;
} finally {
readUnlock();
}
}
// --- ACL ---
public Acl getAcl(boolean onlyBasicPermissions) {
String objectId = getObjectId();
return getBinding().getAclService().getAcl(getRepositoryId(), objectId, onlyBasicPermissions, null);
}
public Acl applyAcl(List<Ace> addAces, List<Ace> removeAces, AclPropagation aclPropagation) {
Acl result = getSession().applyAcl(this, addAces, removeAces, aclPropagation);
refresh();
return result;
}
public Acl addAcl(List<Ace> addAces, AclPropagation aclPropagation) {
return applyAcl(addAces, null, aclPropagation);
}
public Acl removeAcl(List<Ace> removeAces, AclPropagation aclPropagation) {
return applyAcl(null, removeAces, aclPropagation);
}
public Acl setAcl(List<Ace> aces) {
Acl result = getSession().setAcl(this, aces);
refresh();
return result;
}
public Acl getAcl() {
readLock();
try {
return this.acl;
} finally {
readUnlock();
}
}
public Set<String> getPermissionsForPrincipal(String principalId) {
if (principalId == null) {
throw new IllegalArgumentException("Principal must be set!");
}
Acl currentAcl = getAcl();
if (currentAcl == null) {
throw new IllegalStateException("ACLs are not available!");
}
if (isNullOrEmpty(acl.getAces())) {
return Collections.emptySet();
}
HashSet<String> result = new HashSet<String>();
for (Ace ace : acl.getAces()) {
if (principalId.equals(ace.getPrincipalId()) && ace.getPermissions() != null) {
result.addAll(ace.getPermissions());
}
}
return result;
}
public Set<String> getPermissonsForPrincipal(String principalId) {
return getPermissionsForPrincipal(principalId);
}
// --- policies ---
public void applyPolicy(ObjectId... policyIds) {
readLock();
try {
getSession().applyPolicy(this, policyIds);
} finally {
readUnlock();
}
refresh();
}
public void removePolicy(ObjectId... policyIds) {
readLock();
try {
getSession().removePolicy(this, policyIds);
} finally {
readUnlock();
}
refresh();
}
public List<Policy> getPolicies() {
readLock();
try {
return this.policies;
} finally {
readUnlock();
}
}
// --- relationships ---
public List<Relationship> getRelationships() {
readLock();
try {
return this.relationships;
} finally {
readUnlock();
}
}
// --- extensions ---
public List<CmisExtensionElement> getExtensions(ExtensionLevel level) {
List<CmisExtensionElement> ext = extensions.get(level);
if (ext == null) {
return null;
}
return Collections.unmodifiableList(ext);
}
// --- adapters ---
public <T> T getAdapter(Class<T> adapterInterface) {
return null;
}
// --- other ---
public long getRefreshTimestamp() {
readLock();
try {
return this.refreshTimestamp;
} finally {
readUnlock();
}
}
public void refresh() {
writeLock();
try {
String objectId = getObjectId();
OperationContext oc = getCreationContext();
// get the latest data from the repository
ObjectData objectData = getSession()
.getBinding()
.getObjectService()
.getObject(getRepositoryId(), objectId, oc.getFilterString(), oc.isIncludeAllowableActions(),
oc.getIncludeRelationships(), oc.getRenditionFilterString(), oc.isIncludePolicies(),
oc.isIncludeAcls(), null);
// reset this object
initialize(session, session.getTypeDefinition(objectType.getId()), objectData, creationContext);
} finally {
writeUnlock();
}
}
public void refreshIfOld(long durationInMillis) {
writeLock();
try {
if (this.refreshTimestamp < System.currentTimeMillis() - durationInMillis) {
refresh();
}
} finally {
writeUnlock();
}
}
@Override
public String toString() {
readLock();
try {
if (objectType == null) {
return "<unknown>";
}
return objectType.getBaseTypeId() + " (" + objectType.getId() + "): " + getId();
} finally {
readUnlock();
}
}
}