/*
* (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* bstefanescu
*/
package org.nuxeo.ecm.automation.core.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import org.nuxeo.common.utils.StringUtils;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.NuxeoException;
import org.nuxeo.ecm.core.api.PropertyException;
import org.nuxeo.ecm.core.api.model.Property;
import org.nuxeo.ecm.core.api.model.impl.ListProperty;
import org.nuxeo.ecm.core.api.security.ACE;
import org.nuxeo.ecm.core.api.security.ACL;
import org.nuxeo.ecm.core.api.security.impl.ACLImpl;
import org.nuxeo.ecm.core.api.security.impl.ACPImpl;
import org.nuxeo.ecm.core.schema.types.ComplexType;
import org.nuxeo.ecm.core.schema.types.ListType;
import org.nuxeo.ecm.core.schema.types.SimpleType;
import org.nuxeo.ecm.core.schema.types.Type;
import org.nuxeo.ecm.core.schema.types.primitives.BinaryType;
import org.nuxeo.ecm.core.schema.types.primitives.BooleanType;
import org.nuxeo.ecm.core.schema.types.primitives.DateType;
import org.nuxeo.ecm.core.schema.types.primitives.DoubleType;
import org.nuxeo.ecm.core.schema.types.primitives.IntegerType;
import org.nuxeo.ecm.core.schema.types.primitives.LongType;
import org.nuxeo.ecm.core.schema.types.primitives.StringType;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class DocumentHelper {
private DocumentHelper() {
}
/**
* Saves the document and clear context data to avoid incrementing version in next operations if not needed.
*/
public static DocumentModel saveDocument(CoreSession session, DocumentModel doc) {
doc = session.saveDocument(doc);
return session.getDocument(doc.getRef());
}
/**
* Removes a property from a document given the xpath. If the xpath points to a list property the list will be
* cleared. If the path points to a blob in a list the property is removed from the list. Otherwise the xpath should
* point to a non list property that will be removed.
*/
public static void removeProperty(DocumentModel doc, String xpath) {
Property p = doc.getProperty(xpath);
if (p instanceof ListProperty) {
((ListProperty) p).clear();
} else {
Property pp = p.getParent();
if (pp != null && pp.isList()) { // remove list entry
((ListProperty) pp).remove(p);
} else {
p.remove();
}
}
}
/**
* Given a document property, updates its value with the given blob. The property can be a blob list or a blob. If a
* blob list the blob is appended to the list, if a blob then it will be set as the property value. Both blob list
* formats are supported: the file list (blob holder list) and simple blob list.
*/
public static void addBlob(Property p, Blob blob) throws PropertyException {
if (p.isList()) {
// detect if a list of simple blobs or a list of files (blob
// holder)
Type ft = ((ListProperty) p).getType().getFieldType();
if (ft.isComplexType() && ((ComplexType) ft).getFieldsCount() == 1) {
p.addValue(createBlobHolderMap(blob));
} else {
p.addValue(blob);
}
} else {
p.setValue(blob);
}
}
public static HashMap<String, Serializable> createBlobHolderMap(Blob blob) {
HashMap<String, Serializable> map = new HashMap<>();
map.put("file", (Serializable) blob);
return map;
}
public static void setProperties(CoreSession session, DocumentModel doc, Properties properties)
throws IOException, PropertyException {
if (properties instanceof DataModelProperties) {
DataModelProperties dataModelProperties = (DataModelProperties) properties;
for (Map.Entry<String, Serializable> entry : dataModelProperties.getMap().entrySet()) {
doc.setPropertyValue(entry.getKey(), entry.getValue());
}
}
for (Map.Entry<String, String> entry : properties.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
setProperty(session, doc, key, value);
}
}
/**
* Sets the properties given as a map of xpath:value to the given document. There is one special property: ecm:acl
* that can be used to set the local acl. The format of this property value is: [string username]:[string
* permission]:[boolean grant], [string username]:[string permission]:[boolean grant], ... TODO list properties are
* not yet supported
*/
public static void setProperties(CoreSession session, DocumentModel doc, Map<String, String> values)
throws IOException {
for (Map.Entry<String, String> entry : values.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
setProperty(session, doc, key, value);
}
}
public static void setProperty(CoreSession session, DocumentModel doc, String key, String value)
throws IOException {
setProperty(session, doc, key, value, false);
}
protected static void setLocalAcl(CoreSession session, DocumentModel doc, String value) {
ACPImpl acp = new ACPImpl();
ACLImpl acl = new ACLImpl(ACL.LOCAL_ACL);
acp.addACL(acl);
String[] entries = StringUtils.split(value, ',', true);
if (entries.length == 0) {
return;
}
for (String entry : entries) {
String[] ace = StringUtils.split(entry, ':', true);
acl.add(new ACE(ace[0], ace[1], Boolean.parseBoolean(ace[2])));
}
session.setACP(doc.getRef(), acp, false);
}
/**
* Read an encoded string list as a comma separated list. To use comma inside list element values you need to escape
* them using '\'. If the given type is different from {@link StringType#ID} then array elements will be converted
* to the actual type.
*/
public static Object readStringList(String value, SimpleType type) {
if (!type.isPrimitive()) {
return readStringList(value, type.getPrimitiveType());
}
String[] ar = readStringList(value);
if (ar == null) {
return null;
}
if (StringType.INSTANCE == type) {
return ar;
} else if (DateType.INSTANCE == type) {
Calendar[] r = new Calendar[ar.length];
for (int i = 0; i < r.length; i++) {
r[i] = (Calendar) type.decode(ar[i]);
}
return r;
} else if (LongType.INSTANCE == type) {
Long[] r = new Long[ar.length];
for (int i = 0; i < r.length; i++) {
r[i] = (Long) type.decode(ar[i]);
}
return r;
} else if (IntegerType.INSTANCE == type) {
Integer[] r = new Integer[ar.length];
for (int i = 0; i < r.length; i++) {
r[i] = (Integer) type.decode(ar[i]);
}
return r;
} else if (DoubleType.INSTANCE == type) {
Double[] r = new Double[ar.length];
for (int i = 0; i < r.length; i++) {
r[i] = (Double) type.decode(ar[i]);
}
return r;
} else if (BooleanType.INSTANCE == type) {
Boolean[] r = new Boolean[ar.length];
for (int i = 0; i < r.length; i++) {
r[i] = (Boolean) type.decode(ar[i]);
}
return r;
} else if (BinaryType.INSTANCE == type) {
InputStream[] r = new InputStream[ar.length];
for (int i = 0; i < r.length; i++) {
r[i] = (InputStream) type.decode(ar[i]);
}
return r;
}
throw new IllegalArgumentException(
"Unsupported type when updating document properties from string representation: " + type);
}
/**
* Read an encoded string list as a comma separated list. To use comma inside list element values you need to escape
* them using '\'.
*/
public static String[] readStringList(String value) {
if (value == null) {
return null;
}
if (value.length() == 0) {
return new String[0];
}
ArrayList<String> result = new ArrayList<>();
char[] chars = value.toCharArray();
StringBuilder buf = new StringBuilder();
boolean esc = false;
for (char c : chars) {
if (c == '\\') {
if (esc) {
buf.append('\\');
esc = false;
} else {
esc = true;
}
} else if (c == ',') {
if (esc) {
buf.append(',');
esc = false;
} else {
result.add(buf.toString());
buf = new StringBuilder();
}
} else {
buf.append(c);
}
}
result.add(buf.toString());
return result.toArray(new String[result.size()]);
}
/**
* Sets the properties of a document based on their JSON representation (especially for scalar lists).
*
* @since 5.9.2
*/
public static void setJSONProperties(CoreSession session, DocumentModel doc, Properties properties)
throws IOException {
for (Map.Entry<String, String> entry : properties.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
setProperty(session, doc, key, value, true);
}
}
/**
* @since 5.9.2
*/
public static void setProperty(CoreSession session, DocumentModel doc, String key, String value,
boolean decodeStringListAsJSON) throws IOException {
if ("ecm:acl".equals(key)) {
setLocalAcl(session, doc, value);
}
Property p = doc.getProperty(key);
if (value == null || value.length() == 0) {
p.setValue(null);
return;
}
Type type = p.getField().getType();
if (!type.isSimpleType()) {
if (type.isListType()) {
ListType ltype = (ListType) type;
if (ltype.isScalarList() && !decodeStringListAsJSON) {
p.setValue(readStringList(value, (SimpleType) ltype.getFieldType()));
return;
} else {
Object val = ComplexTypeJSONDecoder.decodeList(ltype, value);
p.setValue(val);
return;
}
} else if (type.isComplexType()) {
Object val = ComplexTypeJSONDecoder.decode((ComplexType) type, value);
p.setValue(val);
return;
}
throw new NuxeoException("Property type is not supported by this operation");
} else {
p.setValue(((SimpleType) type).getPrimitiveType().decode(value));
}
}
}