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