001/* 002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 */ 011package org.nuxeo.ecm.core.opencmis.impl.server; 012 013import java.io.File; 014import java.io.FileOutputStream; 015import java.io.IOException; 016import java.io.InputStream; 017import java.io.OutputStream; 018import java.io.Serializable; 019import java.math.BigDecimal; 020import java.math.BigInteger; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.GregorianCalendar; 026import java.util.List; 027import java.util.Locale; 028 029import javax.servlet.http.HttpServletRequest; 030 031import org.apache.chemistry.opencmis.commons.PropertyIds; 032import org.apache.chemistry.opencmis.commons.data.ContentStream; 033import org.apache.chemistry.opencmis.commons.data.PropertyBoolean; 034import org.apache.chemistry.opencmis.commons.data.PropertyData; 035import org.apache.chemistry.opencmis.commons.data.PropertyDateTime; 036import org.apache.chemistry.opencmis.commons.data.PropertyDecimal; 037import org.apache.chemistry.opencmis.commons.data.PropertyHtml; 038import org.apache.chemistry.opencmis.commons.data.PropertyId; 039import org.apache.chemistry.opencmis.commons.data.PropertyInteger; 040import org.apache.chemistry.opencmis.commons.data.PropertyString; 041import org.apache.chemistry.opencmis.commons.data.PropertyUri; 042import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; 043import org.apache.chemistry.opencmis.commons.enums.Cardinality; 044import org.apache.chemistry.opencmis.commons.enums.DateTimeFormat; 045import org.apache.chemistry.opencmis.commons.enums.PropertyType; 046import org.apache.chemistry.opencmis.commons.enums.Updatability; 047import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException; 048import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; 049import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; 050import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException; 051import org.apache.chemistry.opencmis.commons.impl.Constants; 052import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamHashImpl; 053import org.apache.chemistry.opencmis.commons.server.CallContext; 054import org.apache.chemistry.opencmis.server.shared.HttpUtils; 055import org.apache.commons.io.IOUtils; 056import org.apache.commons.logging.Log; 057import org.apache.commons.logging.LogFactory; 058import org.nuxeo.common.utils.FileUtils; 059import org.nuxeo.ecm.automation.core.util.ComplexPropertyJSONEncoder; 060import org.nuxeo.ecm.automation.core.util.ComplexTypeJSONDecoder; 061import org.nuxeo.ecm.core.api.Blob; 062import org.nuxeo.ecm.core.api.Blobs; 063import org.nuxeo.ecm.core.api.CoreSession; 064import org.nuxeo.ecm.core.api.DocumentModel; 065import org.nuxeo.ecm.core.api.DocumentRef; 066import org.nuxeo.ecm.core.api.IdRef; 067import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 068import org.nuxeo.ecm.core.api.model.Property; 069import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 070import org.nuxeo.ecm.core.api.model.impl.ComplexProperty; 071import org.nuxeo.ecm.core.api.model.impl.ListProperty; 072import org.nuxeo.ecm.core.io.download.DownloadService; 073import org.nuxeo.ecm.core.schema.FacetNames; 074import org.nuxeo.ecm.core.schema.types.ComplexType; 075import org.nuxeo.ecm.core.schema.types.ListType; 076import org.nuxeo.ecm.core.schema.types.Type; 077import org.nuxeo.runtime.api.Framework; 078 079/** 080 * Nuxeo implementation of an object's property, backed by a property of a {@link DocumentModel}. 081 */ 082public abstract class NuxeoPropertyData<T> extends NuxeoPropertyDataBase<T> { 083 084 protected final String name; 085 086 protected final boolean readOnly; 087 088 protected final CallContext callContext; 089 090 public NuxeoPropertyData(PropertyDefinition<T> propertyDefinition, DocumentModel doc, String name, 091 boolean readOnly, CallContext callContext) { 092 super(propertyDefinition, doc); 093 this.name = name; 094 this.readOnly = readOnly; 095 this.callContext = callContext; 096 } 097 098 /** 099 * Factory for a new Property. 100 */ 101 @SuppressWarnings("unchecked") 102 public static <U> PropertyData<U> construct(NuxeoObjectData data, PropertyDefinition<U> pd, CallContext callContext) { 103 DocumentModel doc = data.doc; 104 String name = pd.getId(); 105 if (PropertyIds.OBJECT_ID.equals(name)) { 106 return (PropertyData<U>) new NuxeoPropertyIdDataFixed((PropertyDefinition<String>) pd, doc.getId()); 107 } else if (PropertyIds.OBJECT_TYPE_ID.equals(name)) { 108 return (PropertyData<U>) new NuxeoPropertyIdDataFixed((PropertyDefinition<String>) pd, 109 NuxeoTypeHelper.mappedId(doc.getType())); 110 } else if (PropertyIds.BASE_TYPE_ID.equals(name)) { 111 return (PropertyData<U>) new NuxeoPropertyIdDataFixed((PropertyDefinition<String>) pd, 112 NuxeoTypeHelper.getBaseTypeId(doc).value()); 113 } else if (PropertyIds.DESCRIPTION.equals(name)) { 114 return (PropertyData<U>) new NuxeoPropertyStringData((PropertyDefinition<String>) pd, doc, 115 NuxeoTypeHelper.NX_DC_DESCRIPTION, true, callContext); 116 } else if (PropertyIds.CREATED_BY.equals(name)) { 117 return (PropertyData<U>) new NuxeoPropertyStringData((PropertyDefinition<String>) pd, doc, 118 NuxeoTypeHelper.NX_DC_CREATOR, true, callContext); 119 } else if (PropertyIds.CREATION_DATE.equals(name)) { 120 return (PropertyData<U>) new NuxeoPropertyDateTimeData((PropertyDefinition<GregorianCalendar>) pd, doc, 121 NuxeoTypeHelper.NX_DC_CREATED, true, callContext); 122 } else if (PropertyIds.LAST_MODIFIED_BY.equals(name)) { 123 return (PropertyData<U>) new NuxeoPropertyStringData((PropertyDefinition<String>) pd, doc, 124 NuxeoTypeHelper.NX_DC_LAST_CONTRIBUTOR, true, callContext); 125 } else if (PropertyIds.LAST_MODIFICATION_DATE.equals(name)) { 126 return (PropertyData<U>) new NuxeoPropertyDateTimeData((PropertyDefinition<GregorianCalendar>) pd, doc, 127 NuxeoTypeHelper.NX_DC_MODIFIED, true, callContext); 128 } else if (PropertyIds.CHANGE_TOKEN.equals(name)) { 129 return (PropertyData<U>) new NuxeoPropertyStringDataFixed((PropertyDefinition<String>) pd, null); 130 } else if (PropertyIds.NAME.equals(name)) { 131 return (PropertyData<U>) new NuxeoPropertyDataName((PropertyDefinition<String>) pd, doc); 132 } else if (PropertyIds.IS_IMMUTABLE.equals(name)) { 133 // TODO check write 134 return (PropertyData<U>) new NuxeoPropertyBooleanDataFixed((PropertyDefinition<Boolean>) pd, Boolean.FALSE); 135 } else if (PropertyIds.IS_LATEST_VERSION.equals(name)) { 136 return (PropertyData<U>) new NuxeoPropertyDataIsLatestVersion((PropertyDefinition<Boolean>) pd, doc); 137 } else if (PropertyIds.IS_LATEST_MAJOR_VERSION.equals(name)) { 138 return (PropertyData<U>) new NuxeoPropertyDataIsLatestMajorVersion((PropertyDefinition<Boolean>) pd, doc); 139 } else if (PropertyIds.IS_MAJOR_VERSION.equals(name)) { 140 return (PropertyData<U>) new NuxeoPropertyDataIsMajorVersion((PropertyDefinition<Boolean>) pd, doc); 141 } else if (PropertyIds.VERSION_LABEL.equals(name)) { 142 return (PropertyData<U>) new NuxeoPropertyDataVersionLabel((PropertyDefinition<String>) pd, doc); 143 } else if (PropertyIds.VERSION_SERIES_ID.equals(name)) { 144 // doesn't change once computed, no need to have a dynamic prop 145 String versionSeriesId = doc.getVersionSeriesId(); 146 return (PropertyData<U>) new NuxeoPropertyIdDataFixed((PropertyDefinition<String>) pd, versionSeriesId); 147 } else if (PropertyIds.IS_VERSION_SERIES_CHECKED_OUT.equals(name)) { 148 return (PropertyData<U>) new NuxeoPropertyDataIsVersionSeriesCheckedOut((PropertyDefinition<Boolean>) pd, 149 doc); 150 } else if (PropertyIds.VERSION_SERIES_CHECKED_OUT_BY.equals(name)) { 151 return (PropertyData<U>) new NuxeoPropertyDataVersionSeriesCheckedOutBy((PropertyDefinition<String>) pd, 152 doc, callContext); 153 } else if (PropertyIds.VERSION_SERIES_CHECKED_OUT_ID.equals(name)) { 154 return (PropertyData<U>) new NuxeoPropertyDataVersionSeriesCheckedOutId((PropertyDefinition<String>) pd, 155 doc); 156 } else if (NuxeoTypeHelper.NX_ISVERSION.equals(name)) { 157 return (PropertyData<U>) new NuxeoPropertyBooleanDataFixed((PropertyDefinition<Boolean>) pd, 158 Boolean.valueOf(doc.isVersion())); 159 } else if (NuxeoTypeHelper.NX_ISCHECKEDIN.equals(name)) { 160 boolean co = doc.isCheckedOut(); 161 return (PropertyData<U>) new NuxeoPropertyBooleanDataFixed((PropertyDefinition<Boolean>) pd, 162 Boolean.valueOf(!co)); 163 } else if (PropertyIds.IS_PRIVATE_WORKING_COPY.equals(name)) { 164 boolean co = doc.isCheckedOut(); 165 return (PropertyData<U>) new NuxeoPropertyBooleanDataFixed((PropertyDefinition<Boolean>) pd, 166 Boolean.valueOf(co)); 167 } else if (PropertyIds.CHECKIN_COMMENT.equals(name)) { 168 return (PropertyData<U>) new NuxeoPropertyDataCheckInComment((PropertyDefinition<String>) pd, doc); 169 } else if (PropertyIds.CONTENT_STREAM_LENGTH.equals(name)) { 170 return (PropertyData<U>) new NuxeoPropertyDataContentStreamLength((PropertyDefinition<BigInteger>) pd, doc); 171 } else if (NuxeoTypeHelper.NX_DIGEST.equals(name)) { 172 return (PropertyData<U>) new NuxeoPropertyDataContentStreamDigest((PropertyDefinition<String>) pd, doc); 173 } else if (PropertyIds.CONTENT_STREAM_HASH.equals(name)) { 174 String digest = new NuxeoPropertyDataContentStreamDigest((PropertyDefinition<String>) pd, doc).getFirstValue(); 175 List<String> hashes; 176 if (digest == null) { 177 hashes = new ArrayList<String>(); 178 } else { 179 hashes = Arrays.asList(new ContentStreamHashImpl(ContentStreamHashImpl.ALGORITHM_MD5, digest).getPropertyValue()); 180 } 181 return (PropertyData<U>) new NuxeoPropertyDataContentStreamHash((PropertyDefinition<String>) pd, hashes); 182 } else if (PropertyIds.CONTENT_STREAM_MIME_TYPE.equals(name)) { 183 return (PropertyData<U>) new NuxeoPropertyDataContentStreamMimeType((PropertyDefinition<String>) pd, doc); 184 } else if (PropertyIds.CONTENT_STREAM_FILE_NAME.equals(name)) { 185 return (PropertyData<U>) new NuxeoPropertyDataContentStreamFileName((PropertyDefinition<String>) pd, doc); 186 } else if (PropertyIds.CONTENT_STREAM_ID.equals(name)) { 187 return (PropertyData<U>) new NuxeoPropertyIdDataFixed((PropertyDefinition<String>) pd, null); 188 } else if (PropertyIds.PARENT_ID.equals(name)) { 189 return (PropertyData<U>) new NuxeoPropertyDataParentId((PropertyDefinition<String>) pd, doc); 190 } else if (NuxeoTypeHelper.NX_PARENT_ID.equals(name)) { 191 return (PropertyData<U>) new NuxeoPropertyDataParentId((PropertyDefinition<String>) pd, doc); 192 } else if (NuxeoTypeHelper.NX_PATH_SEGMENT.equals(name)) { 193 return (PropertyData<U>) new NuxeoPropertyStringDataFixed((PropertyDefinition<String>) pd, doc.getName()); 194 } else if (NuxeoTypeHelper.NX_POS.equals(name)) { 195 return (PropertyData<U>) new NuxeoPropertyIntegerDataFixed((PropertyDefinition<BigInteger>) pd, 196 doc.getPos()); 197 } else if (PropertyIds.PATH.equals(name)) { 198 return (PropertyData<U>) new NuxeoPropertyDataPath((PropertyDefinition<String>) pd, doc); 199 } else if (PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS.equals(name)) { 200 return (PropertyData<U>) new NuxeoPropertyIdMultiDataFixed((PropertyDefinition<String>) pd, 201 Collections.<String> emptyList()); 202 } else if (PropertyIds.SOURCE_ID.equals(name)) { 203 return (PropertyData<U>) new NuxeoPropertyIdData((PropertyDefinition<String>) pd, doc, 204 NuxeoTypeHelper.NX_REL_SOURCE, false, callContext); 205 } else if (PropertyIds.TARGET_ID.equals(name)) { 206 return (PropertyData<U>) new NuxeoPropertyIdData((PropertyDefinition<String>) pd, doc, 207 NuxeoTypeHelper.NX_REL_TARGET, false, callContext); 208 } else if (PropertyIds.POLICY_TEXT.equals(name)) { 209 return (PropertyData<U>) new NuxeoPropertyStringDataFixed((PropertyDefinition<String>) pd, null); 210 } else if (PropertyIds.SECONDARY_OBJECT_TYPE_IDS.equals(name)) { 211 // secondary object types require that the types be 212 // actually registered through the type service 213 // -> return empty for now 214 return (PropertyData<U>) new NuxeoPropertyIdMultiDataFixed((PropertyDefinition<String>) pd, 215 Collections.<String> emptyList()); 216 } else if (NuxeoTypeHelper.NX_FACETS.equals(name)) { 217 List<String> facets = new ArrayList<String>(doc.getFacets()); 218 facets.remove(FacetNames.IMMUTABLE); // not actually stored or registered 219 Collections.sort(facets); 220 return (PropertyData<U>) new NuxeoPropertyIdMultiDataFixed((PropertyDefinition<String>) pd, facets); 221 } else if (NuxeoTypeHelper.NX_LIFECYCLE_STATE.equals(name)) { 222 String state = doc.getCurrentLifeCycleState(); 223 return (PropertyData<U>) new NuxeoPropertyStringDataFixed((PropertyDefinition<String>) pd, state); 224 } else { 225 boolean readOnly = pd.getUpdatability() != Updatability.READWRITE; 226 // TODO WHEN_CHECKED_OUT, ON_CREATE 227 228 switch (pd.getPropertyType()) { 229 case BOOLEAN: 230 return (PropertyData<U>) new NuxeoPropertyBooleanData((PropertyDefinition<Boolean>) pd, doc, name, 231 readOnly, callContext); 232 case DATETIME: 233 return (PropertyData<U>) new NuxeoPropertyDateTimeData((PropertyDefinition<GregorianCalendar>) pd, doc, 234 name, readOnly, callContext); 235 case DECIMAL: 236 return (PropertyData<U>) new NuxeoPropertyDecimalData((PropertyDefinition<BigDecimal>) pd, doc, name, 237 readOnly, callContext); 238 case HTML: 239 return (PropertyData<U>) new NuxeoPropertyHtmlData((PropertyDefinition<String>) pd, doc, name, 240 readOnly, callContext); 241 case ID: 242 return (PropertyData<U>) new NuxeoPropertyIdData((PropertyDefinition<String>) pd, doc, name, readOnly, 243 callContext); 244 case INTEGER: 245 return (PropertyData<U>) new NuxeoPropertyIntegerData((PropertyDefinition<BigInteger>) pd, doc, name, 246 readOnly, callContext); 247 case STRING: 248 return (PropertyData<U>) new NuxeoPropertyStringData((PropertyDefinition<String>) pd, doc, name, 249 readOnly, callContext); 250 case URI: 251 return (PropertyData<U>) new NuxeoPropertyUriData((PropertyDefinition<String>) pd, doc, name, readOnly, 252 callContext); 253 default: 254 throw new AssertionError(pd.getPropertyType().toString()); 255 } 256 } 257 } 258 259 public static ContentStream getContentStream(DocumentModel doc) throws CmisRuntimeException { 260 BlobHolder blobHolder = doc.getAdapter(BlobHolder.class); 261 if (blobHolder == null) { 262 throw new CmisStreamNotSupportedException(); 263 } 264 Blob blob = blobHolder.getBlob(); 265 if (blob == null) { 266 return null; 267 } 268 GregorianCalendar lastModified = (GregorianCalendar) doc.getPropertyValue("dc:modified"); 269 DownloadService downloadService = Framework.getService(DownloadService.class); 270 downloadService.logDownload(doc, DownloadService.BLOBHOLDER_0, blob.getFilename(), "cmis", null); 271 return new NuxeoContentStream(blob, lastModified); 272 } 273 274 public static void setContentStream(DocumentModel doc, ContentStream contentStream, boolean overwrite) 275 throws IOException, CmisContentAlreadyExistsException, CmisRuntimeException { 276 BlobHolder blobHolder = doc.getAdapter(BlobHolder.class); 277 if (blobHolder == null) { 278 throw new CmisContentAlreadyExistsException(); 279 } 280 Blob oldBlob = blobHolder.getBlob(); 281 if (!overwrite && oldBlob != null) { 282 throw new CmisContentAlreadyExistsException(); 283 } 284 Blob blob; 285 if (contentStream == null) { 286 blob = null; 287 } else { 288 // default filename if none provided 289 String filename = contentStream.getFileName(); 290 if (filename == null && oldBlob != null) { 291 filename = oldBlob.getFilename(); 292 } 293 if (filename == null) { 294 filename = doc.getTitle(); 295 } 296 blob = getPersistentBlob(contentStream, filename); 297 } 298 blobHolder.setBlob(blob); 299 } 300 301 /** Returns a Blob whose stream can be used several times. */ 302 public static Blob getPersistentBlob(ContentStream contentStream, String filename) throws IOException { 303 if (filename == null) { 304 filename = contentStream.getFileName(); 305 } 306 InputStream in = contentStream.getStream(); 307 OutputStream out = null; 308 File file; 309 try { 310 file = File.createTempFile("NuxeoCMIS-", null); 311 out = new FileOutputStream(file); 312 IOUtils.copy(in, out); 313 Framework.trackFile(file, in); 314 } finally { 315 FileUtils.close(in); 316 FileUtils.close(out); 317 } 318 return Blobs.createBlob(file, contentStream.getMimeType(), null, filename); 319 } 320 321 /** 322 * Conversion from Nuxeo values to CMIS ones. 323 * 324 * @return either a primitive type or a List of them, or {@code null} 325 */ 326 @Override 327 @SuppressWarnings("unchecked") 328 public <U> U getValue() { 329 Property prop = doc.getProperty(name); 330 Serializable value = prop.getValue(); 331 if (value == null) { 332 return null; 333 } 334 Type type = prop.getType(); 335 if (type.isListType()) { 336 // array/list 337 type = ((ListType) type).getFieldType(); 338 Collection<Object> values; 339 if (type.isComplexType()) { 340 values = (Collection) ((ListProperty) prop).getChildren(); 341 } else if (value instanceof Object[]) { 342 values = Arrays.asList((Object[]) value); 343 } else if (value instanceof List<?>) { 344 values = (List<Object>) value; 345 } else { 346 throw new CmisRuntimeException("Unknown value type: " + value.getClass().getName()); 347 } 348 List<Object> list = new ArrayList<Object>(values); 349 for (int i = 0; i < list.size(); i++) { 350 if (type.isComplexType()) { 351 value = (Serializable) convertComplexPropertyToCMIS((ComplexProperty) list.get(i), callContext); 352 } else { 353 value = (Serializable) convertToCMIS(list.get(i)); 354 } 355 list.set(i, value); 356 } 357 return (U) list; 358 } else { 359 // primitive type or complex type 360 if (type.isComplexType()) { 361 value = (Serializable) convertComplexPropertyToCMIS((ComplexProperty) prop, callContext); 362 } else { 363 value = (Serializable) convertToCMIS(value); 364 } 365 return (U) convertToCMIS(value); 366 } 367 } 368 369 // conversion from Nuxeo value types to CMIS ones 370 protected static Object convertToCMIS(Object value) { 371 if (value instanceof Double) { 372 return BigDecimal.valueOf(((Double) value).doubleValue()); 373 } else if (value instanceof Integer) { 374 return BigInteger.valueOf(((Integer) value).intValue()); 375 } else if (value instanceof Long) { 376 return BigInteger.valueOf(((Long) value).longValue()); 377 } else { 378 return value; 379 } 380 } 381 382 protected static Object convertComplexPropertyToCMIS(ComplexProperty prop, CallContext callContext) { 383 DateTimeFormat cmisDateTimeFormat = getCMISDateTimeFormat(callContext); 384 org.nuxeo.ecm.automation.core.util.DateTimeFormat nuxeoDateTimeFormat = cmisDateTimeFormat == DateTimeFormat.SIMPLE 385 ? org.nuxeo.ecm.automation.core.util.DateTimeFormat.TIME_IN_MILLIS 386 : org.nuxeo.ecm.automation.core.util.DateTimeFormat.W3C; 387 try { 388 return ComplexPropertyJSONEncoder.encode(prop, nuxeoDateTimeFormat); 389 } catch (IOException e) { 390 throw new CmisRuntimeException(e.toString(), e); 391 } 392 } 393 394 protected static DateTimeFormat getCMISDateTimeFormat(CallContext callContext) { 395 if (callContext != null && CallContext.BINDING_BROWSER.equals(callContext.getBinding())) { 396 HttpServletRequest request = (HttpServletRequest) callContext.get(CallContext.HTTP_SERVLET_REQUEST); 397 if (request != null) { 398 String s = HttpUtils.getStringParameter(request, Constants.PARAM_DATETIME_FORMAT); 399 if (s != null) { 400 try { 401 return DateTimeFormat.fromValue(s.trim().toLowerCase(Locale.ENGLISH)); 402 } catch (IllegalArgumentException e) { 403 throw new CmisInvalidArgumentException("Invalid value for parameter " 404 + Constants.PARAM_DATETIME_FORMAT + "!"); 405 } 406 } 407 } 408 return DateTimeFormat.SIMPLE; 409 } 410 return DateTimeFormat.EXTENDED; 411 } 412 413 // conversion from CMIS value types to Nuxeo ones 414 protected static Object convertToNuxeo(Object value, Type type) { 415 if (value instanceof BigDecimal) { 416 return Double.valueOf(((BigDecimal) value).doubleValue()); 417 } else if (value instanceof BigInteger) { 418 return Long.valueOf(((BigInteger) value).longValue()); 419 } else if (type.isComplexType()) { 420 try { 421 return ComplexTypeJSONDecoder.decode((ComplexType) type, value.toString()); 422 } catch (IOException e) { 423 throw new CmisRuntimeException(e.toString(), e); 424 } 425 } else { 426 return value; 427 } 428 } 429 430 /** 431 * Validates a CMIS value according to a property definition. 432 */ 433 @SuppressWarnings("unchecked") 434 public static <T> void validateCMISValue(Object value, PropertyDefinition<T> pd) { 435 if (value == null) { 436 return; 437 } 438 List<T> values; 439 if (value instanceof List<?>) { 440 if (pd.getCardinality() != Cardinality.MULTI) { 441 throw new CmisInvalidArgumentException("Property is single-valued: " + pd.getId()); 442 } 443 values = (List<T>) value; 444 if (values.isEmpty()) { 445 return; 446 } 447 } else { 448 if (pd.getCardinality() != Cardinality.SINGLE) { 449 throw new CmisInvalidArgumentException("Property is multi-valued: " + pd.getId()); 450 } 451 values = Collections.singletonList((T) value); 452 } 453 PropertyType type = pd.getPropertyType(); 454 for (Object v : values) { 455 if (v == null) { 456 throw new CmisInvalidArgumentException("Null values not allowed: " + values); 457 } 458 boolean ok; 459 switch (type) { 460 case STRING: 461 case ID: 462 case URI: 463 case HTML: 464 ok = v instanceof String; 465 break; 466 case INTEGER: 467 ok = v instanceof BigInteger || v instanceof Byte || v instanceof Short || v instanceof Integer 468 || v instanceof Long; 469 break; 470 case DECIMAL: 471 ok = v instanceof BigDecimal; 472 break; 473 case BOOLEAN: 474 ok = v instanceof Boolean; 475 break; 476 case DATETIME: 477 ok = v instanceof GregorianCalendar; 478 break; 479 default: 480 throw new RuntimeException(type.toString()); 481 } 482 if (!ok) { 483 throw new CmisInvalidArgumentException("Value does not match property type " + type + ": " + v); 484 } 485 } 486 } 487 488 @SuppressWarnings("unchecked") 489 @Override 490 public T getFirstValue() { 491 Object value = getValue(); 492 if (value == null) { 493 return null; 494 } 495 if (value instanceof List) { 496 List<?> list = (List<?>) value; 497 if (list.isEmpty()) { 498 return null; 499 } 500 return (T) list.get(0); 501 } else { 502 return (T) value; 503 } 504 } 505 506 @SuppressWarnings("unchecked") 507 @Override 508 public List<T> getValues() { 509 Object value = getValue(); 510 if (value == null) { 511 return Collections.emptyList(); 512 } 513 if (value instanceof List) { 514 return (List<T>) value; 515 } else { 516 return (List<T>) Collections.singletonList(value); 517 } 518 } 519 520 @Override 521 public void setValue(Object value) { 522 if (readOnly) { 523 super.setValue(value); 524 } else { 525 Type type = doc.getProperty(name).getType(); 526 if (type.isListType()) { 527 type = ((ListType) type).getFieldType(); 528 } 529 Object propValue; 530 if (value instanceof List<?>) { 531 @SuppressWarnings("unchecked") 532 List<Object> list = new ArrayList<Object>((List<Object>) value); 533 for (int i = 0; i < list.size(); i++) { 534 list.set(i, convertToNuxeo(list.get(i), type)); 535 } 536 if (list.isEmpty()) { 537 list = null; 538 } 539 propValue = list; 540 } else { 541 propValue = convertToNuxeo(value, type); 542 } 543 doc.setPropertyValue(name, (Serializable) propValue); 544 } 545 } 546 547 protected static Blob getBlob(DocumentModel doc) throws CmisRuntimeException { 548 BlobHolder blobHolder = doc.getAdapter(BlobHolder.class); 549 if (blobHolder == null) { 550 return null; 551 } 552 return blobHolder.getBlob(); 553 } 554 555 public static class NuxeoPropertyStringData extends NuxeoPropertyData<String> implements PropertyString { 556 public NuxeoPropertyStringData(PropertyDefinition<String> propertyDefinition, DocumentModel doc, String name, 557 boolean readOnly, CallContext callContext) { 558 super(propertyDefinition, doc, name, readOnly, callContext); 559 } 560 } 561 562 public static class NuxeoPropertyIdData extends NuxeoPropertyData<String> implements PropertyId { 563 public NuxeoPropertyIdData(PropertyDefinition<String> propertyDefinition, DocumentModel doc, String name, 564 boolean readOnly, CallContext callContext) { 565 super(propertyDefinition, doc, name, readOnly, callContext); 566 } 567 } 568 569 public static class NuxeoPropertyBooleanData extends NuxeoPropertyData<Boolean> implements PropertyBoolean { 570 public NuxeoPropertyBooleanData(PropertyDefinition<Boolean> propertyDefinition, DocumentModel doc, String name, 571 boolean readOnly, CallContext callContext) { 572 super(propertyDefinition, doc, name, readOnly, callContext); 573 } 574 } 575 576 public static class NuxeoPropertyIntegerData extends NuxeoPropertyData<BigInteger> implements PropertyInteger { 577 public NuxeoPropertyIntegerData(PropertyDefinition<BigInteger> propertyDefinition, DocumentModel doc, 578 String name, boolean readOnly, CallContext callContext) { 579 super(propertyDefinition, doc, name, readOnly, callContext); 580 } 581 } 582 583 public static class NuxeoPropertyDecimalData extends NuxeoPropertyData<BigDecimal> implements PropertyDecimal { 584 public NuxeoPropertyDecimalData(PropertyDefinition<BigDecimal> propertyDefinition, DocumentModel doc, 585 String name, boolean readOnly, CallContext callContext) { 586 super(propertyDefinition, doc, name, readOnly, callContext); 587 } 588 } 589 590 public static class NuxeoPropertyDateTimeData extends NuxeoPropertyData<GregorianCalendar> implements 591 PropertyDateTime { 592 public NuxeoPropertyDateTimeData(PropertyDefinition<GregorianCalendar> propertyDefinition, DocumentModel doc, 593 String name, boolean readOnly, CallContext callContext) { 594 super(propertyDefinition, doc, name, readOnly, callContext); 595 } 596 } 597 598 public static class NuxeoPropertyHtmlData extends NuxeoPropertyData<String> implements PropertyHtml { 599 public NuxeoPropertyHtmlData(PropertyDefinition<String> propertyDefinition, DocumentModel doc, String name, 600 boolean readOnly, CallContext callContext) { 601 super(propertyDefinition, doc, name, readOnly, callContext); 602 } 603 } 604 605 public static class NuxeoPropertyUriData extends NuxeoPropertyData<String> implements PropertyUri { 606 public NuxeoPropertyUriData(PropertyDefinition<String> propertyDefinition, DocumentModel doc, String name, 607 boolean readOnly, CallContext callContext) { 608 super(propertyDefinition, doc, name, readOnly, callContext); 609 } 610 } 611 612 /** 613 * Property for cmis:contentStreamFileName. 614 */ 615 public static class NuxeoPropertyDataContentStreamFileName extends NuxeoPropertyDataBase<String> implements 616 PropertyString { 617 618 protected NuxeoPropertyDataContentStreamFileName(PropertyDefinition<String> propertyDefinition, 619 DocumentModel doc) { 620 super(propertyDefinition, doc); 621 } 622 623 @Override 624 public String getFirstValue() { 625 Blob blob = getBlob(doc); 626 return blob == null ? null : blob.getFilename(); 627 } 628 629 // @Override 630 // public void setValue(Serializable value) { 631 // BlobHolder blobHolder = docHolder.getDocumentModel().getAdapter( 632 // BlobHolder.class); 633 // if (blobHolder == null) { 634 // throw new StreamNotSupportedException(); 635 // } 636 // Blob blob; 637 // blob = blobHolder.getBlob(); 638 // if (blob != null) { 639 // blob.setFilename((String) value); 640 // } 641 // } 642 } 643 644 /** 645 * Property for cmis:contentStreamLength. 646 */ 647 public static class NuxeoPropertyDataContentStreamLength extends NuxeoPropertyDataBase<BigInteger> implements 648 PropertyInteger { 649 650 protected NuxeoPropertyDataContentStreamLength(PropertyDefinition<BigInteger> propertyDefinition, 651 DocumentModel doc) { 652 super(propertyDefinition, doc); 653 } 654 655 @Override 656 public BigInteger getFirstValue() { 657 Blob blob = getBlob(doc); 658 return blob == null ? null : BigInteger.valueOf(blob.getLength()); 659 } 660 } 661 662 /** 663 * Property for nuxeo:contentStreamDigest. 664 */ 665 public static class NuxeoPropertyDataContentStreamDigest extends NuxeoPropertyDataBase<String> implements 666 PropertyString { 667 668 protected NuxeoPropertyDataContentStreamDigest(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { 669 super(propertyDefinition, doc); 670 } 671 672 @Override 673 public String getFirstValue() { 674 Blob blob = getBlob(doc); 675 return blob == null ? null : blob.getDigest(); 676 } 677 } 678 679 /** 680 * Property for cmis:contentStreamHash. 681 */ 682 public static class NuxeoPropertyDataContentStreamHash extends NuxeoPropertyMultiDataFixed<String> implements 683 PropertyString { 684 685 protected NuxeoPropertyDataContentStreamHash(PropertyDefinition<String> propertyDefinition, List<String> hashes) { 686 super(propertyDefinition, hashes); 687 } 688 } 689 690 /** 691 * Property for cmis:contentMimeTypeLength. 692 */ 693 public static class NuxeoPropertyDataContentStreamMimeType extends NuxeoPropertyDataBase<String> implements 694 PropertyString { 695 696 protected NuxeoPropertyDataContentStreamMimeType(PropertyDefinition<String> propertyDefinition, 697 DocumentModel doc) { 698 super(propertyDefinition, doc); 699 } 700 701 @Override 702 public String getFirstValue() { 703 Blob blob = getBlob(doc); 704 return blob == null ? null : blob.getMimeType(); 705 } 706 } 707 708 /** 709 * Property for cmis:name. 710 */ 711 public static class NuxeoPropertyDataName extends NuxeoPropertyDataBase<String> implements PropertyString { 712 713 private static final Log log = LogFactory.getLog(NuxeoPropertyDataName.class); 714 715 protected NuxeoPropertyDataName(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { 716 super(propertyDefinition, doc); 717 } 718 719 /** 720 * Gets the value for the cmis:name property. 721 */ 722 public static String getValue(DocumentModel doc) { 723 if (doc.getPath() == null) { 724 // not a real doc (content changes) 725 return ""; 726 } 727 if (doc.getPath().isRoot()) { 728 return ""; // Nuxeo root 729 } 730 return doc.getTitle(); 731 } 732 733 @Override 734 public String getFirstValue() { 735 return getValue(doc); 736 } 737 738 @Override 739 public void setValue(Object value) { 740 try { 741 doc.setPropertyValue(NuxeoTypeHelper.NX_DC_TITLE, (String) value); 742 } catch (PropertyNotFoundException e) { 743 // trying to set the name of a type with no dublincore 744 // ignore 745 log.debug("Cannot set CMIS name on type: " + doc.getType()); 746 } 747 } 748 } 749 750 /** 751 * Property for cmis:parentId and nuxeo:parentId. 752 */ 753 public static class NuxeoPropertyDataParentId extends NuxeoPropertyDataBase<String> implements PropertyId { 754 755 protected NuxeoPropertyDataParentId(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { 756 super(propertyDefinition, doc); 757 } 758 759 @Override 760 public String getFirstValue() { 761 if (doc.getName() == null) { 762 return null; 763 } else { 764 DocumentRef parentRef = doc.getParentRef(); 765 if (parentRef == null) { 766 return null; // unfiled document 767 } else if (parentRef instanceof IdRef) { 768 return ((IdRef) parentRef).value; 769 } else { 770 return doc.getCoreSession().getDocument(parentRef).getId(); 771 } 772 } 773 } 774 } 775 776 /** 777 * Property for cmis:path. 778 */ 779 public static class NuxeoPropertyDataPath extends NuxeoPropertyDataBase<String> implements PropertyString { 780 781 protected NuxeoPropertyDataPath(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { 782 super(propertyDefinition, doc); 783 } 784 785 @Override 786 public String getFirstValue() { 787 String path = doc.getPathAsString(); 788 return path == null ? "" : path; 789 } 790 } 791 792 protected static boolean isLiveDocumentMajorVersion(DocumentModel doc) { 793 return !doc.isCheckedOut() && doc.getVersionLabel().endsWith(".0"); 794 } 795 796 /** 797 * Property for cmis:isMajorVersion. 798 */ 799 public static class NuxeoPropertyDataIsMajorVersion extends NuxeoPropertyDataBase<Boolean> implements 800 PropertyBoolean { 801 802 protected NuxeoPropertyDataIsMajorVersion(PropertyDefinition<Boolean> propertyDefinition, DocumentModel doc) { 803 super(propertyDefinition, doc); 804 } 805 806 @Override 807 public Boolean getFirstValue() { 808 if (doc.isVersion() || doc.isProxy()) { 809 return Boolean.valueOf(doc.isMajorVersion()); 810 } 811 // checked in doc considered latest version 812 return Boolean.valueOf(isLiveDocumentMajorVersion(doc)); 813 } 814 } 815 816 /** 817 * Property for cmis:isLatestVersion. 818 */ 819 public static class NuxeoPropertyDataIsLatestVersion extends NuxeoPropertyDataBase<Boolean> implements 820 PropertyBoolean { 821 822 protected NuxeoPropertyDataIsLatestVersion(PropertyDefinition<Boolean> propertyDefinition, DocumentModel doc) { 823 super(propertyDefinition, doc); 824 } 825 826 @Override 827 public Boolean getFirstValue() { 828 if (doc.isVersion() || doc.isProxy()) { 829 return Boolean.valueOf(doc.isLatestVersion()); 830 } 831 // checked in doc considered latest version 832 return Boolean.valueOf(!doc.isCheckedOut()); 833 } 834 } 835 836 /** 837 * Property for cmis:isLatestMajorVersion. 838 */ 839 public static class NuxeoPropertyDataIsLatestMajorVersion extends NuxeoPropertyDataBase<Boolean> implements 840 PropertyBoolean { 841 842 protected NuxeoPropertyDataIsLatestMajorVersion(PropertyDefinition<Boolean> propertyDefinition, 843 DocumentModel doc) { 844 super(propertyDefinition, doc); 845 } 846 847 @Override 848 public Boolean getFirstValue() { 849 if (doc.isVersion() || doc.isProxy()) { 850 return Boolean.valueOf(doc.isLatestMajorVersion()); 851 } 852 // checked in doc considered latest version 853 return Boolean.valueOf(isLiveDocumentMajorVersion(doc)); 854 } 855 } 856 857 /** 858 * Property for cmis:isVersionSeriesCheckedOut. 859 */ 860 public static class NuxeoPropertyDataIsVersionSeriesCheckedOut extends NuxeoPropertyDataBase<Boolean> implements 861 PropertyBoolean { 862 863 protected NuxeoPropertyDataIsVersionSeriesCheckedOut(PropertyDefinition<Boolean> propertyDefinition, 864 DocumentModel doc) { 865 super(propertyDefinition, doc); 866 } 867 868 @Override 869 public Boolean getFirstValue() { 870 return Boolean.valueOf(doc.isVersionSeriesCheckedOut()); 871 } 872 } 873 874 /** 875 * Property for cmis:versionSeriesCheckedOutId. 876 */ 877 public static class NuxeoPropertyDataVersionSeriesCheckedOutId extends NuxeoPropertyDataBase<String> implements 878 PropertyId { 879 880 protected NuxeoPropertyDataVersionSeriesCheckedOutId(PropertyDefinition<String> propertyDefinition, 881 DocumentModel doc) { 882 super(propertyDefinition, doc); 883 } 884 885 @Override 886 public String getFirstValue() { 887 if (!doc.isVersionSeriesCheckedOut()) { 888 return null; 889 } 890 DocumentModel pwc = doc.getCoreSession().getWorkingCopy(doc.getRef()); 891 return pwc == null ? null : pwc.getId(); 892 } 893 } 894 895 /** 896 * Property for cmis:versionSeriesCheckedOutBy. 897 */ 898 public static class NuxeoPropertyDataVersionSeriesCheckedOutBy extends NuxeoPropertyDataBase<String> implements 899 PropertyString { 900 901 protected final CallContext callContext; 902 903 protected NuxeoPropertyDataVersionSeriesCheckedOutBy(PropertyDefinition<String> propertyDefinition, 904 DocumentModel doc, CallContext callContext) { 905 super(propertyDefinition, doc); 906 this.callContext = callContext; 907 } 908 909 @Override 910 public String getFirstValue() { 911 if (!doc.isVersionSeriesCheckedOut()) { 912 return null; 913 } 914 DocumentModel pwc = doc.getCoreSession().getWorkingCopy(doc.getRef()); 915 // TODO not implemented 916 return pwc == null ? null : callContext.getUsername(); 917 } 918 } 919 920 /** 921 * Property for cmis:versionLabel. 922 */ 923 public static class NuxeoPropertyDataVersionLabel extends NuxeoPropertyDataBase<String> implements PropertyString { 924 925 protected NuxeoPropertyDataVersionLabel(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { 926 super(propertyDefinition, doc); 927 } 928 929 @Override 930 public String getFirstValue() { 931 if (doc.isVersion() || doc.isProxy()) { 932 return doc.getVersionLabel(); 933 } 934 return doc.isCheckedOut() ? null : doc.getVersionLabel(); 935 } 936 } 937 938 /** 939 * Property for cmis:checkinComment. 940 */ 941 public static class NuxeoPropertyDataCheckInComment extends NuxeoPropertyDataBase<String> implements PropertyString { 942 943 protected NuxeoPropertyDataCheckInComment(PropertyDefinition<String> propertyDefinition, DocumentModel doc) { 944 super(propertyDefinition, doc); 945 } 946 947 @Override 948 public String getFirstValue() { 949 if (doc.isVersion() || doc.isProxy()) { 950 return doc.getCheckinComment(); 951 } 952 if (doc.isCheckedOut()) { 953 return null; 954 } 955 CoreSession session = doc.getCoreSession(); 956 DocumentRef v = session.getBaseVersion(doc.getRef()); 957 DocumentModel ver = session.getDocument(v); 958 return ver.getCheckinComment(); 959 } 960 } 961 962}