001/* 002 * (C) Copyright 2017-2019 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 * Kevin Leturc <kleturc@nuxeo.com> 018 */ 019package org.nuxeo.ecm.core.io.impl.transformers; 020 021import java.util.List; 022import java.util.Optional; 023 024import org.apache.commons.lang3.StringUtils; 025import org.dom4j.DocumentHelper; 026import org.dom4j.Element; 027import org.dom4j.Namespace; 028import org.dom4j.QName; 029import org.nuxeo.ecm.core.io.DocumentTransformer; 030import org.nuxeo.ecm.core.io.ExportedDocument; 031import org.nuxeo.ecm.core.schema.SchemaManager; 032import org.nuxeo.ecm.core.schema.TypeConstants; 033import org.nuxeo.ecm.core.schema.types.Field; 034import org.nuxeo.runtime.api.Framework; 035 036/** 037 * This is a {@link DocumentTransformer} which removes property marked as removed in deprecation system. 038 * 039 * @since 9.2 040 */ 041public class PropertyDeprecationRemover implements DocumentTransformer { 042 043 protected final SchemaManager schemaManager; 044 045 public PropertyDeprecationRemover() { 046 schemaManager = Framework.getService(SchemaManager.class); 047 } 048 049 @Override 050 public boolean transform(ExportedDocument xdoc) { 051 Element root = xdoc.getDocument().getRootElement(); 052 for (Element schema : root.elements("schema")) { 053 String schemaName = schema.attributeValue("name"); 054 for (String prop : schemaManager.getRemovedProperties(schemaName)) { 055 handleProperty(schema, prop); 056 } 057 } 058 return true; 059 } 060 061 @SuppressWarnings("unchecked") 062 protected void handleProperty(Element schema, String propertyToRemove) { 063 String schemaName = schema.attributeValue("name"); 064 065 // build namespace uri for input schema 066 String namespaceURI = "http://www.nuxeo.org/ecm/schemas/" + schemaName + '/'; 067 String schemaPrefix = Optional.ofNullable(schema.getNamespaceForURI(namespaceURI)) 068 .map(Namespace::getPrefix) 069 .filter(StringUtils::isNotBlank) 070 .map(p -> p + ':') 071 .orElse(StringUtils.EMPTY); 072 073 Optional<String> fallback = schemaManager.getFallback(schemaName, propertyToRemove) 074 .map(f -> correctBlobProperty(schemaPrefix, f)); 075 076 // handle list and other elements 077 int starIndex = propertyToRemove.indexOf('*'); 078 if (starIndex < 0) { 079 // removed property is not in a list 080 Element elementToRemove = (Element) schema.selectSingleNode(schemaPrefix + propertyToRemove); 081 if (elementToRemove != null) { 082 fallback.ifPresent(f -> moveProperty(schema, schema, elementToRemove, f)); 083 // finally detach removed property 084 elementToRemove.detach(); 085 } 086 } else { 087 // removed property is in a list 088 List<Element> elementsToRemove = (List<Element>) (List<?>) schema.selectNodes( 089 schemaPrefix + propertyToRemove); 090 // compute number of times we need to get parent - here we only handle one list level (the last one) 091 // we assume that fallback of a list of complex is inside the same complex property 092 int count = StringUtils.countMatches(propertyToRemove.substring(starIndex), "/"); 093 // compute a new fallback 094 // we want to skip "*/" 095 Optional<String> newFallback = fallback.map(f -> f.substring(f.lastIndexOf("*/") + 2)); 096 for (Element elementToRemove : elementsToRemove) { 097 newFallback.ifPresent(f -> moveProperty(schema, getParent(count, elementToRemove), elementToRemove, f)); 098 // finally detach removed property 099 elementToRemove.detach(); 100 } 101 } 102 } 103 104 private String correctBlobProperty(String schemaPrefix, String path) { 105 // check if the fallback is inside a blob 106 Field fallbackField = schemaManager.getField(schemaPrefix + path); 107 if (fallbackField != null && TypeConstants.isContentType(fallbackField.getDeclaringType())) { 108 // as we export blob with the property "filename" instead of "name", which differ from schema definition, 109 // we need to replace the "name" property name 110 path = path.replace("/name", "/filename"); 111 } 112 return path; 113 } 114 115 protected Element getParent(int count, Element elementToRemove) { 116 Element parent = elementToRemove; 117 for (int i = 0; i < count; i++) { 118 parent = parent.getParent(); 119 } 120 return parent; 121 } 122 123 protected void moveProperty(Element schema, Element parent, Element elementToRemove, String fallback) { 124 // as we don't currently handle fallback to another schema, we can move content easily 125 String[] fallbackSegments = fallback.split("/"); 126 for (String fallbackSegment : fallbackSegments) { 127 QName qName; 128 Element element; 129 if (parent == schema) { 130 // first element has a namespace 131 qName = QName.get(fallbackSegment, schema.getNamespaceForPrefix(schema.attributeValue("name"))); 132 } else { 133 // children don't have namespace 134 qName = QName.get(fallbackSegment); 135 } 136 element = parent.element(qName); 137 // create element if it doesn't exist 138 if (element == null) { 139 element = DocumentHelper.createElement(qName); 140 parent.add(element); 141 } 142 parent = element; 143 } 144 // move content to last element if it doesn't has content - removed properties don't override fallback 145 if (!parent.hasContent()) { 146 parent.setContent(elementToRemove.content()); 147 } 148 } 149 150}