aboutsummaryrefslogtreecommitdiffstats
path: root/docproc/src/main/java/com/yahoo/docproc/proxy/ProxyDocument.java
diff options
context:
space:
mode:
Diffstat (limited to 'docproc/src/main/java/com/yahoo/docproc/proxy/ProxyDocument.java')
-rw-r--r--docproc/src/main/java/com/yahoo/docproc/proxy/ProxyDocument.java361
1 files changed, 361 insertions, 0 deletions
diff --git a/docproc/src/main/java/com/yahoo/docproc/proxy/ProxyDocument.java b/docproc/src/main/java/com/yahoo/docproc/proxy/ProxyDocument.java
new file mode 100644
index 00000000000..b3b29a41cc7
--- /dev/null
+++ b/docproc/src/main/java/com/yahoo/docproc/proxy/ProxyDocument.java
@@ -0,0 +1,361 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.docproc.proxy;
+
+import com.yahoo.docproc.Accesses;
+import com.yahoo.docproc.DocumentOperationWrapper;
+import com.yahoo.docproc.DocumentProcessor;
+import com.yahoo.document.DataType;
+import com.yahoo.document.Document;
+import com.yahoo.document.DocumentId;
+import com.yahoo.document.DocumentOperation;
+import com.yahoo.document.DocumentPut;
+import com.yahoo.document.DocumentType;
+import com.yahoo.document.Field;
+import com.yahoo.document.FieldPath;
+import com.yahoo.document.datatypes.FieldPathIteratorHandler;
+import com.yahoo.document.datatypes.FieldPathIteratorHandler.ModificationStatus;
+import com.yahoo.document.datatypes.FieldValue;
+import com.yahoo.document.datatypes.Struct;
+import com.yahoo.document.serialization.DocumentReader;
+import com.yahoo.document.serialization.DocumentWriter;
+import com.yahoo.document.serialization.FieldReader;
+import com.yahoo.document.serialization.FieldWriter;
+import com.yahoo.document.serialization.SerializationException;
+import com.yahoo.document.serialization.XmlStream;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * This is a facade to a Document, with multiple purposes: <ul> <li>Getters and setters for field data takes possibly
+ * into account a schema map of field names. <li>We support mapping into struct fields of arbitrary depth using
+ * from→mystruct.mystruct.myfield </ul> We also enforce the @Accesses annotation(s) of the doc proc which uses this.
+ *
+ * @author <a href="mailto:vegardh@yahoo-inc.com">Vegard Havdal</a>
+ */
+public class ProxyDocument extends Document implements DocumentOperationWrapper {
+
+ private static final long serialVersionUID = 1L;
+ private final Map<String, String> fieldMap;
+ private final Set<String> fieldsAllowed = new HashSet<>();
+ private final String docProcName;
+ private Document doc;
+
+ public ProxyDocument(DocumentProcessor docProc, Document doc, Map<String, String> fieldMap) {
+ super(doc);
+ if (docProc.getClass().getAnnotation(Accesses.class)!=null) {
+ for (com.yahoo.docproc.Accesses.Field field : docProc.getClass().getAnnotation(Accesses.class).value()) {
+ String name = field.name();
+ if (fieldMap!=null && fieldMap.get(name) !=null) name = fieldMap.get(name);
+ fieldsAllowed.add(name);
+ }
+ }
+ this.fieldMap = fieldMap;
+ this.docProcName = docProc.toString();
+ this.doc=doc;
+ }
+
+ private void checkAccess(Field field) {
+ if (!fieldsAllowed.isEmpty() && !fieldsAllowed.contains(field.getName())) {
+ throw new IllegalArgumentException("Processor '" + docProcName + "' is not allowed to access field '" +
+ field.getName() + "'.");
+ }
+ }
+
+ /**
+ * note that the returned Field may not be in this Document
+ * directly, but may refer to a field in a struct contained in it,
+ * in which case the returned Field is only useful for obtaining
+ * the field type; it can't be used for get() and set().
+ **/
+ @Override
+ public Field getField(String fieldName) {
+ if (fieldMap != null && fieldMap.containsKey(fieldName)) {
+ fieldName = fieldMap.get(fieldName);
+ }
+ FieldPath path = getFieldPath(fieldName);
+ Field ret = path.get(path.size() - 1).getFieldRef();
+ checkAccess(ret);
+ return ret;
+ }
+
+ @Override
+ public FieldValue getFieldValue(String fieldName) {
+ return getRecursiveValue(getFieldPath(fieldName));
+ }
+
+ @Override
+ public FieldValue getFieldValue(Field field) {
+ //checkAccess(field);
+ return doc.getFieldValue(field);
+ }
+
+ @Override
+ public FieldValue setFieldValue(String fieldName, FieldValue fieldValue) {
+ SetHandler handler = new SetHandler(fieldValue);
+ FieldPath path = getFieldPath(fieldName);
+ iterateNested(path, 0, handler);
+ if (!handler.doModifyCalled) {
+ //the value in question was not found
+ throw new IllegalArgumentException("Field '" + fieldName + "' mapped by '" + path + "' was not found.");
+ }
+ return handler.prevVal;
+ }
+
+ @Override
+ public FieldValue setFieldValue(Field field, FieldValue fieldValue) {
+ checkAccess(field);
+ return doc.setFieldValue(field, fieldValue);
+ }
+
+ @Override
+ public FieldValue removeFieldValue(String fieldName) {
+ RemoveHandler handler = new RemoveHandler();
+ FieldPath path = getFieldPath(fieldName);
+ iterateNested(path, 0, handler);
+ if (!handler.doModifyCalled) {
+ //the value in question was not found
+ throw new IllegalArgumentException("Field '" + fieldName + "' mapped by '" + path + "' was not found.");
+ }
+ return handler.prevVal;
+ }
+
+ @Override
+ public FieldValue removeFieldValue(Field field) {
+ checkAccess(field);
+ return doc.removeFieldValue(field);
+ }
+
+ private FieldPath getFieldPath(String fieldName) {
+ if (fieldMap != null && fieldMap.containsKey(fieldName)) {
+ fieldName = fieldMap.get(fieldName);
+ }
+ checkAccess(new Field(fieldName));
+ FieldPath path = FieldPath.newInstance(getDataType(), fieldName);
+ if (path == null || path.size() == 0) {
+ throw new IllegalArgumentException("Malformed schema mapping '" + fieldName + "'.");
+ }
+ return path;
+ }
+
+ public DocumentOperation getWrappedDocumentOperation() {
+ return new DocumentPut(this);
+ }
+
+ private static class SetHandler extends FieldPathIteratorHandler {
+
+ private final FieldValue nextVal;
+ private FieldValue prevVal;
+ private boolean doModifyCalled = false;
+
+ public SetHandler(FieldValue fieldValue) {
+ nextVal = fieldValue;
+ }
+
+ @Override
+ public boolean onComplex(FieldValue fieldVal) {
+ return false;
+ }
+
+ @Override
+ public boolean createMissingPath() {
+ return true;
+ }
+
+
+ @Override
+ public ModificationStatus doModify(FieldValue fieldVal) {
+ doModifyCalled = true;
+ prevVal = fieldVal.clone();
+ fieldVal.assign(nextVal);
+ return ModificationStatus.MODIFIED;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return doc.equals(o);
+ }
+
+ @Override
+ public String toString() {
+ return doc.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return doc.hashCode();
+ }
+
+ @Override
+ public Document clone() {
+ return doc.clone();
+ }
+
+ @Override
+ public void clear() {
+ doc.clear();
+ }
+
+ @Override
+ public Iterator<Entry<Field, FieldValue>> iterator() {
+ return doc.iterator();
+ }
+
+ @Override
+ public DocumentId getId() {
+ return doc.getId();
+ }
+
+ @Override
+ public void setLastModified(Long lastModified) {
+ doc.setLastModified(lastModified);
+ }
+
+ @Override
+ public Long getLastModified() {
+ return doc.getLastModified();
+ }
+
+ @Override
+ public void setId(DocumentId id) {
+ doc.setId(id);
+ }
+
+ @Override
+ public Struct getHeader() {
+ return doc.getHeader();
+ }
+
+ @Override
+ public Struct getBody() {
+ return doc.getBody();
+ }
+
+ @Override
+ public void assign(Object o) {
+ doc.assign(o);
+ }
+
+ @Override
+ public void setDataType(DataType type) {
+ doc.setDataType(type);
+ }
+
+ @Override
+ public int getSerializedSize() throws SerializationException {
+ return doc.getSerializedSize();
+ }
+
+ @Override
+ public void serialize(OutputStream out) throws SerializationException {
+ doc.serialize(out);
+ }
+
+ @Override
+ protected void doSetFieldValue(Field field, FieldValue value) {
+ super.doSetFieldValue(field, value);
+ }
+
+ @Override
+ public String toXML(String indent) {
+ return doc.toXML(indent);
+ }
+
+ @Override
+ public String toXml() {
+ return doc.toXml();
+ }
+
+ @Override
+ public void printXml(XmlStream xml) {
+ doc.printXml(xml);
+ }
+
+ @Override
+ public void onSerialize(Serializer target) throws SerializationException {
+ doc.onSerialize(target);
+ }
+
+ @Override
+ public void serializeHeader(Serializer target) throws SerializationException {
+ doc.serializeHeader(target);
+ }
+
+ @Override
+ public void serializeBody(Serializer target) throws SerializationException {
+ doc.serializeBody(target);
+ }
+
+ @Override
+ public DocumentType getDataType() {
+ return doc.getDataType();
+ }
+
+ @Override
+ public int getFieldCount() {
+ return doc.getFieldCount();
+ }
+
+ @Override
+ public void serialize(DocumentWriter writer) {
+ doc.serialize(writer);
+ }
+
+ @Override
+ public void deserialize(DocumentReader reader) {
+ doc.deserialize(reader);
+ }
+
+ @Override
+ public void serialize(Field field, FieldWriter writer) {
+ doc.serialize(field, writer);
+ }
+
+ @Override
+ public void deserialize(Field field, FieldReader reader) {
+ doc.deserialize(field, reader);
+ }
+
+ @Override
+ public int compareTo(FieldValue fieldValue) {
+ return super.compareTo(fieldValue);
+ }
+
+ @Override
+ public ModificationStatus iterateNested(FieldPath fieldPath, int pos,
+ FieldPathIteratorHandler handler) {
+ return doc.iterateNested(fieldPath, pos, handler);
+ }
+
+ private static class RemoveHandler extends FieldPathIteratorHandler {
+ private boolean doModifyCalled = false;
+ private FieldValue prevVal;
+
+ @Override
+ public boolean onComplex(FieldValue fieldVal) {
+ return false;
+ }
+
+ @Override
+ public ModificationStatus doModify(FieldValue fieldVal) {
+ doModifyCalled = true;
+ prevVal = fieldVal.clone();
+ return ModificationStatus.REMOVED;
+ }
+ }
+
+ /**
+ * The {@link Document} which this proxies
+ * @return The proxied Document
+ */
+ public Document getDocument() {
+ return doc;
+ }
+
+}