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