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