/* * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: */ package org.nuxeo.ecm.core.opencmis.impl.server; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Locale; import javax.servlet.http.HttpServletRequest; import org.apache.chemistry.opencmis.commons.PropertyIds; import org.apache.chemistry.opencmis.commons.data.ContentStream; import org.apache.chemistry.opencmis.commons.data.PropertyBoolean; import org.apache.chemistry.opencmis.commons.data.PropertyData; import org.apache.chemistry.opencmis.commons.data.PropertyDateTime; import org.apache.chemistry.opencmis.commons.data.PropertyDecimal; import org.apache.chemistry.opencmis.commons.data.PropertyHtml; import org.apache.chemistry.opencmis.commons.data.PropertyId; import org.apache.chemistry.opencmis.commons.data.PropertyInteger; import org.apache.chemistry.opencmis.commons.data.PropertyString; import org.apache.chemistry.opencmis.commons.data.PropertyUri; import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; import org.apache.chemistry.opencmis.commons.enums.Cardinality; import org.apache.chemistry.opencmis.commons.enums.DateTimeFormat; import org.apache.chemistry.opencmis.commons.enums.PropertyType; import org.apache.chemistry.opencmis.commons.enums.Updatability; import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException; import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException; import org.apache.chemistry.opencmis.commons.impl.Constants; import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamHashImpl; import org.apache.chemistry.opencmis.commons.server.CallContext; import org.apache.chemistry.opencmis.server.shared.HttpUtils; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.automation.core.util.ComplexPropertyJSONEncoder; import org.nuxeo.ecm.automation.core.util.ComplexTypeJSONDecoder; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.Blobs; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentRef; import org.nuxeo.ecm.core.api.IdRef; import org.nuxeo.ecm.core.api.blobholder.BlobHolder; import org.nuxeo.ecm.core.api.model.Property; import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; import org.nuxeo.ecm.core.api.model.impl.ComplexProperty; import org.nuxeo.ecm.core.io.download.DownloadService; import org.nuxeo.ecm.core.schema.DocumentType; import org.nuxeo.ecm.core.schema.FacetNames; import org.nuxeo.ecm.core.schema.SchemaManager; import org.nuxeo.ecm.core.schema.types.ComplexType; import org.nuxeo.ecm.core.schema.types.CompositeType; import org.nuxeo.ecm.core.schema.types.ListType; import org.nuxeo.ecm.core.schema.types.Type; import org.nuxeo.runtime.api.Framework; import com.google.common.collect.Iterators; /** * Nuxeo implementation of an object's property, backed by a property of a {@link DocumentModel}. */ public abstract class NuxeoPropertyData<T> extends NuxeoPropertyDataBase<T> { private static final Log log = LogFactory.getLog(NuxeoPropertyData.class); protected final String name; protected final boolean readOnly; protected final CallContext callContext; public NuxeoPropertyData(PropertyDefinition<T> propertyDefinition, DocumentModel doc, String name, boolean readOnly, CallContext callContext) { super(propertyDefinition, doc); this.name = name; this.readOnly = readOnly; this.callContext = callContext; } /** * Factory for a new Property. */ @SuppressWarnings("unchecked") public static <U> PropertyData<U> construct(NuxeoObjectData data, PropertyDefinition<U> pd, CallContext callContext) { DocumentModel doc = data.doc; String name = pd.getId(); if (PropertyIds.OBJECT_ID.equals(name)) { return (PropertyData<U>) new NuxeoPropertyIdDataFixed((PropertyDefinition<String>) pd, doc.getId()); } else if (PropertyIds.OBJECT_TYPE_ID.equals(name)) { return (PropertyData<U>) new NuxeoPropertyIdDataFixed((PropertyDefinition<String>) pd, NuxeoTypeHelper.mappedId(doc.getType())); } else if (PropertyIds.BASE_TYPE_ID.equals(name)) { return (PropertyData<U>) new NuxeoPropertyIdDataFixed((PropertyDefinition<String>) pd, NuxeoTypeHelper.getBaseTypeId(doc).value()); } else if (PropertyIds.DESCRIPTION.equals(name)) { return (PropertyData<U>) new NuxeoPropertyStringData((PropertyDefinition<String>) pd, doc, NuxeoTypeHelper.NX_DC_DESCRIPTION, false, callContext); } else if (PropertyIds.CREATED_BY.equals(name)) { return (PropertyData<U>) new NuxeoPropertyStringData((PropertyDefinition<String>) pd, doc, NuxeoTypeHelper.NX_DC_CREATOR, true, callContext); } else if (PropertyIds.CREATION_DATE.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDateTimeData((PropertyDefinition<GregorianCalendar>) pd, doc, NuxeoTypeHelper.NX_DC_CREATED, true, callContext); } else if (PropertyIds.LAST_MODIFIED_BY.equals(name)) { return (PropertyData<U>) new NuxeoPropertyStringData((PropertyDefinition<String>) pd, doc, NuxeoTypeHelper.NX_DC_LAST_CONTRIBUTOR, true, callContext); } else if (PropertyIds.LAST_MODIFICATION_DATE.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDateTimeData((PropertyDefinition<GregorianCalendar>) pd, doc, NuxeoTypeHelper.NX_DC_MODIFIED, true, callContext); } else if (PropertyIds.CHANGE_TOKEN.equals(name)) { return (PropertyData<U>) new NuxeoPropertyStringDataFixed((PropertyDefinition<String>) pd, doc.getChangeToken()); } else if (PropertyIds.NAME.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataName((PropertyDefinition<String>) pd, doc); } else if (PropertyIds.IS_IMMUTABLE.equals(name)) { // TODO check write return (PropertyData<U>) new NuxeoPropertyBooleanDataFixed((PropertyDefinition<Boolean>) pd, Boolean.FALSE); } else if (PropertyIds.IS_LATEST_VERSION.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataIsLatestVersion((PropertyDefinition<Boolean>) pd, doc); } else if (PropertyIds.IS_LATEST_MAJOR_VERSION.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataIsLatestMajorVersion((PropertyDefinition<Boolean>) pd, doc); } else if (PropertyIds.IS_MAJOR_VERSION.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataIsMajorVersion((PropertyDefinition<Boolean>) pd, doc); } else if (PropertyIds.VERSION_LABEL.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataVersionLabel((PropertyDefinition<String>) pd, doc); } else if (PropertyIds.VERSION_SERIES_ID.equals(name)) { // doesn't change once computed, no need to have a dynamic prop String versionSeriesId = doc.getVersionSeriesId(); return (PropertyData<U>) new NuxeoPropertyIdDataFixed((PropertyDefinition<String>) pd, versionSeriesId); } else if (PropertyIds.IS_VERSION_SERIES_CHECKED_OUT.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataIsVersionSeriesCheckedOut((PropertyDefinition<Boolean>) pd, doc); } else if (PropertyIds.VERSION_SERIES_CHECKED_OUT_BY.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataVersionSeriesCheckedOutBy((PropertyDefinition<String>) pd, doc, callContext); } else if (PropertyIds.VERSION_SERIES_CHECKED_OUT_ID.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataVersionSeriesCheckedOutId((PropertyDefinition<String>) pd, doc); } else if (NuxeoTypeHelper.NX_ISVERSION.equals(name)) { return (PropertyData<U>) new NuxeoPropertyBooleanDataFixed((PropertyDefinition<Boolean>) pd, Boolean.valueOf(doc.isVersion())); } else if (NuxeoTypeHelper.NX_ISCHECKEDIN.equals(name)) { boolean co = doc.isCheckedOut(); return (PropertyData<U>) new NuxeoPropertyBooleanDataFixed((PropertyDefinition<Boolean>) pd, Boolean.valueOf(!co)); } else if (PropertyIds.IS_PRIVATE_WORKING_COPY.equals(name)) { boolean co = doc.isCheckedOut(); return (PropertyData<U>) new NuxeoPropertyBooleanDataFixed((PropertyDefinition<Boolean>) pd, Boolean.valueOf(co)); } else if (PropertyIds.CHECKIN_COMMENT.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataCheckInComment((PropertyDefinition<String>) pd, doc); } else if (PropertyIds.CONTENT_STREAM_LENGTH.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataContentStreamLength((PropertyDefinition<BigInteger>) pd, doc); } else if (NuxeoTypeHelper.NX_DIGEST.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataContentStreamDigest((PropertyDefinition<String>) pd, doc); } else if (PropertyIds.CONTENT_STREAM_HASH.equals(name)) { String digest = new NuxeoPropertyDataContentStreamDigest((PropertyDefinition<String>) pd, doc).getFirstValue(); List<String> hashes; if (digest == null) { hashes = new ArrayList<String>(); } else { hashes = Arrays.asList(new ContentStreamHashImpl(ContentStreamHashImpl.ALGORITHM_MD5, digest).getPropertyValue()); } return (PropertyData<U>) new NuxeoPropertyDataContentStreamHash((PropertyDefinition<String>) pd, hashes); } else if (PropertyIds.CONTENT_STREAM_MIME_TYPE.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataContentStreamMimeType((PropertyDefinition<String>) pd, doc); } else if (PropertyIds.CONTENT_STREAM_FILE_NAME.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataContentStreamFileName((PropertyDefinition<String>) pd, doc); } else if (PropertyIds.CONTENT_STREAM_ID.equals(name)) { return (PropertyData<U>) new NuxeoPropertyIdDataFixed((PropertyDefinition<String>) pd, null); } else if (PropertyIds.PARENT_ID.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataParentId((PropertyDefinition<String>) pd, doc); } else if (NuxeoTypeHelper.NX_PARENT_ID.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataParentId((PropertyDefinition<String>) pd, doc); } else if (NuxeoTypeHelper.NX_PATH_SEGMENT.equals(name)) { return (PropertyData<U>) new NuxeoPropertyStringDataFixed((PropertyDefinition<String>) pd, doc.getName()); } else if (NuxeoTypeHelper.NX_POS.equals(name)) { return (PropertyData<U>) new NuxeoPropertyIntegerDataFixed((PropertyDefinition<BigInteger>) pd, doc.getPos()); } else if (PropertyIds.PATH.equals(name)) { return (PropertyData<U>) new NuxeoPropertyDataPath((PropertyDefinition<String>) pd, doc); } else if (PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS.equals(name)) { return (PropertyData<U>) new NuxeoPropertyIdMultiDataFixed((PropertyDefinition<String>) pd, Collections.<String> emptyList()); } else if (PropertyIds.SOURCE_ID.equals(name)) { return (PropertyData<U>) new NuxeoPropertyIdData((PropertyDefinition<String>) pd, doc, NuxeoTypeHelper.NX_REL_SOURCE, false, callContext); } else if (PropertyIds.TARGET_ID.equals(name)) { return (PropertyData<U>) new NuxeoPropertyIdData((PropertyDefinition<String>) pd, doc, NuxeoTypeHelper.NX_REL_TARGET, false, callContext); } else if (PropertyIds.POLICY_TEXT.equals(name)) { return (PropertyData<U>) new NuxeoPropertyStringDataFixed((PropertyDefinition<String>) pd, null); } else if (PropertyIds.SECONDARY_OBJECT_TYPE_IDS.equals(name)) { List<String> facets = getSecondaryTypeIds(doc); return (PropertyData<U>) new NuxeoPropertyIdMultiDataFixed((PropertyDefinition<String>) pd, facets); } else if (NuxeoTypeHelper.NX_FACETS.equals(name)) { List<String> facets = getFacets(doc); return (PropertyData<U>) new NuxeoPropertyIdMultiDataFixed((PropertyDefinition<String>) pd, facets); } else if (NuxeoTypeHelper.NX_LIFECYCLE_STATE.equals(name)) { String state = doc.getCurrentLifeCycleState(); return (PropertyData<U>) new NuxeoPropertyStringDataFixed((PropertyDefinition<String>) pd, state); } else { boolean readOnly = pd.getUpdatability() != Updatability.READWRITE; // TODO WHEN_CHECKED_OUT, ON_CREATE switch (pd.getPropertyType()) { case BOOLEAN: return (PropertyData<U>) new NuxeoPropertyBooleanData((PropertyDefinition<Boolean>) pd, doc, name, readOnly, callContext); case DATETIME: return (PropertyData<U>) new NuxeoPropertyDateTimeData((PropertyDefinition<GregorianCalendar>) pd, doc, name, readOnly, callContext); case DECIMAL: return (PropertyData<U>) new NuxeoPropertyDecimalData((PropertyDefinition<BigDecimal>) pd, doc, name, readOnly, callContext); case HTML: return (PropertyData<U>) new NuxeoPropertyHtmlData((PropertyDefinition<String>) pd, doc, name, readOnly, callContext); case ID: return (PropertyData<U>) new NuxeoPropertyIdData((PropertyDefinition<String>) pd, doc, name, readOnly, callContext); case INTEGER: return (PropertyData<U>) new NuxeoPropertyIntegerData((PropertyDefinition<BigInteger>) pd, doc, name, readOnly, callContext); case STRING: return (PropertyData<U>) new NuxeoPropertyStringData((PropertyDefinition<String>) pd, doc, name, readOnly, callContext); case URI: return (PropertyData<U>) new NuxeoPropertyUriData((PropertyDefinition<String>) pd, doc, name, readOnly, callContext); default: throw new AssertionError(pd.getPropertyType().toString()); } } } /** Gets the doc's relevant facets. */ public static List<String> getFacets(DocumentModel doc) { List<String> facets = new ArrayList<>(); SchemaManager schemaManager = Framework.getService(SchemaManager.class); for (String facet : doc.getFacets()) { // immutable facet is not actually stored or registered if (!FacetNames.IMMUTABLE.equals(facet)) { CompositeType facetType = schemaManager.getFacet(facet); if (facetType != null) { facets.add(facet); } else { log.warn("The document " + doc.getName() + " with id=" + doc.getId() + " and type=" + doc.getDocumentType().getName() + " contains the facet '" + facet + "', which is not registered as available in the schemaManager. This facet will be ignored."); if (log.isDebugEnabled()) { log.debug("Available facets: " + Arrays.toString(schemaManager.getFacets())); } } } } Collections.sort(facets); return facets; } /** Gets the doc's secondary type ids. */ public static List<String> getSecondaryTypeIds(DocumentModel doc) { List<String> facets = getFacets(doc); DocumentType type = doc.getDocumentType(); for (ListIterator<String> it = facets.listIterator(); it.hasNext();) { // remove those already in the doc type String facet = it.next(); if (type.hasFacet(facet)) { it.remove(); continue; } // add prefix it.set(NuxeoTypeHelper.FACET_TYPE_PREFIX + facet); } return facets; } public static ContentStream getContentStream(DocumentModel doc, HttpServletRequest request) throws CmisRuntimeException { BlobHolder blobHolder = doc.getAdapter(BlobHolder.class); if (blobHolder == null) { throw new CmisStreamNotSupportedException(); } Blob blob = blobHolder.getBlob(); if (blob == null) { return null; } GregorianCalendar lastModified = (GregorianCalendar) doc.getPropertyValue("dc:modified"); return NuxeoContentStream.create(doc, DownloadService.BLOBHOLDER_0, blob, "cmis", null, lastModified, request); } public static void setContentStream(DocumentModel doc, ContentStream contentStream, boolean overwrite) throws IOException, CmisContentAlreadyExistsException, CmisRuntimeException { BlobHolder blobHolder = doc.getAdapter(BlobHolder.class); if (blobHolder == null) { throw new CmisContentAlreadyExistsException(); } Blob oldBlob = blobHolder.getBlob(); if (!overwrite && oldBlob != null) { throw new CmisContentAlreadyExistsException(); } Blob blob; if (contentStream == null) { blob = null; } else { // default filename if none provided String filename = contentStream.getFileName(); if (filename == null && oldBlob != null) { filename = oldBlob.getFilename(); } if (filename == null) { filename = doc.getTitle(); } blob = getPersistentBlob(contentStream, filename); } blobHolder.setBlob(blob); } /** Returns a Blob whose stream can be used several times. */ public static Blob getPersistentBlob(ContentStream contentStream, String filename) throws IOException { if (filename == null) { filename = contentStream.getFileName(); } File file = Framework.createTempFile("NuxeoCMIS-", null); try (InputStream in = contentStream.getStream(); OutputStream out = new FileOutputStream(file)){ IOUtils.copy(in, out); Framework.trackFile(file, in); } return Blobs.createBlob(file, contentStream.getMimeType(), null, filename); } public static void validateBlobDigest(DocumentModel doc, CallContext callContext) { Blob blob = doc.getAdapter(BlobHolder.class).getBlob(); if (blob == null) { return; } String blobDigestAlgorithm = blob.getDigestAlgorithm(); if (blobDigestAlgorithm == null) { return; } HttpServletRequest request = (HttpServletRequest) callContext.get(CallContext.HTTP_SERVLET_REQUEST); String reqDigest = NuxeoPropertyData.extractDigestFromRequestHeaders(request, blobDigestAlgorithm); if (reqDigest == null) { return; } String blobDigest = blob.getDigest(); if (!blobDigest.equals(reqDigest)) { throw new CmisInvalidArgumentException(String.format( "Content Stream Hex-encoded Digest: '%s' must equal Request Header Hex-encoded Digest: '%s'", blobDigest, reqDigest)); } } protected static String extractDigestFromRequestHeaders(HttpServletRequest request, String digestAlgorithm) { if (request == null) { return null; } Enumeration<String> digests = request.getHeaders(NuxeoContentStream.DIGEST_HEADER_NAME); if (digests == null) { return null; } Iterator<String> it = Iterators.forEnumeration(digests); while (it.hasNext()) { String value = it.next(); int equals = value.indexOf('='); if (equals < 0) { continue; } String reqDigestAlgorithm = value.substring(0, equals); if (reqDigestAlgorithm.equalsIgnoreCase(digestAlgorithm)) { String digest = value.substring(equals + 1); digest = transcodeBase64ToHex(digest); return digest; } } return null; } public static String transcodeBase64ToHex(String base64String){ byte[] bytes = Base64.decodeBase64(base64String); String hexString = Hex.encodeHexString(bytes); return hexString; } public static String transcodeHexToBase64(String hexString) { byte[] bytes; try { bytes = Hex.decodeHex(hexString.toCharArray()); } catch (DecoderException e) { throw new RuntimeException(e); } String base64String = Base64.encodeBase64String(bytes); return base64String; } /** * Conversion from Nuxeo values to CMIS ones. * * @return either a primitive type or a List of them, or {@code null} */ @Override @SuppressWarnings("unchecked") public <U> U getValue() { Property prop = doc.getProperty(name); Serializable value = prop.getValue(); if (value == null) { return null; } Type type = prop.getType(); if (type.isListType()) { // array/list type = ((ListType) type).getFieldType(); Collection<Object> values; if (type.isComplexType()) { values = (Collection) prop.getChildren(); } else if (value instanceof Object[]) { values = Arrays.asList((Object[]) value); } else if (value instanceof List<?>) { values = (List<Object>) value; } else { throw new CmisRuntimeException("Unknown value type: " + value.getClass().getName()); } List<Object> list = new ArrayList<>(values); for (int i = 0; i < list.size(); i++) { if (type.isComplexType()) { value = (Serializable) convertComplexPropertyToCMIS((ComplexProperty) list.get(i), callContext); } else { value = (Serializable) convertToCMIS(list.get(i)); } list.set(i, value); } return (U) list; } else { // primitive type or complex type if (type.isComplexType()) { value = (Serializable) convertComplexPropertyToCMIS((ComplexProperty) prop, callContext); } else { value = (Serializable) convertToCMIS(value); } return (U) convertToCMIS(value); } } // conversion from Nuxeo value types to CMIS ones protected static Object convertToCMIS(Object value) { if (value instanceof Double) { return BigDecimal.valueOf(((Double) value).doubleValue()); } else if (value instanceof Integer) { return BigInteger.valueOf(((Integer) value).intValue()); } else if (value instanceof Long) { return BigInteger.valueOf(((Long) value).longValue()); } else { return value; } } protected static Object convertComplexPropertyToCMIS(ComplexProperty prop, CallContext callContext) { DateTimeFormat cmisDateTimeFormat = getCMISDateTimeFormat(callContext); org.nuxeo.ecm.automation.core.util.DateTimeFormat nuxeoDateTimeFormat = cmisDateTimeFormat == DateTimeFormat.SIMPLE ? org.nuxeo.ecm.automation.core.util.DateTimeFormat.TIME_IN_MILLIS : org.nuxeo.ecm.automation.core.util.DateTimeFormat.W3C; try { return ComplexPropertyJSONEncoder.encode(prop, nuxeoDateTimeFormat); } catch (IOException e) { throw new CmisRuntimeException(e.toString(), e); } } protected static DateTimeFormat getCMISDateTimeFormat(CallContext callContext) { if (callContext != null && CallContext.BINDING_BROWSER.equals(callContext.getBinding())) { HttpServletRequest request = (HttpServletRequest) callContext.get(CallContext.HTTP_SERVLET_REQUEST); if (request != null) { String s = HttpUtils.getStringParameter(request, Constants.PARAM_DATETIME_FORMAT); if (s != null) { try { return DateTimeFormat.fromValue(s.trim().toLowerCase(Locale.ENGLISH)); } catch (IllegalArgumentException e) { throw new CmisInvalidArgumentException("Invalid value for parameter " + Constants.PARAM_DATETIME_FORMAT + "!"); } } } return DateTimeFormat.SIMPLE; } return DateTimeFormat.EXTENDED; } // conversion from CMIS value types to Nuxeo ones protected static Object convertToNuxeo(Object value, Type type) { if (value instanceof BigDecimal) { return Double.valueOf(((BigDecimal) value).doubleValue()); } else if (value instanceof BigInteger) { return Long.valueOf(((BigInteger) value).longValue()); } else if (type.isComplexType()) { try { return ComplexTypeJSONDecoder.decode((ComplexType) type, value.toString()); } catch (IOException e) { throw new CmisRuntimeException(e.toString(), e); } } else { return value; } } /** * Validates a CMIS value according to a property definition. */ @SuppressWarnings("unchecked") public static <T> void validateCMISValue(Object value, PropertyDefinition<T> pd) { if (value == null) { return; } List<T> values; if (value instanceof List<?>) { if (pd.getCardinality() != Cardinality.MULTI) { throw new CmisInvalidArgumentException("Property is single-valued: " + pd.getId()); } values = (List<T>) value; if (values.isEmpty()) { return; } } else { if (pd.getCardinality() != Cardinality.SINGLE) { throw new CmisInvalidArgumentException("Property is multi-valued: " + pd.getId()); } values = Collections.singletonList((T) value); } PropertyType type = pd.getPropertyType(); for (Object v : values) { if (v == null) { throw new CmisInvalidArgumentException("Null values not allowed: " + values); } boolean ok; switch (type) { case STRING: case ID: case URI: case HTML: ok = v instanceof String; break; case INTEGER: ok = v instanceof BigInteger || v instanceof Byte || v instanceof Short || v instanceof Integer || v instanceof Long; break; case DECIMAL: ok = v instanceof BigDecimal; break; case BOOLEAN: ok = v instanceof Boolean; break; case DATETIME: ok = v instanceof GregorianCalendar; break; default: throw new RuntimeException(type.toString()); } if (!ok) { throw new CmisInvalidArgumentException("Value does not match property type " + type + ": " + v); } } } @SuppressWarnings("unchecked") @Override public T getFirstValue() { Object value = getValue(); if (value == null) { return null; } if (value instanceof List) { List<?> list = (List<?>) value; if (list.isEmpty()) { return null; } return (T) list.get(0); } else { return (T) value; } } @SuppressWarnings("unchecked") @Override public List<T> getValues() { Object value = getValue(); if (value == null) { return Collections.emptyList(); } if (value instanceof List) { return (List<T>) value; } else { return (List<T>) Collections.singletonList(value); } } @Override public void setValue(Object value) { if (readOnly) { super.setValue(value); } else { Type type = doc.getProperty(name).getType(); if (type.isListType()) { type = ((ListType) type).getFieldType(); } Object propValue; if (value instanceof List<?>) { @SuppressWarnings("unchecked") List<Object> list = new ArrayList<>((List<Object>) value); for (int i = 0; i < list.size(); i++) { list.set(i, convertToNuxeo(list.get(i), type)); } if (list.isEmpty()) { list = null; } propValue = list; } else { propValue = convertToNuxeo(value, type); } doc.setPropertyValue(name, (Serializable) propValue); } } protected static Blob getBlob(DocumentModel doc) throws CmisRuntimeException { BlobHolder blobHolder = doc.getAdapter(BlobHolder.class); if (blobHolder == null) { return null; } return blobHolder.getBlob(); } public static class NuxeoPropertyStringData extends NuxeoPropertyData<String> implements PropertyString { public NuxeoPropertyStringData(PropertyDefinition<String> propertyDefinition, DocumentModel doc, String name, boolean readOnly, CallContext callContext) { super(propertyDefinition, doc, name, readOnly, callContext); } } public static class NuxeoPropertyIdData extends NuxeoPropertyData<String> implements PropertyId { public NuxeoPropertyIdData(PropertyDefinition<String> propertyDefinition, DocumentModel doc, String name, boolean readOnly, CallContext callContext) { super(propertyDefinition, doc, name, readOnly, callContext); } } public static class NuxeoPropertyBooleanData extends NuxeoPropertyData<Boolean> implements PropertyBoolean { public NuxeoPropertyBooleanData(PropertyDefinition<Boolean> propertyDefinition, DocumentModel doc, String name, boolean readOnly, CallContext callContext) { super(propertyDefinition, doc, name, readOnly, callContext); } } public static class NuxeoPropertyIntegerData extends NuxeoPropertyData<BigInteger> implements PropertyInteger { public NuxeoPropertyIntegerData(PropertyDefinition<BigInteger> propertyDefinition, DocumentModel doc, String name, boolean readOnly, CallContext callContext) { super(propertyDefinition, doc, name, readOnly, callContext); } } public static class NuxeoPropertyDecimalData extends NuxeoPropertyData<BigDecimal> implements PropertyDecimal { public NuxeoPropertyDecimalData(PropertyDefinition<BigDecimal> propertyDefinition, DocumentModel doc, String name, boolean readOnly, CallContext callContext) { super(propertyDefinition, doc, name, readOnly, callContext); } } public static class NuxeoPropertyDateTimeData extends NuxeoPropertyData<GregorianCalendar> implements PropertyDateTime { public NuxeoPropertyDateTimeData(PropertyDefinition<GregorianCalendar> propertyDefinition, DocumentModel doc, String name, boolean readOnly, CallContext callContext) { super(propertyDefinition, doc, name, readOnly, callContext); } } public static class NuxeoPropertyHtmlData extends NuxeoPropertyData<String> implements PropertyHtml { public NuxeoPropertyHtmlData(PropertyDefinition<String> propertyDefinition, DocumentModel doc, String name, boolean readOnly, CallContext callContext) { super(propertyDefinition, doc, name, readOnly, callContext); } } public static class NuxeoPropertyUriData extends NuxeoPropertyData<String> implements PropertyUri { public NuxeoPropertyUriData(PropertyDefinition<String> propertyDefinition, DocumentModel doc, String name, boolean readOnly, CallContext callContext) { super(propertyDefinition, doc, name, readOnly, callContext); } } /** * Property for cmis:contentStreamFileName. */ public static class NuxeoPropertyDataContentStreamFileName extends NuxeoPropertyDataBase<String> implements PropertyString { protected NuxeoPropertyDataContentStreamFileName(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public String getFirstValue() { Blob blob = getBlob(doc); return blob == null ? null : blob.getFilename(); } // @Override // public void setValue(Serializable value) { // BlobHolder blobHolder = docHolder.getDocumentModel().getAdapter( // BlobHolder.class); // if (blobHolder == null) { // throw new StreamNotSupportedException(); // } // Blob blob; // blob = blobHolder.getBlob(); // if (blob != null) { // blob.setFilename((String) value); // } // } } /** * Property for cmis:contentStreamLength. */ public static class NuxeoPropertyDataContentStreamLength extends NuxeoPropertyDataBase<BigInteger> implements PropertyInteger { protected NuxeoPropertyDataContentStreamLength(PropertyDefinition<BigInteger> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public BigInteger getFirstValue() { Blob blob = getBlob(doc); return blob == null ? null : BigInteger.valueOf(blob.getLength()); } } /** * Property for nuxeo:contentStreamDigest. */ public static class NuxeoPropertyDataContentStreamDigest extends NuxeoPropertyDataBase<String> implements PropertyString { protected NuxeoPropertyDataContentStreamDigest(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public String getFirstValue() { Blob blob = getBlob(doc); return blob == null ? null : blob.getDigest(); } } /** * Property for cmis:contentStreamHash. */ public static class NuxeoPropertyDataContentStreamHash extends NuxeoPropertyMultiDataFixed<String> implements PropertyString { protected NuxeoPropertyDataContentStreamHash(PropertyDefinition<String> propertyDefinition, List<String> hashes) { super(propertyDefinition, hashes); } } /** * Property for cmis:contentMimeTypeLength. */ public static class NuxeoPropertyDataContentStreamMimeType extends NuxeoPropertyDataBase<String> implements PropertyString { protected NuxeoPropertyDataContentStreamMimeType(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public String getFirstValue() { Blob blob = getBlob(doc); return blob == null ? null : blob.getMimeType(); } } /** * Property for cmis:name. */ public static class NuxeoPropertyDataName extends NuxeoPropertyDataBase<String> implements PropertyString { private static final Log log = LogFactory.getLog(NuxeoPropertyDataName.class); protected NuxeoPropertyDataName(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } /** * Gets the value for the cmis:name property. */ public static String getValue(DocumentModel doc) { if (doc.getPath() == null) { // not a real doc (content changes) return ""; } if (doc.getPath().isRoot()) { return ""; // Nuxeo root } return doc.getTitle(); } @Override public String getFirstValue() { return getValue(doc); } @Override public void setValue(Object value) { try { doc.setPropertyValue(NuxeoTypeHelper.NX_DC_TITLE, (String) value); } catch (PropertyNotFoundException e) { // trying to set the name of a type with no dublincore // ignore log.debug("Cannot set CMIS name on type: " + doc.getType()); } } } /** * Property for cmis:parentId and nuxeo:parentId. */ public static class NuxeoPropertyDataParentId extends NuxeoPropertyDataBase<String> implements PropertyId { protected NuxeoPropertyDataParentId(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public String getFirstValue() { if (doc.getName() == null) { return null; } else { DocumentRef parentRef = doc.getParentRef(); if (parentRef == null) { return null; // unfiled document } else if (parentRef instanceof IdRef) { return ((IdRef) parentRef).value; } else { return doc.getCoreSession().getDocument(parentRef).getId(); } } } } /** * Property for cmis:path. */ public static class NuxeoPropertyDataPath extends NuxeoPropertyDataBase<String> implements PropertyString { protected NuxeoPropertyDataPath(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public String getFirstValue() { String path = doc.getPathAsString(); return path == null ? "" : path; } } protected static boolean isVersionOrProxyToVersion(DocumentModel doc) { return doc.isVersion() || (doc.isProxy() && doc.getCoreSession().getSourceDocument(doc.getRef()).isVersion()); } /** * Property for cmis:isMajorVersion. */ public static class NuxeoPropertyDataIsMajorVersion extends NuxeoPropertyDataBase<Boolean> implements PropertyBoolean { protected NuxeoPropertyDataIsMajorVersion(PropertyDefinition<Boolean> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public Boolean getFirstValue() { if (isVersionOrProxyToVersion(doc)) { return Boolean.valueOf(doc.isMajorVersion()); } // checked in doc considered latest version return Boolean.valueOf(!doc.isCheckedOut() && doc.getVersionLabel().endsWith(".0")); } } /** * Property for cmis:isLatestVersion. */ public static class NuxeoPropertyDataIsLatestVersion extends NuxeoPropertyDataBase<Boolean> implements PropertyBoolean { protected NuxeoPropertyDataIsLatestVersion(PropertyDefinition<Boolean> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public Boolean getFirstValue() { if (isVersionOrProxyToVersion(doc)) { return Boolean.valueOf(doc.isLatestVersion()); } // checked in doc considered latest version return Boolean.valueOf(!doc.isCheckedOut()); } } /** * Property for cmis:isLatestMajorVersion. */ public static class NuxeoPropertyDataIsLatestMajorVersion extends NuxeoPropertyDataBase<Boolean> implements PropertyBoolean { protected NuxeoPropertyDataIsLatestMajorVersion(PropertyDefinition<Boolean> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public Boolean getFirstValue() { if (isVersionOrProxyToVersion(doc)) { return Boolean.valueOf(doc.isLatestMajorVersion()); } // checked in doc considered latest version return Boolean.valueOf(!doc.isCheckedOut() && doc.getVersionLabel().endsWith(".0")); } } /** * Property for cmis:isVersionSeriesCheckedOut. */ public static class NuxeoPropertyDataIsVersionSeriesCheckedOut extends NuxeoPropertyDataBase<Boolean> implements PropertyBoolean { protected NuxeoPropertyDataIsVersionSeriesCheckedOut(PropertyDefinition<Boolean> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public Boolean getFirstValue() { return Boolean.valueOf(doc.isVersionSeriesCheckedOut()); } } /** * Property for cmis:versionSeriesCheckedOutId. */ public static class NuxeoPropertyDataVersionSeriesCheckedOutId extends NuxeoPropertyDataBase<String> implements PropertyId { protected NuxeoPropertyDataVersionSeriesCheckedOutId(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public String getFirstValue() { if (!doc.isVersionSeriesCheckedOut()) { return null; } DocumentModel pwc = doc.getCoreSession().getWorkingCopy(doc.getRef()); return pwc == null ? null : pwc.getId(); } } /** * Property for cmis:versionSeriesCheckedOutBy. */ public static class NuxeoPropertyDataVersionSeriesCheckedOutBy extends NuxeoPropertyDataBase<String> implements PropertyString { protected final CallContext callContext; protected NuxeoPropertyDataVersionSeriesCheckedOutBy(PropertyDefinition<String> propertyDefinition, DocumentModel doc, CallContext callContext) { super(propertyDefinition, doc); this.callContext = callContext; } @Override public String getFirstValue() { if (!doc.isVersionSeriesCheckedOut()) { return null; } DocumentModel pwc = doc.getCoreSession().getWorkingCopy(doc.getRef()); // TODO not implemented return pwc == null ? null : callContext.getUsername(); } } /** * Property for cmis:versionLabel. */ public static class NuxeoPropertyDataVersionLabel extends NuxeoPropertyDataBase<String> implements PropertyString { protected NuxeoPropertyDataVersionLabel(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public String getFirstValue() { if (isVersionOrProxyToVersion(doc)) { return doc.getVersionLabel(); } return doc.isCheckedOut() ? null : doc.getVersionLabel(); } } /** * Property for cmis:checkinComment. */ public static class NuxeoPropertyDataCheckInComment extends NuxeoPropertyDataBase<String> implements PropertyString { protected NuxeoPropertyDataCheckInComment(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { super(propertyDefinition, doc); } @Override public String getFirstValue() { if (isVersionOrProxyToVersion(doc)) { return doc.getCheckinComment(); } if (doc.isCheckedOut()) { return null; } CoreSession session = doc.getCoreSession(); DocumentRef v = session.getBaseVersion(doc.getRef()); DocumentModel ver = session.getDocument(v); return ver.getCheckinComment(); } } }