]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java
Support writing file as XML
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / acr / TypesManager.java
1 package org.argeo.cms.acr;
2
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Set;
9 import java.util.SortedMap;
10 import java.util.TreeMap;
11
12 import javax.xml.XMLConstants;
13 import javax.xml.namespace.QName;
14 import javax.xml.parsers.DocumentBuilder;
15 import javax.xml.parsers.DocumentBuilderFactory;
16 import javax.xml.parsers.ParserConfigurationException;
17 import javax.xml.transform.Source;
18 import javax.xml.transform.stream.StreamSource;
19 import javax.xml.validation.Schema;
20 import javax.xml.validation.SchemaFactory;
21 import javax.xml.validation.Validator;
22
23 import org.apache.xerces.impl.xs.XSImplementationImpl;
24 import org.apache.xerces.impl.xs.util.StringListImpl;
25 import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
26 import org.apache.xerces.xs.StringList;
27 import org.apache.xerces.xs.XSAttributeDeclaration;
28 import org.apache.xerces.xs.XSAttributeUse;
29 import org.apache.xerces.xs.XSComplexTypeDefinition;
30 import org.apache.xerces.xs.XSConstants;
31 import org.apache.xerces.xs.XSElementDeclaration;
32 import org.apache.xerces.xs.XSException;
33 import org.apache.xerces.xs.XSImplementation;
34 import org.apache.xerces.xs.XSLoader;
35 import org.apache.xerces.xs.XSModel;
36 import org.apache.xerces.xs.XSModelGroup;
37 import org.apache.xerces.xs.XSNamedMap;
38 import org.apache.xerces.xs.XSObjectList;
39 import org.apache.xerces.xs.XSParticle;
40 import org.apache.xerces.xs.XSSimpleTypeDefinition;
41 import org.apache.xerces.xs.XSTerm;
42 import org.apache.xerces.xs.XSTypeDefinition;
43 import org.argeo.api.acr.CrAttributeType;
44 import org.argeo.api.acr.NamespaceUtils;
45 import org.argeo.api.acr.RuntimeNamespaceContext;
46 import org.argeo.api.cms.CmsLog;
47 import org.xml.sax.ErrorHandler;
48 import org.xml.sax.SAXException;
49 import org.xml.sax.SAXParseException;
50
51 /** Register content types. */
52 class TypesManager {
53 private final static CmsLog log = CmsLog.getLog(TypesManager.class);
54 // private Map<String, String> prefixes = new TreeMap<>();
55
56 // immutable factories
57 private SchemaFactory schemaFactory;
58
59 /** Schema sources. */
60 private List<Source> sources = new ArrayList<>();
61
62 // cached
63 private Schema schema;
64 private DocumentBuilderFactory documentBuilderFactory;
65 private XSModel xsModel;
66 private SortedMap<QName, Map<QName, CrAttributeType>> types;
67
68 private boolean validating = false;
69
70 private final static boolean limited = false;
71
72 public TypesManager() {
73 schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
74
75 // types
76 types = new TreeMap<>(NamespaceUtils.QNAME_COMPARATOR);
77
78 }
79
80 public void init() {
81 for (CmsContentTypes cs : CmsContentTypes.values()) {
82 if (cs.getResource() != null) {
83 StreamSource source = new StreamSource(cs.getResource().toExternalForm());
84 sources.add(source);
85 }
86 RuntimeNamespaceContext.register(cs.getNamespace(), cs.getDefaultPrefix());
87 }
88
89 reload();
90 }
91
92 public void registerTypes(String defaultPrefix, String namespace, String xsdSystemId) {
93 // if (prefixes.containsKey(defaultPrefix))
94 // throw new IllegalStateException(
95 // "Prefix " + defaultPrefix + " is already mapped with " + prefixes.get(defaultPrefix));
96 // prefixes.put(defaultPrefix, namespace);
97 RuntimeNamespaceContext.register(namespace, defaultPrefix);
98
99 if (xsdSystemId != null) {
100 sources.add(new StreamSource(xsdSystemId));
101 reload();
102 log.debug(() -> "Registered types " + namespace + " from " + xsdSystemId);
103 }
104 }
105
106 public Set<QName> listTypes() {
107 return types.keySet();
108 }
109
110 public Map<QName, CrAttributeType> getAttributeTypes(QName type) {
111 if (!types.containsKey(type))
112 throw new IllegalArgumentException("Unkown type");
113 return types.get(type);
114 }
115
116 private synchronized void reload() {
117 try {
118 // schema
119 schema = schemaFactory.newSchema(sources.toArray(new Source[sources.size()]));
120
121 // document builder factory
122 // we force usage of Xerces for predictability
123 documentBuilderFactory = limited ? DocumentBuilderFactory.newInstance() : new DocumentBuilderFactoryImpl();
124 documentBuilderFactory.setNamespaceAware(true);
125 if (!limited) {
126 documentBuilderFactory.setXIncludeAware(true);
127 documentBuilderFactory.setSchema(getSchema());
128 documentBuilderFactory.setValidating(validating);
129 }
130
131 // XS model
132 // TODO use JVM implementation?
133 // DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
134 // XSImplementation implementation = (XSImplementation) registry.getDOMImplementation("XS-Loader");
135 XSImplementation xsImplementation = new XSImplementationImpl();
136 XSLoader xsLoader = xsImplementation.createXSLoader(null);
137 List<String> systemIds = new ArrayList<>();
138 for (Source source : sources) {
139 systemIds.add(source.getSystemId());
140 }
141 StringList sl = new StringListImpl(systemIds.toArray(new String[systemIds.size()]), systemIds.size());
142 xsModel = xsLoader.loadURIList(sl);
143
144 // types
145 // XSNamedMap map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
146 // for (int i = 0; i < map.getLength(); i++) {
147 // XSElementDeclaration eDec = (XSElementDeclaration) map.item(i);
148 // QName type = new QName(eDec.getNamespace(), eDec.getName());
149 // types.add(type);
150 // }
151 collectTypes();
152 } catch (XSException | SAXException e) {
153 throw new IllegalStateException("Cannot reload types", e);
154 }
155 }
156
157 private void collectTypes() {
158 types.clear();
159 // elements
160 XSNamedMap topLevelElements = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
161 for (int i = 0; i < topLevelElements.getLength(); i++) {
162 XSElementDeclaration eDec = (XSElementDeclaration) topLevelElements.item(i);
163 collectElementDeclaration("", eDec);
164 }
165
166 // types
167 XSNamedMap topLevelTypes = xsModel.getComponents(XSConstants.TYPE_DEFINITION);
168 for (int i = 0; i < topLevelTypes.getLength(); i++) {
169 XSTypeDefinition tDef = (XSTypeDefinition) topLevelTypes.item(i);
170 collectType(tDef, null, null);
171 }
172
173 }
174
175 private void collectType(XSTypeDefinition tDef, String namespace, String nameHint) {
176 if (tDef.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
177 XSComplexTypeDefinition ctDef = (XSComplexTypeDefinition) tDef;
178 if (ctDef.getContentType() != XSComplexTypeDefinition.CONTENTTYPE_SIMPLE
179 || ctDef.getAttributeUses().getLength() > 0 || ctDef.getAttributeWildcard() != null) {
180 collectComplexType("", null, ctDef);
181 } else {
182 throw new IllegalArgumentException("Unsupported type " + tDef.getTypeCategory());
183 }
184 }
185 }
186
187 private void collectComplexType(String prefix, QName parent, XSComplexTypeDefinition ctDef) {
188 if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_SIMPLE) {
189
190 // content with attributes and a string value
191
192 XSSimpleTypeDefinition stDef = ctDef.getSimpleType();
193 // QName name = new QName(stDef.getNamespace(), stDef.getName());
194 // log.warn(prefix + "Simple " + ctDef + " - " + attributes);
195 // System.err.println(prefix + "Simple from " + parent + " - " + attributes);
196 //
197 // if (parentAttributes != null) {
198 // for (QName attr : attributes.keySet()) {
199 // if (!parentAttributes.containsKey(attr))
200 // System.err.println(prefix + " - " + attr + " not available in parent");
201 //
202 // }
203 // }
204
205 } else if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_ELEMENT
206 || ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_MIXED) {
207 XSParticle p = ctDef.getParticle();
208
209 collectParticle(prefix, p, false);
210 } else if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_EMPTY) {
211 // Parent only contains attributes
212 // if (parent != null)
213 // System.err.println(prefix + "Empty from " + parent + " - " + attributes);
214 // if (parentAttributes != null) {
215 // for (QName attr : attributes.keySet()) {
216 // if (!parentAttributes.containsKey(attr))
217 // System.err.println(prefix + " - " + attr + " not available in parent");
218 //
219 // }
220 // }
221 // log.debug(prefix + "Empty " + ctDef.getNamespace() + ":" + ctDef.getName() + " - " + attributes);
222 } else {
223 throw new IllegalArgumentException("Unsupported type " + ctDef.getTypeCategory());
224 }
225 }
226
227 private void collectParticle(String prefix, XSParticle particle, boolean multipleFromAbove) {
228 boolean orderable = false;
229
230 XSTerm term = particle.getTerm();
231
232 if (particle.getMaxOccurs() == 0) {
233 return;
234 }
235
236 boolean mandatory = false;
237 if (particle.getMinOccurs() > 0) {
238 mandatory = true;
239 }
240
241 boolean multiple = false;
242 if (particle.getMaxOccurs() > 1 || particle.getMaxOccursUnbounded()) {
243 multiple = true;
244 }
245 if (!multiple && multipleFromAbove)
246 multiple = true;
247
248 if (term.getType() == XSConstants.ELEMENT_DECLARATION) {
249 XSElementDeclaration eDec = (XSElementDeclaration) term;
250
251 collectElementDeclaration(prefix, eDec);
252 // If this particle is a wildcard (an <xs:any> )then it
253 // is converted into a node def.
254 } else if (term.getType() == XSConstants.WILDCARD) {
255 // TODO can be anything
256
257 // If this particle is a model group (one of
258 // <xs:sequence>, <xs:choice> or <xs:all>) then
259 // it subparticles must be processed.
260 } else if (term.getType() == XSConstants.MODEL_GROUP) {
261 XSModelGroup mg = (XSModelGroup) term;
262
263 if (mg.getCompositor() == XSModelGroup.COMPOSITOR_SEQUENCE) {
264 orderable = true;
265 }
266 XSObjectList list = mg.getParticles();
267 for (int i = 0; i < list.getLength(); i++) {
268 XSParticle pp = (XSParticle) list.item(i);
269 collectParticle(prefix + " ", pp, multiple);
270 }
271 }
272 }
273
274 private void collectElementDeclaration(String prefix, XSElementDeclaration eDec) {
275 QName name = new QName(eDec.getNamespace(), eDec.getName());
276 XSTypeDefinition tDef = eDec.getTypeDefinition();
277
278 XSComplexTypeDefinition ctDef = null;
279 Map<QName, CrAttributeType> attributes = new HashMap<>();
280 if (tDef.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) {
281 XSSimpleTypeDefinition stDef = (XSSimpleTypeDefinition) tDef;
282 // System.err.println(prefix + "Simple element " + name);
283 } else if (tDef.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
284 ctDef = (XSComplexTypeDefinition) tDef;
285 if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_SIMPLE
286 && ctDef.getAttributeUses().getLength() == 0 && ctDef.getAttributeWildcard() == null) {
287 XSSimpleTypeDefinition stDef = ctDef.getSimpleType();
288 // System.err.println(prefix + "Simplified element " + name);
289 } else {
290 if (!types.containsKey(name)) {
291 // System.out.println(prefix + "Element " + name);
292
293 XSObjectList list = ctDef.getAttributeUses();
294 for (int i = 0; i < list.getLength(); i++) {
295 XSAttributeUse au = (XSAttributeUse) list.item(i);
296 XSAttributeDeclaration ad = au.getAttrDeclaration();
297 QName attrName = new QName(ad.getNamespace(), ad.getName());
298 // Get the simple type def for this attribute
299 XSSimpleTypeDefinition std = ad.getTypeDefinition();
300 attributes.put(attrName, xsToCrType(std.getBuiltInKind()));
301 // System.out.println(prefix + " - " + attrName + " = " + attributes.get(attrName));
302 }
303 // REGISTER
304 types.put(name, attributes);
305 if (ctDef != null)
306 collectComplexType(prefix + " ", name, ctDef);
307 }
308 }
309 }
310
311 }
312
313 private CrAttributeType xsToCrType(short kind) {
314 CrAttributeType propertyType;
315 switch (kind) {
316 case XSConstants.ANYSIMPLETYPE_DT:
317 case XSConstants.STRING_DT:
318 case XSConstants.ID_DT:
319 case XSConstants.ENTITY_DT:
320 case XSConstants.NOTATION_DT:
321 case XSConstants.NORMALIZEDSTRING_DT:
322 case XSConstants.TOKEN_DT:
323 case XSConstants.LANGUAGE_DT:
324 case XSConstants.NMTOKEN_DT:
325 propertyType = CrAttributeType.STRING;
326 break;
327 case XSConstants.BOOLEAN_DT:
328 propertyType = CrAttributeType.BOOLEAN;
329 break;
330 case XSConstants.DECIMAL_DT:
331 case XSConstants.FLOAT_DT:
332 case XSConstants.DOUBLE_DT:
333 propertyType = CrAttributeType.DOUBLE;
334 break;
335 case XSConstants.DURATION_DT:
336 case XSConstants.DATETIME_DT:
337 case XSConstants.TIME_DT:
338 case XSConstants.DATE_DT:
339 case XSConstants.GYEARMONTH_DT:
340 case XSConstants.GYEAR_DT:
341 case XSConstants.GMONTHDAY_DT:
342 case XSConstants.GDAY_DT:
343 case XSConstants.GMONTH_DT:
344 propertyType = CrAttributeType.DATE_TIME;
345 break;
346 case XSConstants.HEXBINARY_DT:
347 case XSConstants.BASE64BINARY_DT:
348 case XSConstants.ANYURI_DT:
349 propertyType = CrAttributeType.ANY_URI;
350 break;
351 case XSConstants.QNAME_DT:
352 case XSConstants.NAME_DT:
353 case XSConstants.NCNAME_DT:
354 // TODO support QName?
355 propertyType = CrAttributeType.STRING;
356 break;
357 case XSConstants.IDREF_DT:
358 // TODO support references?
359 propertyType = CrAttributeType.STRING;
360 break;
361 case XSConstants.INTEGER_DT:
362 case XSConstants.NONPOSITIVEINTEGER_DT:
363 case XSConstants.NEGATIVEINTEGER_DT:
364 case XSConstants.LONG_DT:
365 case XSConstants.INT_DT:
366 case XSConstants.SHORT_DT:
367 case XSConstants.BYTE_DT:
368 case XSConstants.NONNEGATIVEINTEGER_DT:
369 case XSConstants.UNSIGNEDLONG_DT:
370 case XSConstants.UNSIGNEDINT_DT:
371 case XSConstants.UNSIGNEDSHORT_DT:
372 case XSConstants.UNSIGNEDBYTE_DT:
373 case XSConstants.POSITIVEINTEGER_DT:
374 propertyType = CrAttributeType.LONG;
375 break;
376 case XSConstants.LISTOFUNION_DT:
377 case XSConstants.LIST_DT:
378 case XSConstants.UNAVAILABLE_DT:
379 propertyType = CrAttributeType.STRING;
380 break;
381 default:
382 propertyType = CrAttributeType.STRING;
383 break;
384 }
385 return propertyType;
386 }
387
388 public DocumentBuilder newDocumentBuilder() {
389 try {
390 DocumentBuilder dBuilder = documentBuilderFactory.newDocumentBuilder();
391 dBuilder.setErrorHandler(new ErrorHandler() {
392
393 @Override
394 public void warning(SAXParseException exception) throws SAXException {
395 log.warn(exception);
396 }
397
398 @Override
399 public void fatalError(SAXParseException exception) throws SAXException {
400 log.error(exception);
401 }
402
403 @Override
404 public void error(SAXParseException exception) throws SAXException {
405 log.error(exception);
406 }
407 });
408 return dBuilder;
409 } catch (ParserConfigurationException e) {
410 throw new IllegalStateException("Cannot create document builder", e);
411 }
412 }
413
414 public void printTypes() {
415 try {
416
417 // Convert top level complex type definitions to node types
418 log.debug("\n## TYPES");
419 XSNamedMap map = xsModel.getComponents(XSConstants.TYPE_DEFINITION);
420 for (int i = 0; i < map.getLength(); i++) {
421 XSTypeDefinition tDef = (XSTypeDefinition) map.item(i);
422 log.debug(tDef);
423 }
424 // Convert local (anonymous) complex type defs found in top level
425 // element declarations
426 log.debug("\n## ELEMENTS");
427 map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
428 for (int i = 0; i < map.getLength(); i++) {
429 XSElementDeclaration eDec = (XSElementDeclaration) map.item(i);
430 XSTypeDefinition tDef = eDec.getTypeDefinition();
431 log.debug(eDec + ", " + tDef);
432 }
433 log.debug("\n## ATTRIBUTES");
434 map = xsModel.getComponents(XSConstants.ATTRIBUTE_DECLARATION);
435 for (int i = 0; i < map.getLength(); i++) {
436 XSAttributeDeclaration eDec = (XSAttributeDeclaration) map.item(i);
437 XSTypeDefinition tDef = eDec.getTypeDefinition();
438 log.debug(eDec.getNamespace() + ":" + eDec.getName() + ", " + tDef);
439 }
440 } catch (ClassCastException | XSException e) {
441 throw new RuntimeException(e);
442 }
443
444 }
445
446 public void validate(Source source) throws IOException {
447 if (!validating)
448 return;
449 Validator validator;
450 synchronized (this) {
451 validator = schema.newValidator();
452 }
453 try {
454 validator.validate(source);
455 } catch (SAXException e) {
456 log.error(source + " is not valid ", e);
457 // throw new IllegalArgumentException("Provided source is not valid", e);
458 }
459 }
460
461 // public Map<String, String> getPrefixes() {
462 // return prefixes;
463 // }
464
465 public List<Source> getSources() {
466 return sources;
467 }
468
469 public Schema getSchema() {
470 return schema;
471 }
472
473 }