001/* 002 * (C) Copyright 2006-2012 Nuxeo SA (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 * Bogdan Stefanescu 018 * Florent Guillaume 019 */ 020package org.nuxeo.ecm.core.schema.types; 021 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.Map; 026import java.util.Map.Entry; 027 028import org.nuxeo.ecm.core.schema.Namespace; 029import org.nuxeo.ecm.core.schema.TypeConstants; 030import org.nuxeo.ecm.core.schema.types.constraints.Constraint; 031 032/** 033 * A Complex Type holds several fields. 034 */ 035public class ComplexTypeImpl extends AbstractType implements ComplexType { 036 037 private static final long serialVersionUID = 1L; 038 039 /** The fields held by this complex type. */ 040 protected final Map<QName, Field> fields = new HashMap<>(); 041 042 /** The map of name or prefixed name to field. */ 043 protected volatile Map<String, Field> fieldsByName = new HashMap<>(); 044 045 protected final Namespace ns; 046 047 public ComplexTypeImpl(ComplexType superType, String schema, String name, Namespace ns) { 048 super(superType, schema, name); 049 // for composite types, they already include schemas from supertypes 050 // if (superType != null) { 051 // for (Field field : superType.getFields()) { 052 // addField(field); 053 // } 054 // } 055 this.ns = ns; 056 } 057 058 public ComplexTypeImpl(ComplexType superType, String schema, String name) { 059 this(superType, schema, name, Namespace.DEFAULT_NS); 060 } 061 062 // also called by CompositeTypeImpl 063 protected void addField(Field field) { 064 QName name = field.getName(); 065 fields.put(name, field); 066 fieldsByName.put(name.getLocalName(), field); 067 fieldsByName.put(name.getPrefixedName(), field); 068 } 069 070 // called by XSDLoader 071 @Override 072 public Field addField(String name, Type type, String defaultValue, int flags, Collection<Constraint> constraints) { 073 QName qname = QName.valueOf(name, ns.prefix); 074 FieldImpl field = new FieldImpl(qname, this, type, defaultValue, flags, constraints); 075 addField(field); 076 return field; 077 } 078 079 @Override 080 public Namespace getNamespace() { 081 return ns; 082 } 083 084 @Override 085 public Field getField(String name) { 086 return fieldsByName.get(name); 087 } 088 089 @Override 090 public Field getField(QName name) { 091 return fields.get(name); 092 } 093 094 @Override 095 public Collection<Field> getFields() { 096 return fields.values(); 097 } 098 099 @Override 100 public int getFieldsCount() { 101 return fields.size(); 102 } 103 104 @Override 105 public boolean hasField(String name) { 106 return fieldsByName.containsKey(name); 107 } 108 109 @Override 110 public boolean hasFields() { 111 return !fields.isEmpty(); 112 } 113 114 @Override 115 public boolean isComplexType() { 116 return true; 117 } 118 119 @SuppressWarnings("unchecked") 120 @Override 121 public boolean validate(Object object) throws TypeException { 122 throw new UnsupportedOperationException("Unimplemented, use DocumentValidationService"); 123 } 124 125 @Override 126 public String toString() { 127 return getClass().getSimpleName() + '(' + name + ')'; 128 } 129 130 @Override 131 public Map<String, Object> newInstance() { 132 if (TypeConstants.isContentType(this)) { 133 // NXP-912: should return null for a blob. Since there is no 134 // pluggable adapter mechanism on types, and since document model 135 // properties consider that every complex property named "content" 136 // should be dealt with a BlobProperty, this is hardcoded here. 137 return null; 138 } 139 Map<String, Object> map = new HashMap<>(); 140 for (Field field : fields.values()) { 141 Object value; 142 Type type = field.getType(); 143 if (type.isComplexType()) { 144 value = type.newInstance(); 145 } else if (type.isListType()) { 146 value = new ArrayList<>(); 147 } else { 148 value = field.getDefaultValue(); 149 } 150 map.put(field.getName().getLocalName(), value); 151 } 152 return map; 153 } 154 155 @Override 156 @SuppressWarnings("unchecked") 157 public Object convert(Object object) throws TypeException { 158 if (object instanceof Map) { 159 Map<Object, Object> map = (Map<Object, Object>) object; 160 for (Entry<Object, Object> entry : map.entrySet()) { 161 String key = entry.getKey().toString(); 162 Field field = getField(key); 163 if (field == null) { 164 throw new IllegalArgumentException("Field " + key + " is not defined for the complex type " 165 + getName()); 166 } 167 entry.setValue(field.getType().convert(entry.getValue())); 168 } 169 return object; 170 } 171 throw new TypeException("Incompatible object: " + object.getClass() + " for type " + this); 172 } 173 174 /** 175 * Canonicalizes a Nuxeo-xpath. 176 * <p> 177 * Replaces {@code a/foo[123]/b} with {@code a/123/b} 178 * <p> 179 * A star can be used instead of the digits as well (for configuration). 180 * 181 * @param xpath the xpath 182 * @return the canonicalized xpath. 183 */ 184 public static String canonicalXPath(String xpath) { 185 while (xpath.length() > 0 && xpath.charAt(0) == '/') { 186 xpath = xpath.substring(1); 187 } 188 if (xpath.indexOf('[') == -1) { 189 return xpath; 190 } else { 191 return xpath.replaceAll("[^/\\[\\]]+\\[(\\d+|\\*)\\]", "$1"); 192 } 193 } 194 195}