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