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