001/*
002 * (C) Copyright 2018 Nuxeo (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 *     pierre
018 */
019package org.nuxeo.ecm.core.io.avro;
020
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import org.apache.avro.Schema;
027import org.apache.avro.Schema.Field;
028import org.apache.avro.generic.GenericData;
029import org.apache.avro.generic.GenericRecord;
030import org.nuxeo.common.utils.Path;
031import org.nuxeo.ecm.core.api.DocumentModel;
032import org.nuxeo.ecm.core.api.DocumentRef;
033import org.nuxeo.ecm.core.api.IdRef;
034import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
035import org.nuxeo.ecm.core.api.model.Property;
036import org.nuxeo.runtime.RuntimeServiceException;
037import org.nuxeo.runtime.avro.AvroMapper;
038import org.nuxeo.runtime.avro.AvroService;
039
040/**
041 * @since 10.2
042 */
043public class DocumentModelMapper extends AvroMapper<DocumentModel, GenericRecord> {
044
045    public DocumentModelMapper(AvroService service) {
046        super(service);
047    }
048
049    @Override
050    public DocumentModel fromAvro(Schema schema, GenericRecord input) {
051        if (!AvroConstants.DOCUMENT_MODEL.equals(getLogicalType(schema))) {
052            throw new RuntimeServiceException("Schema does not match DocumentModel");
053        }
054        DocumentModel doc = documentModelFromAvro(input);
055        GenericRecord documentTypeRecord = (GenericRecord) input.get(AvroConstants.DOCUMENT_TYPE);
056        for (Field schemaField : documentTypeRecord.getSchema().getFields()) {
057            GenericRecord schemaRecord = (GenericRecord) documentTypeRecord.get(schemaField.name());
058            List<Field> fields = schemaField.schema().getFields();
059            Map<String, Object> data = new HashMap<>(fields.size());
060            for (Field field : fields) {
061                data.put(service.decodeName(field.name()),
062                        service.fromAvro(field.schema(), Property.class, schemaRecord.get(field.name())));
063            }
064            doc.setProperties(service.decodeName(schemaField.name()), data);
065        }
066        return doc;
067    }
068
069    @Override
070    public GenericRecord toAvro(Schema schema, DocumentModel input) {
071        GenericRecord record = new GenericData.Record(schema);
072        if (AvroConstants.DOCUMENT_MODEL.equals(getLogicalType(schema))) {
073            documentModelToAvro(schema, input, record);
074        } else if (AvroConstants.DOCUMENT_TYPE.equals(getLogicalType(schema))) {
075            for (Field field : schema.getFields()) {
076                record.put(field.name(), service.toAvro(field.schema(), input));
077            }
078        } else {
079            for (Field field : schema.getFields()) {
080                Property p = input.getProperty(service.decodeName(field.name()));
081                record.put(field.name(), service.toAvro(field.schema(), p));
082            }
083        }
084        return record;
085    }
086
087    @SuppressWarnings("unchecked")
088    protected DocumentModel documentModelFromAvro(GenericRecord input) {
089        String path = (String) input.get(AvroConstants.PATH);
090        String type = (String) input.get(AvroConstants.PRIMARY_TYPE);
091        String uuid = (String) input.get(AvroConstants.UUID);
092        String parentId = (String) input.get(AvroConstants.PARENT_ID);
093        String repositoryName = (String) input.get(AvroConstants.REPOSITORY_NAME);
094        Boolean isProxy = (Boolean) input.get(AvroConstants.IS_PROXY);
095        DocumentRef parentRef = parentId == null ? null : new IdRef(parentId);
096        Set<String> facets = (Set<String>) input.get(AvroConstants.MIXIN_TYPES);
097        DocumentModelImpl doc = new DocumentModelImpl(null, type, uuid, new Path(path), null, parentRef,
098                null, facets, null, repositoryName, isProxy);
099        doc.setIsVersion((Boolean) input.get(AvroConstants.IS_VERSION));
100        doc.prefetchCurrentLifecycleState((String) input.get(AvroConstants.CURRENT_LIFE_CYCLE_STATE));
101        return doc;
102    }
103
104    protected void documentModelToAvro(Schema schema, DocumentModel doc, GenericRecord record) {
105        record.put(AvroConstants.UUID, doc.getId());
106        record.put(AvroConstants.NAME, doc.getName());
107        record.put(AvroConstants.TITLE, doc.getTitle());
108        record.put(AvroConstants.PATH, doc.getPathAsString());
109        record.put(AvroConstants.REPOSITORY_NAME, doc.getRepositoryName());
110        record.put(AvroConstants.PRIMARY_TYPE, doc.getType());
111        DocumentRef parentRef = doc.getParentRef();
112        if (parentRef != null) {
113            record.put(AvroConstants.PARENT_ID, parentRef.toString());
114        }
115        record.put(AvroConstants.CURRENT_LIFE_CYCLE_STATE, doc.getCurrentLifeCycleState());
116        if (doc.isVersion()) {
117            record.put(AvroConstants.VERSION_LABEL, doc.getVersionLabel());
118            record.put(AvroConstants.VERSION_VERSIONABLE_ID, doc.getVersionSeriesId());
119        }
120        record.put(AvroConstants.IS_PROXY, doc.isProxy());
121        record.put(AvroConstants.IS_TRASHED, doc.isTrashed());
122        record.put(AvroConstants.IS_VERSION, doc.isVersion());
123        record.put(AvroConstants.IS_CHECKEDIN, !doc.isCheckedOut());
124        record.put(AvroConstants.IS_LATEST_VERSION, doc.isLatestVersion());
125        record.put(AvroConstants.IS_LATEST_MAJOR_VERSION, doc.isLatestMajorVersion());
126        record.put(AvroConstants.CHANGE_TOKEN, doc.getChangeToken());
127        if (doc.getPos() != null) {
128            record.put(AvroConstants.POS, doc.getPos());
129        }
130        // facets
131        record.put(AvroConstants.MIXIN_TYPES, doc.getFacets());
132        // document type with schemas
133        record.put(AvroConstants.DOCUMENT_TYPE, service.toAvro(schema.getField(AvroConstants.DOCUMENT_TYPE).schema(), doc));
134        // INFO \\ tags and acls are ignored for now
135    }
136
137}