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