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