/** Copies the array with an appropriate class depending on the type. */ protected static Object[] typedArray(Type type, Object[] array) { if (array == null) { array = EMPTY_STRING_ARRAY; } Class<?> klass; if (type instanceof StringType) { klass = String.class; } else if (type instanceof BooleanType) { klass = Boolean.class; } else if (type instanceof LongType) { klass = Long.class; } else if (type instanceof DoubleType) { klass = Double.class; } else if (type instanceof DateType) { klass = Calendar.class; } else if (type instanceof BinaryType) { klass = String.class; } else if (type instanceof IntegerType) { throw new RuntimeException("Unimplemented primitive type: " + type.getClass().getName()); } else if (type instanceof SimpleTypeImpl) { // simple type with constraints -- ignore constraints XXX return typedArray(type.getSuperType(), array); } else { throw new RuntimeException("Invalid primitive type: " + type.getClass().getName()); } int len = array.length; Object[] copy = (Object[]) Array.newInstance(klass, len); System.arraycopy(array, 0, copy, 0, len); return copy; }
protected Field getField(Field parent, String subFieldName, boolean finalCall) { if (parent != null) { Type type = parent.getType(); if (type.isListType()) { ListType listType = (ListType) type; // remove indexes in case of multiple values if ("*".equals(subFieldName)) { if (!finalCall) { return parent; } else { return resolveSubField(listType, null, true); } } try { Integer.valueOf(subFieldName); if (!finalCall) { return parent; } else { return resolveSubField(listType, null, true); } } catch (NumberFormatException e) { return resolveSubField(listType, subFieldName, false); } } else if (type.isComplexType()) { return ((ComplexType) type).getField(subFieldName); } } return null; }
protected void visitBlobsField(T state, Field field) throws PropertyException { Type type = field.getType(); if (type.isSimpleType()) { // scalar } else if (type.isComplexType()) { // complex property String name = field.getName().getPrefixedName(); T childState = getChild(state, name, type); if (childState != null) { path.addLast(name); visitBlobsComplex(childState, (ComplexType) type); path.removeLast(); } } else { // array or list Type fieldType = ((ListType) type).getFieldType(); if (fieldType.isSimpleType()) { // array } else { // complex list String name = field.getName().getPrefixedName(); path.addLast(name); int i = 0; for (T childState : getChildAsList(state, name)) { path.addLast(String.valueOf(i++)); visitBlobsComplex(childState, (ComplexType) fieldType); path.removeLast(); } path.removeLast(); } } }
protected Object getValueField(T state, Field field) throws PropertyException { Type type = field.getType(); String name = field.getName().getPrefixedName(); name = internalName(name); if (type.isSimpleType()) { // scalar return state.getSingle(name); } else if (type.isComplexType()) { // complex property T childState = getChild(state, name, type); if (childState == null) { return null; } return getValueComplex(childState, (ComplexType) type); } else { // array or list Type fieldType = ((ListType) type).getFieldType(); if (fieldType.isSimpleType()) { // array return state.getArray(name); } else { // complex list List<T> childStates = getChildAsList(state, name); List<Object> list = new ArrayList<>(childStates.size()); for (T childState : childStates) { Object value = getValueComplex(childState, (ComplexType) fieldType); list.add(value); } return list; } } }
protected Field resolveSubField(ListType listType, String subName, boolean fallbackOnSubElement) { Type itemType = listType.getFieldType(); if (itemType.isComplexType() && subName != null) { ComplexType complexType = (ComplexType) itemType; Field subField = complexType.getField(subName); return subField; } if (fallbackOnSubElement) { return listType.getField(); } return null; }
/** Reads state into a complex property. */ protected void readComplexProperty(T state, ComplexProperty complexProperty) throws PropertyException { if (state == null) { complexProperty.init(null); return; } if (complexProperty instanceof BlobProperty) { Blob blob = getValueBlob(state); complexProperty.init((Serializable) blob); return; } for (Property property : complexProperty) { String name = property.getField().getName().getPrefixedName(); name = internalName(name); Type type = property.getType(); if (type.isSimpleType()) { // simple property Object value = state.getSingle(name); if (value instanceof Delta) { value = ((Delta) value).getFullValue(); } property.init((Serializable) value); } else if (type.isComplexType()) { // complex property T childState = getChild(state, name, type); readComplexProperty(childState, (ComplexProperty) property); ((ComplexProperty) property).removePhantomFlag(); } else { ListType listType = (ListType) type; if (listType.getFieldType().isSimpleType()) { // array Object[] array = state.getArray(name); array = typedArray(listType.getFieldType(), array); property.init(array); } else { // complex list Field listField = listType.getField(); List<T> childStates = getChildAsList(state, name); // TODO property.init(null) if null children in DBS List<Object> list = new ArrayList<>(childStates.size()); for (T childState : childStates) { ComplexProperty p = (ComplexProperty) complexProperty.getRoot().createProperty(property, listField, 0); readComplexProperty(childState, p); list.add(p.getValue()); } property.init((Serializable) list); } } } }
/** * Builds the property. * * @param document the document * @param parent the parent * @param field the field * @param dateVal the dateVal * @throws IOException Signals that an I/O exception has occurred. */ private static void buildProperty(Document document, Element parent, Field field, Object value) throws IOException { Type type = field.getType(); // no need to qualify each element name as namespace is already added String propName = field.getName().getLocalName(); Element element = document.createElement(propName); parent.appendChild(element); // extract the element content if (type.isSimpleType()) { // Avoid returning scientific notation representations of // very large or very small decimal values. See CSPACE-4691. if (isNuxeoDecimalType(type) && valueMatchesNuxeoType(type, value)) { element.setTextContent(nuxeoDecimalValueToDecimalString(value)); /* * We need a way to produce just a Date when the specified data * type is an xs:date vs. xs:datetime. Nuxeo maps both to a Calendar. Sigh. if(logger.isTraceEnabled() && isDateType(type)) { String dateValType = "unknown"; if (value instanceof java.util.Date) { dateValType = "java.util.Date"; } else if (value instanceof java.util.Calendar) { dateValType = "java.util.Calendar"; } logger.trace("building XML for date type: "+type.getName() +" value type: "+dateValType +" encoded: "+encodedVal); } */ } else { String encodedVal = type.encode(value); element.setTextContent(encodedVal); } } else if (type.isComplexType()) { ComplexType ctype = (ComplexType) type; if (ctype.getName().equals(TypeConstants.CONTENT)) { throw new RuntimeException("Unexpected schema type: BLOB for field: " + propName); } else { buildComplex(document, element, ctype, (Map) value); } } else if (type.isListType()) { if (value instanceof List) { buildList(document, element, (ListType) type, (List) value); } else if (value.getClass().getComponentType() != null) { buildList(document, element, (ListType) type, PrimitiveArrays.toList(value)); } else { throw new IllegalArgumentException( "A value of list type is neither list neither array: " + value); } } }
protected void readPrefetchField( T state, Field field, String xpathGeneric, String xpath, Set<String> prefixes, Map<String, Serializable> prefetch) { String name = field.getName().getPrefixedName(); Type type = field.getType(); xpathGeneric = xpathGeneric == null ? name : xpathGeneric + '/' + name; xpath = xpath == null ? name : xpath + '/' + name; if (!prefixes.contains(xpathGeneric)) { return; } if (type.isSimpleType()) { // scalar Object value = state.getSingle(name); prefetch.put(xpath, (Serializable) value); } else if (type.isComplexType()) { // complex property T childState = getChild(state, name, type); if (childState != null) { readPrefetch(childState, (ComplexType) type, xpathGeneric, xpath, prefixes, prefetch); } } else { // array or list ListType listType = (ListType) type; if (listType.getFieldType().isSimpleType()) { // array Object[] value = state.getArray(name); prefetch.put(xpath, value); } else { // complex list List<T> childStates = getChildAsList(state, name); Field listField = listType.getField(); xpathGeneric += "/*"; int i = 0; for (T childState : childStates) { readPrefetch( childState, (ComplexType) listField.getType(), xpathGeneric, xpath + '/' + i++, prefixes, prefetch); } } } }
private static boolean valueMatchesNuxeoType(Type type, Object value) { try { return type.validate(value); } catch (TypeException te) { return false; } }
protected DocumentType recomputeDocumentType( String name, Set<String> stack, Map<String, DocumentTypeDescriptor> dtds) { DocumentTypeImpl docType = documentTypes.get(name); if (docType != null) { // already done return docType; } if (stack.contains(name)) { log.error("Document type: " + name + " used in parent inheritance loop: " + stack); return null; } DocumentTypeDescriptor dtd = dtds.get(name); if (dtd == null) { log.error("Document type: " + name + " does not exist, used as parent by type: " + stack); return null; } // find and recompute the parent first DocumentType parent; String parentName = dtd.superTypeName; if (parentName == null) { parent = null; } else { parent = documentTypes.get(parentName); if (parent == null) { stack.add(name); parent = recomputeDocumentType(parentName, stack, dtds); stack.remove(name); } } // what it extends for (Type p = parent; p != null; p = p.getSuperType()) { Set<String> set = documentTypesExtending.get(p.getName()); set.add(name); } return recomputeDocumentType(name, dtd, parent); }
protected void setValueField(T state, Field field, Object value) throws PropertyException { Type type = field.getType(); String name = field.getName().getPrefixedName(); // normalize from map key name = internalName(name); // TODO we could check for read-only here if (type.isSimpleType()) { // scalar state.setSingle(name, value); } else if (type.isComplexType()) { // complex property T childState = getChildForWrite(state, name, type); setValueComplex(childState, field, value); } else { // array or list ListType listType = (ListType) type; Type fieldType = listType.getFieldType(); if (fieldType.isSimpleType()) { // array if (value instanceof List) { value = ((List<?>) value).toArray(new Object[0]); } state.setArray(name, (Object[]) value); } else { // complex list if (value != null && !(value instanceof List)) { throw new PropertyException( "Expected List value for: " + name + ", got " + value.getClass().getName() + " instead"); } @SuppressWarnings("unchecked") List<Object> values = value == null ? Collections.emptyList() : (List<Object>) value; updateList(state, name, values, listType.getField()); } } }
protected void writeListProperty(JsonGenerator jg, Property prop) throws IOException { jg.writeStartArray(); if (prop instanceof ArrayProperty) { Object[] ar = (Object[]) prop.getValue(); if (ar == null) { jg.writeEndArray(); return; } Type itemType = ((ListType) prop.getType()).getFieldType(); ObjectResolver resolver = itemType.getObjectResolver(); String path = prop.getPath(); for (Object o : ar) { if (!fetchProperty(jg, resolver, o, path)) { writeScalarPropertyValue(jg, ((SimpleType) itemType).getPrimitiveType(), o); } } } else { ListProperty listp = (ListProperty) prop; for (Property p : listp.getChildren()) { writeProperty(jg, p); } } jg.writeEndArray(); }
private void writeScalarPropertyValue(JsonGenerator jg, Type type, Object value) throws IOException { if (value == null) { jg.writeNull(); } else if (type instanceof BooleanType) { jg.writeBoolean((Boolean) value); } else if (type instanceof LongType) { jg.writeNumber((Long) value); } else if (type instanceof DoubleType) { jg.writeNumber((Double) value); } else if (type instanceof IntegerType) { jg.writeNumber((Integer) value); } else if (type instanceof BinaryType) { jg.writeBinary((byte[]) value); } else { jg.writeString(type.encode(value)); } }
protected void registerType(Type type) { types.put(type.getName(), type); }
/** * Gets the element data. * * @param element the element * @param type the type * @return the element data */ @SuppressWarnings("unchecked") private static Object getElementData(org.dom4j.Element element, Type type, ServiceContext ctx) throws Exception { Object result = null; String dateStr = ""; if (type.isSimpleType()) { if (isNuxeoDateType(type)) { String dateVal = element.getText(); if (dateVal == null || dateVal.trim().isEmpty()) { result = type.decode(""); } else { // Dates or date/times in any ISO 8601-based representations // directly supported by Nuxeo will be successfully decoded. result = type.decode(dateVal); // All other date or date/time values must first be converted // to a supported ISO 8601-based representation. if (result == null) { dateStr = DateTimeFormatUtils.toIso8601Timestamp(dateVal, ctx.getTenantId()); if (dateStr != null) { result = type.decode(dateStr); } else { throw new IllegalArgumentException( "Unrecognized date value '" + dateVal + "' in field '" + element.getName() + "'"); } } } } else { String textValue = element.getText(); if (textValue != null && textValue.trim().isEmpty()) { result = null; } else { result = type.decode(textValue); } } } else if (type.isListType()) { ListType ltype = (ListType) type; List<Object> list = new ArrayList<Object>(); Iterator<org.dom4j.Element> it = element.elementIterator(); while (it.hasNext()) { org.dom4j.Element el = it.next(); list.add(getElementData(el, ltype.getFieldType(), ctx)); } Type ftype = ltype.getFieldType(); if (ftype.isSimpleType()) { // these are stored as arrays Class klass = JavaTypes.getClass(ftype); if (klass.isPrimitive()) { return PrimitiveArrays.toPrimitiveArray(list, klass); } else { return list.toArray((Object[]) Array.newInstance(klass, list.size())); } } result = list; } else { ComplexType ctype = (ComplexType) type; if (ctype.getName().equals(TypeConstants.CONTENT)) { // String mimeType = element.elementText(ExportConstants.BLOB_MIME_TYPE); // String encoding = element.elementText(ExportConstants.BLOB_ENCODING); // String content = element.elementTextTrim(ExportConstants.BLOB_DATA); // if ((content == null || content.length() == 0) // && (mimeType == null || mimeType.length() == 0)) { // return null; // remove blob // } // Blob blob = null; // if (xdoc.hasExternalBlobs()) { // blob = xdoc.getBlob(content); // } // if (blob == null) { // may be the blob is embedded like a Base64 // // encoded data // byte[] bytes = Base64.decode(content); // blob = new StreamingBlob(new ByteArraySource(bytes)); // } // blob.setMimeType(mimeType); // blob.setEncoding(encoding); // return blob; } else { // a complex type Map<String, Object> map = new HashMap<String, Object>(); Iterator<org.dom4j.Element> it = element.elementIterator(); while (it.hasNext()) { org.dom4j.Element el = it.next(); String name = el.getName(); Object value = getElementData(el, ctype.getField(el.getName()).getType(), ctx); map.put(name, value); } result = map; } } return result; }
/** * Writes state from a complex property. * * <p>Writes only properties that are dirty. * * @return {@code true} if something changed */ protected boolean writeComplexProperty( T state, ComplexProperty complexProperty, String xpath, WriteContext wc) throws PropertyException { @SuppressWarnings("unchecked") BlobWriteContext<T> writeContext = (BlobWriteContext<T>) wc; if (complexProperty instanceof BlobProperty) { Serializable value = ((BlobProperty) complexProperty).getValueForWrite(); if (value != null && !(value instanceof Blob)) { throw new PropertyException("Cannot write a non-Blob value: " + value); } writeContext.recordBlob(state, (Blob) value, this); return true; } boolean changed = false; for (Property property : complexProperty) { // write dirty properties, but also phantoms with non-null default values // this is critical for DeltaLong updates to work, they need a non-null initial value if (property.isDirty() || (property.isPhantom() && property.getField().getDefaultValue() != null)) { // do the write } else { continue; } String name = property.getField().getName().getPrefixedName(); name = internalName(name); if (checkReadOnlyIgnoredWrite(property, state)) { continue; } String xp = xpath == null ? name : xpath + '/' + name; writeContext.recordChange(xp); changed = true; Type type = property.getType(); if (type.isSimpleType()) { // simple property Serializable value = property.getValueForWrite(); state.setSingle(name, value); if (value instanceof Delta) { value = ((Delta) value).getFullValue(); ((ScalarProperty) property).internalSetValue(value); } } else if (type.isComplexType()) { // complex property T childState = getChildForWrite(state, name, type); writeComplexProperty(childState, (ComplexProperty) property, xp, writeContext); } else { ListType listType = (ListType) type; if (listType.getFieldType().isSimpleType()) { // array Serializable value = property.getValueForWrite(); if (value instanceof List) { List<?> list = (List<?>) value; Object[] array; if (list.isEmpty()) { array = new Object[0]; } else { // use properly-typed array, useful for mem backend that doesn't re-convert all types Class<?> klass = list.get(0).getClass(); array = (Object[]) Array.newInstance(klass, list.size()); } value = list.toArray(array); } else if (value instanceof Object[]) { Object[] ar = (Object[]) value; if (ar.length != 0) { // use properly-typed array, useful for mem backend that doesn't re-convert all types Class<?> klass = Object.class; for (Object o : ar) { if (o != null) { klass = o.getClass(); break; } } Object[] array; if (ar.getClass().getComponentType() == klass) { array = ar; } else { // copy to array with proper component type array = (Object[]) Array.newInstance(klass, ar.length); System.arraycopy(ar, 0, array, 0, ar.length); } value = array; } } else if (value == null) { // ok } else { throw new IllegalStateException(value.toString()); } state.setArray(name, (Object[]) value); } else { // complex list // update it List<T> childStates = updateList(state, name, property); // write values int i = 0; for (Property childProperty : property.getChildren()) { T childState = childStates.get(i); String xpi = xp + '/' + i; boolean c = writeComplexProperty( childState, (ComplexProperty) childProperty, xpi, writeContext); if (c) { writeContext.recordChange(xpi); } i++; } } } } return changed; }
/** Gets a value (may be complex/list) from the document at the given xpath. */ protected Object getValueObject(T state, String xpath) throws PropertyException { xpath = canonicalXPath(xpath); String[] segments = xpath.split("/"); /* * During this loop state may become null if we read an uninitialized complex property (DBS), in that case the * code must treat it as reading uninitialized values for its children. */ ComplexType parentType = getType(); for (int i = 0; i < segments.length; i++) { String segment = segments[i]; Field field = parentType.getField(segment); if (field == null && i == 0) { // check facets SchemaManager schemaManager = Framework.getService(SchemaManager.class); for (String facet : getFacets()) { CompositeType facetType = schemaManager.getFacet(facet); field = facetType.getField(segment); if (field != null) { break; } } } if (field == null && i == 0 && getProxySchemas() != null) { // check proxy schemas for (Schema schema : getProxySchemas()) { field = schema.getField(segment); if (field != null) { break; } } } if (field == null) { throw new PropertyNotFoundException(xpath, i == 0 ? null : "Unknown segment: " + segment); } String name = field.getName().getPrefixedName(); // normalize from segment Type type = field.getType(); // check if we have a complex list index in the next position if (i < segments.length - 1 && StringUtils.isNumeric(segments[i + 1])) { int index = Integer.parseInt(segments[i + 1]); i++; if (!type.isListType() || ((ListType) type).getFieldType().isSimpleType()) { throw new PropertyNotFoundException(xpath, "Cannot use index after segment: " + segment); } List<T> list = state == null ? Collections.emptyList() : getChildAsList(state, name); if (index >= list.size()) { throw new PropertyNotFoundException(xpath, "Index out of bounds: " + index); } // find complex list state state = list.get(index); parentType = (ComplexType) ((ListType) type).getFieldType(); if (i == segments.length - 1) { // last segment return getValueComplex(state, parentType); } else { // not last segment continue; } } if (i == segments.length - 1) { // last segment return state == null ? null : getValueField(state, field); } else { // not last segment if (type.isSimpleType()) { // scalar throw new PropertyNotFoundException(xpath, "Segment must be last: " + segment); } else if (type.isComplexType()) { // complex property state = state == null ? null : getChild(state, name, type); // here state can be null (DBS), continue loop with it, meaning uninitialized for read parentType = (ComplexType) type; } else { // list ListType listType = (ListType) type; if (listType.isArray()) { // array of scalars throw new PropertyNotFoundException(xpath, "Segment must be last: " + segment); } else { // complex list but next segment was not numeric throw new PropertyNotFoundException( xpath, "Missing list index after segment: " + segment); } } } } throw new AssertionError("not reached"); }
/** Sets a value (may be complex/list) into the document at the given xpath. */ protected void setValueObject(T state, String xpath, Object value) throws PropertyException { xpath = canonicalXPath(xpath); String[] segments = xpath.split("/"); ComplexType parentType = getType(); for (int i = 0; i < segments.length; i++) { String segment = segments[i]; Field field = parentType.getField(segment); if (field == null && i == 0) { // check facets SchemaManager schemaManager = Framework.getService(SchemaManager.class); for (String facet : getFacets()) { CompositeType facetType = schemaManager.getFacet(facet); field = facetType.getField(segment); if (field != null) { break; } } } if (field == null && i == 0 && getProxySchemas() != null) { // check proxy schemas for (Schema schema : getProxySchemas()) { field = schema.getField(segment); if (field != null) { break; } } } if (field == null) { throw new PropertyNotFoundException(xpath, i == 0 ? null : "Unknown segment: " + segment); } String name = field.getName().getPrefixedName(); // normalize from segment Type type = field.getType(); // check if we have a complex list index in the next position if (i < segments.length - 1 && StringUtils.isNumeric(segments[i + 1])) { int index = Integer.parseInt(segments[i + 1]); i++; if (!type.isListType() || ((ListType) type).getFieldType().isSimpleType()) { throw new PropertyNotFoundException(xpath, "Cannot use index after segment: " + segment); } List<T> list = getChildAsList(state, name); if (index >= list.size()) { throw new PropertyNotFoundException(xpath, "Index out of bounds: " + index); } // find complex list state state = list.get(index); field = ((ListType) type).getField(); if (i == segments.length - 1) { // last segment setValueComplex(state, field, value); } else { // not last segment parentType = (ComplexType) field.getType(); } continue; } if (i == segments.length - 1) { // last segment setValueField(state, field, value); } else { // not last segment if (type.isSimpleType()) { // scalar throw new PropertyNotFoundException(xpath, "Segment must be last: " + segment); } else if (type.isComplexType()) { // complex property state = getChildForWrite(state, name, type); parentType = (ComplexType) type; } else { // list ListType listType = (ListType) type; if (listType.isArray()) { // array of scalars throw new PropertyNotFoundException(xpath, "Segment must be last: " + segment); } else { // complex list but next segment was not numeric throw new PropertyNotFoundException( xpath, "Missing list index after segment: " + segment); } } } } }