001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.xml; 014 015import static org.apache.juneau.internal.CollectionUtils.*; 016import static org.apache.juneau.xml.annotation.XmlFormat.*; 017 018import java.util.*; 019 020import org.apache.juneau.*; 021import org.apache.juneau.xml.annotation.*; 022 023/** 024 * Metadata on beans specific to the XML serializers and parsers pulled from the {@link Xml @Xml} annotation on the 025 * class. 026 */ 027public class XmlBeanMeta extends ExtendedBeanMeta { 028 029 // XML related fields 030 private final Map<String,BeanPropertyMeta> attrs; // Map of bean properties that are represented as XML attributes. 031 private final Map<String,BeanPropertyMeta> elements; // Map of bean properties that are represented as XML elements. 032 private final BeanPropertyMeta attrsProperty; // Bean property that contain XML attribute key/value pairs for this bean. 033 private final Map<String,BeanPropertyMeta> collapsedProperties; // Properties defined with @Xml.childName annotation. 034 private final BeanPropertyMeta contentProperty; 035 private final XmlFormat contentFormat; 036 037 /** 038 * Constructor. 039 * 040 * @param beanMeta The metadata on the bean that this metadata applies to. 041 * @param mp XML metadata provider (for finding information about other artifacts). 042 */ 043 public XmlBeanMeta(BeanMeta<?> beanMeta, XmlMetaProvider mp) { 044 super(beanMeta); 045 046 Class<?> c = beanMeta.getClassMeta().getInnerClass(); 047 XmlBeanMetaBuilder b = new XmlBeanMetaBuilder(beanMeta, mp); 048 049 attrs = unmodifiableMap(b.attrs); 050 elements = unmodifiableMap(b.elements); 051 attrsProperty = b.attrsProperty; 052 collapsedProperties = unmodifiableMap(b.collapsedProperties); 053 contentProperty = b.contentProperty; 054 contentFormat = b.contentFormat; 055 056 // Do some validation. 057 if (contentProperty != null || contentFormat == XmlFormat.VOID) { 058 if (! elements.isEmpty()) 059 throw new BeanRuntimeException(c, "{0} and ELEMENT properties found on the same bean. These cannot be mixed.", contentFormat); 060 if (! collapsedProperties.isEmpty()) 061 throw new BeanRuntimeException(c, "{0} and COLLAPSED properties found on the same bean. These cannot be mixed.", contentFormat); 062 } 063 064 if (! collapsedProperties.isEmpty()) { 065 if (! Collections.disjoint(elements.keySet(), collapsedProperties.keySet())) 066 throw new BeanRuntimeException(c, "Child element name conflicts found with another property."); 067 } 068 } 069 070 private static class XmlBeanMetaBuilder { 071 Map<String,BeanPropertyMeta> 072 attrs = new LinkedHashMap<>(), 073 elements = new LinkedHashMap<>(), 074 collapsedProperties = new LinkedHashMap<>(); 075 BeanPropertyMeta 076 attrsProperty, 077 contentProperty; 078 XmlFormat contentFormat = DEFAULT; 079 080 XmlBeanMetaBuilder(BeanMeta<?> beanMeta, XmlMetaProvider mp) { 081 Class<?> c = beanMeta.getClassMeta().getInnerClass(); 082 Xml xml = mp.getAnnotation(Xml.class, c); 083 XmlFormat defaultFormat = null; 084 085 if (xml != null) { 086 XmlFormat xf = xml.format(); 087 if (xf == ATTRS) 088 defaultFormat = XmlFormat.ATTR; 089 else if (xf.isOneOf(ELEMENTS, DEFAULT)) 090 defaultFormat = ELEMENT; 091 else if (xf == VOID) { 092 contentFormat = VOID; 093 defaultFormat = VOID; 094 } 095 else 096 throw new BeanRuntimeException(c, "Invalid format specified in @Xml annotation on bean: {0}. Must be one of the following: DEFAULT,ATTRS,ELEMENTS,VOID", xml.format()); 097 } 098 099 for (BeanPropertyMeta p : beanMeta.getPropertyMetas()) { 100 XmlFormat xf = mp.getXmlBeanPropertyMeta(p).getXmlFormat(); 101 ClassMeta<?> pcm = p.getClassMeta(); 102 if (xf == ATTR) { 103 attrs.put(p.getName(), p); 104 } else if (xf == ELEMENT) { 105 elements.put(p.getName(), p); 106 } else if (xf == COLLAPSED) { 107 collapsedProperties.put(p.getName(), p); 108 } else if (xf == DEFAULT) { 109 if (defaultFormat == ATTR) 110 attrs.put(p.getName(), p); 111 else 112 elements.put(p.getName(), p); 113 } else if (xf == ATTRS) { 114 if (attrsProperty != null) 115 throw new BeanRuntimeException(c, "Multiple instances of ATTRS properties defined on class. Only one property can be designated as such."); 116 if (! pcm.isMapOrBean()) 117 throw new BeanRuntimeException(c, "Invalid type for ATTRS property. Only properties of type Map and bean can be used."); 118 attrsProperty = p; 119 } else if (xf.isOneOf(ELEMENTS, MIXED, MIXED_PWS, TEXT, TEXT_PWS, XMLTEXT)) { 120 if (xf.isOneOf(ELEMENTS, MIXED, MIXED_PWS) && ! pcm.isCollectionOrArray()) 121 throw new BeanRuntimeException(c, "Invalid type for {0} property. Only properties of type Collection and array can be used.", xf); 122 if (contentProperty != null) { 123 if (xf == contentFormat) 124 throw new BeanRuntimeException(c, "Multiple instances of {0} properties defined on class. Only one property can be designated as such.", xf); 125 throw new BeanRuntimeException(c, "{0} and {1} properties found on the same bean. Only one property can be designated as such.", contentFormat, xf); 126 } 127 contentProperty = p; 128 contentFormat = xf; 129 } 130 // Look for any properties that are collections with @Xml.childName specified. 131 String n = mp.getXmlBeanPropertyMeta(p).getChildName(); 132 if (n != null) { 133 if (collapsedProperties.containsKey(n) && collapsedProperties.get(n) != p) 134 throw new BeanRuntimeException(c, "Multiple properties found with the child name ''{0}''.", n); 135 collapsedProperties.put(n, p); 136 } 137 } 138 } 139 } 140 141 /** 142 * The list of properties that should be rendered as XML attributes. 143 * 144 * @return Map of property names to property metadata. 145 */ 146 public Map<String,BeanPropertyMeta> getAttrProperties() { 147 return attrs; 148 } 149 150 /** 151 * The list of names of properties that should be rendered as XML attributes. 152 * 153 * @return Set of property names. 154 */ 155 protected Set<String> getAttrPropertyNames() { 156 return attrs.keySet(); 157 } 158 159 /** 160 * The list of properties that should be rendered as child elements. 161 * 162 * @return Map of property names to property metadata. 163 */ 164 protected Map<String,BeanPropertyMeta> getElementProperties() { 165 return elements; 166 } 167 168 /** 169 * The list of names of properties that should be rendered as child elements. 170 * 171 * @return Set of property names. 172 */ 173 protected Set<String> getElementPropertyNames() { 174 return elements.keySet(); 175 } 176 177 /** 178 * The list of properties that should be rendered as collapsed child elements. 179 * <br>See {@link Xml#childName() @Xml(childName)} 180 * 181 * @return Map of property names to property metadata. 182 */ 183 protected Map<String,BeanPropertyMeta> getCollapsedProperties() { 184 return collapsedProperties; 185 } 186 187 /** 188 * The list of names of properties that should be rendered as collapsed child elements. 189 * 190 * @return Set of property names. 191 */ 192 protected Set<String> getCollapsedPropertyNames() { 193 return collapsedProperties.keySet(); 194 } 195 196 /** 197 * The property that returns a map of XML attributes as key/value pairs. 198 * 199 * @return The bean property metadata, or <jk>null</jk> if there is no such method. 200 */ 201 protected BeanPropertyMeta getAttrsProperty() { 202 return attrsProperty; 203 } 204 205 /** 206 * The name of the property that returns a map of XML attributes as key/value pairs. 207 * 208 * @return The bean property name, or <jk>null</jk> if there is no such method. 209 */ 210 protected String getAttrsPropertyName() { 211 return attrsProperty == null ? null : attrsProperty.getName(); 212 } 213 214 /** 215 * The property that represents the inner XML content of this bean. 216 * 217 * @return The bean property metadata, or <jk>null</jk> if there is no such method. 218 */ 219 public BeanPropertyMeta getContentProperty() { 220 return contentProperty; 221 } 222 223 /** 224 * The name of the property that represents the inner XML content of this bean. 225 * 226 * @return The bean property name, or <jk>null</jk> if there is no such method. 227 */ 228 protected String getContentPropertyName() { 229 return contentProperty == null ? null : contentProperty.getName(); 230 } 231 232 /** 233 * Returns the format of the inner XML content of this bean. 234 * 235 * <p> 236 * Can be one of the following: 237 * <ul> 238 * <li>{@link XmlFormat#ELEMENTS} 239 * <li>{@link XmlFormat#MIXED} 240 * <li>{@link XmlFormat#MIXED_PWS} 241 * <li>{@link XmlFormat#TEXT} 242 * <li>{@link XmlFormat#TEXT_PWS} 243 * <li>{@link XmlFormat#XMLTEXT} 244 * <li>{@link XmlFormat#VOID} 245 * <li><jk>null</jk> 246 * </ul> 247 * 248 * @return The format of the inner XML content of this bean. 249 */ 250 public XmlFormat getContentFormat() { 251 return contentFormat; 252 } 253 254 /** 255 * Returns bean property meta with the specified name. 256 * 257 * <p> 258 * This is identical to calling {@link BeanMeta#getPropertyMeta(String)} except it first retrieves the bean property 259 * meta based on the child name (e.g. a property whose name is "people", but whose child name is "person"). 260 * 261 * @param fieldName The bean property name. 262 * @return The property metadata. 263 */ 264 protected BeanPropertyMeta getPropertyMeta(String fieldName) { 265 if (collapsedProperties != null) { 266 BeanPropertyMeta bpm = collapsedProperties.get(fieldName); 267 if (bpm == null) 268 bpm = collapsedProperties.get("*"); 269 if (bpm != null) 270 return bpm; 271 } 272 return getBeanMeta().getPropertyMeta(fieldName); 273 } 274}