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<QName, Field>(); 041 042 /** The map of name or prefixed name to field. */ 043 protected volatile Map<String, Field> fieldsByName = new HashMap<String, Field>(); 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 if (object == null) { 123 return true; 124 } 125 if (object instanceof Map) { 126 return validateMap((Map<Object, Object>) object); 127 } 128 return false; 129 } 130 131 protected boolean validateMap(Map<Object, Object> map) { 132 return true; 133 } 134 135 @Override 136 public String toString() { 137 return getClass().getSimpleName() + '(' + name + ')'; 138 } 139 140 @Override 141 public Map<String, Object> newInstance() { 142 if (TypeConstants.isContentType(this)) { 143 // NXP-912: should return null for a blob. Since there is no 144 // pluggable adapter mechanism on types, and since document model 145 // properties consider that every complex property named "content" 146 // should be dealt with a BlobProperty, this is hardcoded here. 147 return null; 148 } 149 Map<String, Object> map = new HashMap<String, Object>(); 150 for (Field field : fields.values()) { 151 Object value; 152 Type type = field.getType(); 153 if (type.isComplexType()) { 154 value = type.newInstance(); 155 } else if (type.isListType()) { 156 value = new ArrayList<Object>(); 157 } else { 158 value = field.getDefaultValue(); 159 } 160 map.put(field.getName().getLocalName(), value); 161 } 162 return map; 163 } 164 165 @Override 166 @SuppressWarnings("unchecked") 167 public Object convert(Object object) throws TypeException { 168 if (object instanceof Map) { 169 Map<Object, Object> map = (Map<Object, Object>) object; 170 for (Entry<Object, Object> entry : map.entrySet()) { 171 String key = entry.getKey().toString(); 172 Field field = getField(key); 173 if (field == null) { 174 throw new IllegalArgumentException("Field " + key + " is not defined for the complex type " 175 + getName()); 176 } 177 entry.setValue(field.getType().convert(entry.getValue())); 178 } 179 return object; 180 } 181 throw new TypeException("Incompatible object: " + object.getClass() + " for type " + this); 182 } 183 184 /** 185 * Canonicalizes a Nuxeo-xpath. 186 * <p> 187 * Replaces {@code a/foo[123]/b} with {@code a/123/b} 188 * <p> 189 * A star can be used instead of the digits as well (for configuration). 190 * 191 * @param xpath the xpath 192 * @return the canonicalized xpath. 193 */ 194 public static String canonicalXPath(String xpath) { 195 while (xpath.length() > 0 && xpath.charAt(0) == '/') { 196 xpath = xpath.substring(1); 197 } 198 if (xpath.indexOf('[') == -1) { 199 return xpath; 200 } else { 201 return xpath.replaceAll("[^/\\[\\]]+\\[(\\d+|\\*)\\]", "$1"); 202 } 203 } 204 205}