001 package com.oxygenxml.validate.nvdl;
002
003 import java.util.Enumeration;
004 import java.util.Hashtable;
005 import java.util.Stack;
006 import java.util.Vector;
007
008 import org.apache.log4j.Logger;
009 import org.xml.sax.Attributes;
010 import org.xml.sax.ContentHandler;
011 import org.xml.sax.DTDHandler;
012 import org.xml.sax.ErrorHandler;
013 import org.xml.sax.Locator;
014 import org.xml.sax.SAXException;
015 import org.xml.sax.SAXParseException;
016 import org.xml.sax.helpers.AttributesImpl;
017 import org.xml.sax.helpers.DefaultHandler;
018
019 import com.thaiopensource.util.Localizer;
020 import com.thaiopensource.util.PropertyMap;
021 import com.thaiopensource.validate.Schema;
022 import com.thaiopensource.validate.ValidateProperty;
023 import com.thaiopensource.validate.Validator;
024
025 /**
026 * Implementation of a validator of XML documents against NVDL scripts.
027 */
028 class ValidatorImpl extends DefaultHandler implements Validator {
029 /**
030 * Logger
031 */
032 protected static Logger logger = Logger.getLogger(ValidatorImpl.class);
033
034 /**
035 * The namespace for the virtual element that we use for attribute section validation.
036 * We the http://purl.oclc.org/dsdl/nvdl/ns/instance/1.0 namespace.
037 */
038 private static final String VIRTUAL_ELEMENT_URI = "http://purl.oclc.org/dsdl/nvdl/ns/instance/1.0";
039 /**
040 * The name for the dummy element that we use for attribute section validation.
041 * We use "virtualElement"
042 */
043 private static final String VIRTUAL_ELEMENT_LOCAL_NAME = "virtualElement";
044
045 /**
046 * A value for really no namespace, that is different than any other value
047 * for any possible namespace including no namespace which is an empty string.
048 */
049 private static final String NO_NS = "\0";
050
051 /**
052 * The error handler.
053 */
054 private final ErrorHandler eh;
055
056 /**
057 * Properties.
058 */
059 private final PropertyMap properties;
060
061 /**
062 * Triggers.
063 * Specifies elements that start a new section.
064 */
065 private final Triggers triggers;
066
067 /**
068 * Source locator.
069 */
070 private Locator locator;
071
072 /**
073 * Points to the current section.
074 */
075 private Section currentSection;
076
077 /**
078 * The current namespace context, points to the last prefix mapping
079 * the previous can be found on getParent and so on.
080 */
081 private PrefixMapping prefixMapping = null;
082
083 /**
084 * A hashtable that keeps a stack of validators for schemas.
085 */
086 private final Hashtable validatorHandlerCache = new Hashtable();
087
088 /**
089 * Message localizer to report error messages from keys.
090 */
091 private final Localizer localizer = new Localizer(ValidatorImpl.class);
092
093 /**
094 * keeps the no result actions for a section to avoid duplicating them
095 * as the same action can be specified by multiple programs in a section.
096 */
097 private final Hashset noResultActions = new Hashset();
098
099 /**
100 * Stores index sets for attributed for each namespace.
101 */
102 private final Hashtable attributeNamespaceIndexSets = new Hashtable();
103
104 /**
105 * Sores the index sets for attributes for each active handler.
106 * The index set specifies what attributes should be given to what handlers.
107 */
108 private final Vector activeHandlersAttributeIndexSets = new Vector();
109
110 /**
111 * Attribute schemas for a namespace.
112 * It is used to avoid validating twice the set of attributes
113 * from a namespace with the same schema.
114 */
115 private final Hashset attributeSchemas = new Hashset();
116
117 /**
118 * Flag indicating if we had a reject action on attributes from this namespace.
119 * Useful to avoid reporting the same error multiple times.
120 */
121 private boolean attributeNamespaceRejected;
122
123 /**
124 * Ee use this to compute
125 * only once the filtered attributes for a namespace,
126 * laysily when we will need them for the first time.
127 */
128 private Attributes filteredAttributes;
129
130 /**
131 * The start mode for this NVDL script.
132 */
133 private final Mode startMode;
134
135 /**
136 * Stores the element full names. Used for triggers.
137 */
138 private final Stack elementsLocalNameStack;
139
140 /**
141 * Namespace context. Alinked list of proxy namespace
142 * mapping linking to parent.
143 */
144 static private class PrefixMapping {
145 /**
146 * Prefix.
147 */
148 final String prefix;
149 /**
150 * Namespace uri.
151 */
152 final String uri;
153 /**
154 * Link to parent mapping.
155 */
156 final PrefixMapping parent;
157
158 /**
159 * Constructor.
160 * @param prefix The prefix.
161 * @param uri The namespace.
162 * @param parent Parent mapping.
163 */
164 PrefixMapping(String prefix, String uri, PrefixMapping parent) {
165 this.prefix = prefix;
166 this.uri = uri;
167 this.parent = parent;
168 }
169 }
170
171 /**
172 * Store section information.
173 */
174 private class Section implements SectionState {
175 /**
176 * The parent section.
177 */
178 final Section parent;
179 /**
180 * Namespace of this section. Empty string for absent.
181 */
182 final String ns;
183 /**
184 * Number of open elements in this section.
185 */
186 int depth = 0;
187 /**
188 * List of the Validators rooted in this section
189 */
190 final Vector validators = new Vector();
191 final Vector schemas = new Vector();
192 /**
193 * List of the ContentHandlers that want to see the elements in this section
194 */
195 final Vector activeHandlers = new Vector();
196 final Vector activeHandlersAttributeModeUsage = new Vector();
197 final Vector attributeValidationModeUsages = new Vector();
198 /**
199 * List of Programs saying what to do with child sections
200 */
201 final Vector childPrograms = new Vector();
202
203 /**
204 * Keep the context stack if we have a context dependent section.
205 */
206 final Stack context = new Stack();
207 /**
208 * Flag indicating is this section depends on context or not.
209 */
210 boolean contextDependent = false;
211
212 /**
213 * Max attribute processing value from all modes
214 * in this section.
215 */
216 int attributeProcessing = Mode.ATTRIBUTE_PROCESSING_NONE;
217
218 /**
219 * Stores the attach placeholder handlers.
220 */
221 final Vector placeholderHandlers = new Vector();
222 /**
223 * Stores the attach place holder mode usages.
224 */
225 final Vector placeholderModeUsages = new Vector();
226
227 /**
228 * Creates a section for a given namespace and links to to its parent section.
229 *
230 * @param ns The section namespace.
231 * @param parent The parent section.
232 */
233 Section(String ns, Section parent) {
234 this.ns = ns;
235 this.parent = parent;
236 }
237
238 /**
239 * @param modeUsage The mode usage that determines the next mode.
240 * @param handler The content handler that receives notifications.
241 */
242 public void addChildMode(ModeUsage modeUsage, ContentHandler handler) {
243 if (logger.isDebugEnabled()) {
244 logger.debug("Add child mode: " + modeUsage.currentMode.getName() + ":" + handler);
245 }
246 childPrograms.addElement(new Program(modeUsage, handler));
247 if (modeUsage.isContextDependent())
248 contextDependent = true;
249 }
250
251 /**
252 * Adds a validator.
253 * @param schema The schema to validate against.
254 * @param modeUsage The mode usage for this validate action.
255 */
256 public void addValidator(Schema schema, ModeUsage modeUsage) {
257 if (logger.isDebugEnabled()) {
258 logger.debug("Add validator: " + modeUsage.currentMode.getName() + ":" + schema);
259 }
260 // adds the schema to this section schemas
261 schemas.addElement(schema);
262 // creates the validator
263 Validator validator = createValidator(schema);
264 // adds the validator to this section validators
265 validators.addElement(validator);
266 ContentHandler handler = validator.getContentHandler();
267 if (logger.isDebugEnabled()) {
268 logger.debug("Add active handler: " + modeUsage.currentMode.getName() + ":" + handler);
269 }
270 // add the validator handler to the list of active handlers
271 activeHandlers.addElement(validator.getContentHandler());
272 // add the mode usage to the active handlers attribute mode usage list
273 activeHandlersAttributeModeUsage.addElement(modeUsage);
274 // compute the attribute processing
275 attributeProcessing = Math.max(attributeProcessing,
276 modeUsage.getAttributeProcessing());
277 if (logger.isDebugEnabled()) {
278 logger.debug("Add child mode: " + modeUsage.currentMode.getName() + ":" + handler);
279 }
280 // add a child mode with this mode usage and the validator content handler
281 childPrograms.addElement(new Program(modeUsage, validator.getContentHandler()));
282 if (modeUsage.isContextDependent())
283 contextDependent = true;
284 }
285
286 /**
287 * Adds a handler for a mode usage.
288 * @param handler The content handler to be added.
289 * @param attributeModeUsage The mode usage.
290 */
291 public void addActiveHandler(ContentHandler handler, ModeUsage attributeModeUsage) {
292 if (logger.isDebugEnabled()) {
293 logger.debug("Add active handler: " + attributeModeUsage.currentMode.getName() + ":" + handler);
294 }
295 activeHandlers.addElement(handler);
296 activeHandlersAttributeModeUsage.addElement(attributeModeUsage);
297 attributeProcessing = Math.max(attributeProcessing,
298 attributeModeUsage.getAttributeProcessing());
299 if (attributeModeUsage.isContextDependent())
300 contextDependent = true;
301 }
302
303 /**
304 * Adds a mode usage to the attributeValidationModeUsages list
305 * if we process attributes.
306 */
307 public void addAttributeValidationModeUsage(ModeUsage modeUsage) {
308 int ap = modeUsage.getAttributeProcessing();
309 if (ap != Mode.ATTRIBUTE_PROCESSING_NONE) {
310 if (logger.isDebugEnabled()) {
311 logger.debug("Add attribute validation mode usage: " + modeUsage.currentMode.getName());
312 }
313 attributeValidationModeUsages.addElement(modeUsage);
314 attributeProcessing = Math.max(ap, attributeProcessing);
315 if (modeUsage.isContextDependent())
316 contextDependent = true;
317 }
318 }
319
320 /**
321 * Reject content, report an error.
322 */
323 public void reject() throws SAXException {
324 if (logger.isDebugEnabled()) {
325 logger.debug("Reject called!");
326 }
327 if (eh != null)
328 eh.error(new SAXParseException(localizer.message("reject_element", ns),
329 locator));
330 }
331
332 public void attachPlaceholder(ModeUsage modeUsage, ContentHandler handler) {
333 placeholderHandlers.add(handler);
334 placeholderModeUsages.add(modeUsage);
335 }
336
337
338
339 }
340
341 /**
342 * A program is a pair of mode usage and handler.
343 *
344 */
345 static private class Program {
346 /**
347 * The mode usage associated with the handler.
348 */
349 final ModeUsage modeUsage;
350
351 /**
352 * The handler associated with the mode usage.
353 */
354 final ContentHandler handler;
355
356 /**
357 * Creates an association between a mode usage and a handler.
358 * @param modeUsage The mode usage.
359 * @param handler The handler.
360 */
361 Program(ModeUsage modeUsage, ContentHandler handler) {
362 this.modeUsage = modeUsage;
363 this.handler = handler;
364 }
365 }
366
367 /**
368 * Creates a NVDL validator. The initial mode is specified by the mode parameter.
369 * Initializes the current section.
370 * @param mode The start mode.
371 * @param properties Validation properties.
372 */
373 ValidatorImpl(Mode mode, Triggers triggers, PropertyMap properties) {
374 if (logger.isDebugEnabled()) {
375 logger.debug("Creating NVDL validator...");
376 }
377 this.properties = properties;
378 this.triggers = triggers;
379 this.eh = ValidateProperty.ERROR_HANDLER.get(properties);
380 this.startMode = mode;
381 this.elementsLocalNameStack = new Stack();
382 initCurrentSection();
383 }
384
385 /**
386 * Initializes the current session.
387 * Creates a section for a dummy namespace (differnet of "", that is no namespace).
388 * Adds as child mode usage for this a mode usage with start mode as current mode
389 * and that uses start mode. No content handler is set on addChildMode.
390 *
391 */
392 private void initCurrentSection() {
393 currentSection = new Section(NO_NS, null);
394 currentSection.addChildMode(new ModeUsage(startMode, startMode), null);
395 }
396
397 /**
398 * Set document locator callback.
399 * @param locator The document locator.
400 */
401 public void setDocumentLocator(Locator locator) {
402 this.locator = locator;
403 }
404
405 /**
406 * characters callback.
407 * Dispatch it to all active handlers from the current section.
408 */
409 public void characters(char ch[], int start, int length)
410 throws SAXException {
411 for (int i = 0, len = currentSection.activeHandlers.size(); i < len; i++)
412 ((ContentHandler)(currentSection.activeHandlers.elementAt(i))).characters(ch, start, length);
413
414 }
415
416 /**
417 * ignorable whitespace callback.
418 * Dispatch it to all active handlers from the current section.
419 */
420 public void ignorableWhitespace(char ch[], int start, int length)
421 throws SAXException {
422 for (int i = 0, len = currentSection.activeHandlers.size(); i < len; i++)
423 ((ContentHandler)(currentSection.activeHandlers.elementAt(i))).ignorableWhitespace(ch, start, length);
424 }
425
426 /**
427 * startElement callback.
428 *
429 * @param uri The element namespace.
430 * @param localName The element local name.
431 * @param qName The element qualified name.
432 * @param attributes The attributes for this element.
433 */
434 public void startElement(String uri, String localName,
435 String qName, Attributes attributes)
436 throws SAXException {
437
438 if (logger.isDebugEnabled()) {
439 logger.debug("Start element: " + qName);
440 }
441 // if we have a different namespace than the current section namespace
442 // then we start a new section on the new namespace.
443 if (!uri.equals(currentSection.ns)) {
444 startSection(uri);
445 } else
446 if (triggers.trigger(uri, localName, String.valueOf(elementsLocalNameStack.peek()))) {
447 startSection(uri);
448 }
449 elementsLocalNameStack.push(localName);
450 // increase the depth in the current section as we have a new element
451 currentSection.depth++;
452 if (logger.isDebugEnabled()) {
453 logger.debug("Section depth: " + currentSection.depth);
454 }
455 // if the current section contains context dependent mode usages then
456 // we record the local elements in a stack as they form the current path
457 // that determines the context
458 if (currentSection.contextDependent) {
459 currentSection.context.push(localName);
460 }
461 // check if we need to filter attributes or not
462 // and process attributes, eventually validating attribute sections
463 boolean transformAttributes = processAttributes(attributes);
464 if (logger.isDebugEnabled()) {
465 logger.debug("transformAttributes: " + transformAttributes);
466 }
467 // iterate the active session handlers and call start element on them
468 if (logger.isDebugEnabled()) {
469 logger.debug("Notify section active handlers: " + currentSection.activeHandlers.size());
470 }
471 for (int i = 0, len = currentSection.activeHandlers.size(); i < len; i++) {
472 ContentHandler handler = (ContentHandler)(currentSection.activeHandlers.elementAt(i));
473 if (logger.isDebugEnabled()) {
474 logger.debug("Call start element on handler: " + handler);
475 }
476 handler.startElement(uri, localName, qName,
477 transformAttributes
478 // if we need to filter attributes keep the ones the handler is interested in.
479 ? filterAttributes((IntSet)activeHandlersAttributeIndexSets.elementAt(i),
480 attributes)
481 // otherwise just pass all the attributes
482 : attributes);
483 }
484 if (currentSection.depth==1 && currentSection.placeholderHandlers.size()>0) {
485 AttributesImpl atts = new AttributesImpl();
486 atts.addAttribute("", "ns", "ns", "", uri);
487 atts.addAttribute("", "localName", "localName", "", localName);
488 for (int i = 0, len = currentSection.placeholderHandlers.size(); i < len; i++) {
489 ContentHandler handler = (ContentHandler)(currentSection.placeholderHandlers.elementAt(i));
490 if (logger.isDebugEnabled()) {
491 logger.debug("Call start placeholder element on handler: " + handler);
492 }
493 handler.startPrefixMapping("", "http://purl.oclc.org/dsdl/nvdl/ns/instance/1.0");
494 handler.startElement("http://purl.oclc.org/dsdl/nvdl/ns/instance/1.0", "placeholder", "placeholder", atts);
495 }
496 }
497 }
498
499 /**
500 * Get the filtered attributes.
501 * It checks if we want all the attributes and in that case returns the initial attributes,
502 * otherwise creates a FilteredAttributes instance based on the index set and on the attributes.
503 * @param indexSet The set with the indexes of the attributes we want to keep.
504 * @param attributes The list of attributes
505 * @return the attributes containing only those whose indexes are in the indexSet.
506 */
507 private static Attributes filterAttributes(IntSet indexSet, Attributes attributes) {
508 if (indexSet.size() == attributes.getLength())
509 return attributes;
510 return new FilteredAttributes(indexSet, attributes);
511 }
512
513 /**
514 * Processes the element attributes.
515 *
516 * @param attributes The element attributes
517 * @return true if we need to filter attributes when we pass them to the
518 * active handlers, false if we can just pass the initial attributes
519 * to all the active content handlers
520 * @throws SAXException
521 */
522 private boolean processAttributes(Attributes attributes) throws SAXException {
523 // if no match on attributes or attributes -> no need to filter them.
524 if (currentSection.attributeProcessing == Mode.ATTRIBUTE_PROCESSING_NONE
525 || attributes.getLength() == 0)
526 return false;
527
528 // clear the attributeNamespaceIndexSets hashtable.
529 attributeNamespaceIndexSets.clear();
530 // creates index sets based on namespace for the attributes
531 // and places them in the attributeNamespaceIndexSets hashtable
532 for (int i = 0, len = attributes.getLength(); i < len; i++) {
533 String ns = attributes.getURI(i);
534 IntSet indexSet = (IntSet)attributeNamespaceIndexSets.get(ns);
535 if (indexSet == null) {
536 indexSet = new IntSet();
537 attributeNamespaceIndexSets.put(ns, indexSet);
538 }
539 indexSet.add(i);
540 if (logger.isDebugEnabled()) {
541 logger.debug("Added index " + i + " for attribute " + attributes.getQName(i) +
542 " for namespace " + ns);
543 }
544 }
545 // if we need to process only qualified attributes and we have attributes
546 // only in no namespace then return false, no need to filter the attributes
547 if (currentSection.attributeProcessing == Mode.ATTRIBUTE_PROCESSING_QUALIFIED
548 && attributeNamespaceIndexSets.size() == 1
549 && attributeNamespaceIndexSets.get("") != null) {
550 return false;
551 }
552 // Computes the index sets for each handler
553 // get the attribute modes for handlers
554 Vector handlerModes = currentSection.activeHandlersAttributeModeUsage;
555 if (logger.isDebugEnabled()) {
556 logger.debug("Active handlers attribute mode usage: " + handlerModes);
557 }
558 // resize the index set list to the number of handlers
559 activeHandlersAttributeIndexSets.setSize(handlerModes.size());
560 // creates empty index sets for all handlers - initialization
561 for (int i = 0, len = handlerModes.size(); i < len; i++) {
562 activeHandlersAttributeIndexSets.setElementAt(new IntSet(), i);
563 }
564 // we hope we will not need attribute filtering, so we start with transform false.
565 boolean transform = false;
566 // get the list of attribute validation mode usages
567 Vector validationModes = currentSection.attributeValidationModeUsages;
568 // iterate on all attribute namespaces
569 for (Enumeration e = attributeNamespaceIndexSets.keys(); e.hasMoreElements();) {
570 String ns = (String)e.nextElement();
571 if (logger.isDebugEnabled()) {
572 logger.debug("Enumerate attribute namespaces: [" + ns + "]");
573 }
574 // get the index set that represent the attributes in the ns namespace
575 IntSet indexSet = (IntSet)attributeNamespaceIndexSets.get(ns);
576 // clear attribute schemas for this namespace
577 // it is used to avoid validating twice the set of attributes
578 // from this namespace with the same schema.
579 attributeSchemas.clear();
580 // set the filetered attributes to null - we use this to compute
581 // only one the filtered attributes for this namespace, laysily when we
582 // will need them for the first time.
583 filteredAttributes = null;
584 // flag indicating if we had a reject action on attributes from this namespace
585 // we initialize it here in the iteration on attribute namespaces
586 attributeNamespaceRejected = false;
587 // iterates all the handler modes and compute the index sets for all handlers
588 for (int i = 0, len = handlerModes.size(); i < len; i++) {
589 ModeUsage modeUsage = (ModeUsage)handlerModes.elementAt(i);
590 // get the attribute actions for this mode usage, ns namespace
591 // and for the attributes in this namespace
592 AttributeActionSet actions = processAttributeSection(modeUsage, ns, indexSet, attributes);
593 // if we need to attach the attributes we mark that they should be passed
594 // to the handler by adding them to the index set for the handler
595 if (actions.getAttach()) {
596 if (logger.isDebugEnabled()) {
597 logger.debug("Got an attach action for mode usage " + modeUsage);
598 }
599 ((IntSet)activeHandlersAttributeIndexSets.get(i)).addAll(indexSet);
600 } else {
601 if (logger.isDebugEnabled()) {
602 logger.debug("No attach! for ns " + ns + " and mode usage " + modeUsage);
603 }
604 // if that attributes are not attached then we set the transform flag to
605 // true as that means we need to filter out these attributes for the current handler
606 transform = true;
607 }
608 }
609 // iterate the attribute validation mode usages
610 // and process the attribute section with the attributes
611 // from the current namespace
612 for (int i = 0, len = validationModes.size(); i < len; i++) {
613 ModeUsage modeUsage = (ModeUsage)validationModes.elementAt(i);
614 // validation means no result actions, so we are not
615 // interested in the attribute action set returned by
616 // the processAttributeSection method
617 processAttributeSection(modeUsage, ns, indexSet, attributes);
618 }
619 }
620 return transform;
621 }
622
623 /**
624 * Process an attributes section in a specific mode usage.
625 * @param modeUsage The mode usage
626 * @param ns The attribute section namespace
627 * @param indexSet The indexes of the attributes in the given namespace
628 * @param attributes All the attributes
629 * @return The set of attribute actions
630 * @throws SAXException
631 */
632 private AttributeActionSet processAttributeSection(ModeUsage modeUsage,
633 String ns,
634 IntSet indexSet,
635 Attributes attributes)
636 throws SAXException {
637
638 if (logger.isDebugEnabled()) {
639 logger.debug("Start processAttributeSection for ns " + ns);
640 }
641
642 // get the next mode from the mode usage depending on context
643 Mode mode = modeUsage.getMode(currentSection.context);
644 if (logger.isDebugEnabled()) {
645 logger.debug("Mode is " + mode.getName());
646 }
647 // get the attribute action set
648 AttributeActionSet actions = mode.getAttributeActions(ns);
649 if (logger.isDebugEnabled()) {
650 logger.debug("Got schemas in actions: " + actions.getSchemas().length);
651 }
652 // Check if we have a reject action and if we did not reported already
653 // the reject attribute error for this namespace
654 if (actions.getReject() && !attributeNamespaceRejected) {
655 // set the flag to avoid reporting this error again for the same namespace
656 attributeNamespaceRejected = true;
657 if (eh != null)
658 eh.error(new SAXParseException(localizer.message("reject_attribute", ns),
659 locator));
660 }
661 // get the eventual schemas and validate the attributes against them
662 Schema[] schemas = actions.getSchemas();
663 for (int j = 0; j < schemas.length; j++) {
664 // if we already validated against this schema, skip it
665 if (attributeSchemas.contains(schemas[j]))
666 continue;
667 if (logger.isDebugEnabled()) {
668 logger.debug("Adding attribute schema: " + schemas[j]);
669 }
670 // add the schema so that we will not validate again the same attributes against it
671 attributeSchemas.add(schemas[j]);
672 // if we do not computed the filtered attributes for this namespace, compute them
673 if (filteredAttributes == null)
674 filteredAttributes = filterAttributes(indexSet, attributes);
675 // validate the filtered attributes with the schema
676 validateAttributes(schemas[j], filteredAttributes);
677 }
678 // return the actions in case they are needed further.
679 if (logger.isDebugEnabled()) {
680 logger.debug("End processAttributeSection returning " + actions);
681 }
682 return actions;
683 }
684
685 /**
686 * Validates a set of attributes with an attribute schema.
687 * @param schema The attributes schema.
688 * @param attributes The attributes to be validated
689 * @throws SAXException
690 */
691 private void validateAttributes(Schema schema, Attributes attributes) throws SAXException {
692 if (logger.isDebugEnabled()) {
693 logger.debug("Start validateAttributes with schema: " + schema);
694 }
695 // creates a validator for this attributes schema.
696 Validator validator = createValidator(schema);
697 // get its content handler
698 ContentHandler ch = validator.getContentHandler();
699 // initializes the handler with locator and proxy namespace mapping.
700 initHandler(ch);
701 // notifies a dummy element <#bearer> from no namespace
702 ch.startElement(VIRTUAL_ELEMENT_URI, VIRTUAL_ELEMENT_LOCAL_NAME, VIRTUAL_ELEMENT_LOCAL_NAME, attributes);
703 ch.endElement(VIRTUAL_ELEMENT_URI, VIRTUAL_ELEMENT_LOCAL_NAME, VIRTUAL_ELEMENT_LOCAL_NAME);
704 // removes namespaces and signals end document to the handler
705 cleanupHandler(ch);
706 // release the validator so further validate actions with this schema can reuse it
707 releaseValidator(schema, validator);
708 if (logger.isDebugEnabled()) {
709 logger.debug("End validateAttributes");
710 }
711 }
712
713 /**
714 * Start a new section on a given namespace.
715 * Called from startElement when we encounter an element
716 * whose namepsace does not match the current section namespace
717 * or if we get an element declared as a new section trigger in the
718 * NVDL script.
719 * @param uri The new namespace.
720 * @throws SAXException
721 */
722 private void startSection(String uri) throws SAXException {
723 if (logger.isDebugEnabled()) {
724 logger.debug("Start a new section for uri: " + uri + " after " + currentSection.ns);
725 }
726 // creates a new section having the current section as parent section
727 Section section = new Section(uri, currentSection);
728 // get the programs of the current section
729 Vector childPrograms = currentSection.childPrograms;
730 // clear the current no result (validation) actions
731 noResultActions.clear();
732 // iterates current section programs
733 if (logger.isDebugEnabled()) {
734 logger.debug("Iterate programs " + childPrograms.size());
735 }
736 for (int i = 0, len = childPrograms.size(); i < len; i++) {
737 Program program = (Program)childPrograms.elementAt(i);
738 if (logger.isDebugEnabled()) {
739 logger.debug("Program " + program.modeUsage.currentMode.getName() + "->" + program.modeUsage.getMode(currentSection.context).getName() + ":" + program.handler);
740 }
741 // get the mode usage for the program
742 // and determine the use mode from the mode usage based on the current section context
743 // and then get the element actions from that determined mode
744 // that apply to the new namespace
745 ActionSet actions = program.modeUsage.getMode(currentSection.context).getElementActions(uri);
746 if (logger.isDebugEnabled()) {
747 logger.debug("Result action: " + actions.getResultAction());
748 }
749 // check if we have a result action attach/unwrap
750 // and perform it on the program handler and the new section
751 ResultAction resultAction = actions.getResultAction();
752 if (resultAction != null) {
753 if (logger.isDebugEnabled()) {
754 logger.debug("Performing ra: " + resultAction);
755 }
756 resultAction.perform(program.handler, section);
757 }
758 // get the no result (validate, allow, reject) actions
759 NoResultAction[] nra = actions.getNoResultActions();
760 if (logger.isDebugEnabled()) {
761 logger.debug("No result actions: " + nra.length);
762 }
763 for (int j = 0; j < nra.length; j++) {
764 NoResultAction tem = nra[j];
765 // if we did not encountered this action already then perform it on the
766 // section and add it to the noResultActions list
767 if (!noResultActions.contains(tem)) {
768 if (logger.isDebugEnabled()) {
769 logger.debug("Performing nra: " + nra[j]);
770 }
771 nra[j].perform(section);
772 noResultActions.add(tem);
773 }
774 }
775 }
776 if (logger.isDebugEnabled()) {
777 logger.debug("Section validators: " + section.validators.size());
778 }
779 // iterate the validators on the new section and set their content
780 // handler to receive notifications and set the locator,
781 // call start document, and bind the current namespace context.
782 for (int i = 0, len = section.validators.size(); i < len; i++) {
783 ContentHandler handler = ((Validator)section.validators.elementAt(i)).getContentHandler();
784 if (logger.isDebugEnabled()) {
785 logger.debug("Init validator handler: " + handler);
786 }
787 initHandler(handler);
788 }
789 // store the new section as the current section
790 currentSection = section;
791 }
792
793 /**
794 * Initialize a content handler. This content handler will receive the
795 * document fragment starting at the current element. Therefore we need
796 * to set a locator, call startDocument and give the current namespace
797 * content to that content handler.
798 * @param ch The content handler.
799 * @throws SAXException
800 */
801 private void initHandler(ContentHandler ch) throws SAXException {
802 // set the locator
803 if (locator != null)
804 ch.setDocumentLocator(locator);
805 // start the document
806 ch.startDocument();
807 // set the namespace context
808 for (PrefixMapping pm = prefixMapping; pm != null; pm = pm.parent)
809 ch.startPrefixMapping(pm.prefix, pm.uri);
810 }
811
812 /**
813 * endElement callback
814 * @param uri The namespace uri
815 * @param localName The element local name
816 * @param qName The element qualified name
817 */
818 public void endElement(String uri, String localName, String qName)
819 throws SAXException {
820
821 elementsLocalNameStack.pop();
822 // iterate the active handlers from the current section and call
823 // endElement on them
824 for (int i = 0, len = currentSection.activeHandlers.size(); i < len; i++)
825 ((ContentHandler)(currentSection.activeHandlers.elementAt(i))).endElement(uri, localName, qName);
826 // decrease the current section depth
827 currentSection.depth--;
828 // if we keep context information (if the section is context dependent)
829 // then remove that information
830 if (currentSection.contextDependent)
831 currentSection.context.pop();
832 // if we have zero depth then the current section was ended, so we call endSection
833 if (currentSection.depth == 0) {
834 for (int i = 0, len = currentSection.placeholderHandlers.size(); i < len; i++) {
835 ContentHandler handler = (ContentHandler)(currentSection.placeholderHandlers.elementAt(i));
836 if (logger.isDebugEnabled()) {
837 logger.debug("Call end placeholder element on handler: " + handler);
838 }
839 handler.endPrefixMapping("");
840 handler.endElement("http://purl.oclc.org/dsdl/nvdl/ns/instance/1.0", "placeholder", "placeholder");
841 }
842 endSection();
843 }
844 }
845
846 /**
847 * End a section, its depth reached zero.
848 * @throws SAXException
849 */
850 private void endSection() throws SAXException {
851 // iterate validators
852 for (int i = 0, len = currentSection.validators.size(); i < len; i++) {
853 Validator validator = (Validator)currentSection.validators.elementAt(i);
854 // remove namespaces and call end document on each handler
855 cleanupHandler(validator.getContentHandler());
856 // release the validators to the cache be reused further on other sections
857 releaseValidator((Schema)currentSection.schemas.elementAt(i), validator);
858 // endDocument() on one of the validators may throw an exception
859 // in this case we don't want to release the validator twice
860 currentSection.validators.setElementAt(null, i);
861 }
862 // set the parent section as the current section
863 currentSection = currentSection.parent;
864 }
865
866 /**
867 * Cleanup a handler.
868 * Remove proxy namespace mappings calling endPrefixMapping and calls also endDocument
869 * to signal that the source was ended.
870 * @param vh The validator content handler to clean up.
871 * @throws SAXException
872 */
873 private void cleanupHandler(ContentHandler vh) throws SAXException {
874 for (PrefixMapping pm = prefixMapping; pm != null; pm = pm.parent)
875 vh.endPrefixMapping(pm.prefix);
876 vh.endDocument();
877 }
878
879 /**
880 * endDocument callback
881 * We should be in the initial section now so no op is required.
882 */
883 public void endDocument()
884 throws SAXException {
885 }
886
887 /**
888 * start prefix mapping callback
889 */
890 public void startPrefixMapping(String prefix, String uri)
891 throws SAXException {
892 super.startPrefixMapping(prefix, uri);
893 prefixMapping = new PrefixMapping(prefix, uri, prefixMapping);
894 }
895
896 /**
897 * end prefix mapping callback
898 */
899 public void endPrefixMapping(String prefix)
900 throws SAXException {
901 super.endPrefixMapping(prefix);
902 prefixMapping = prefixMapping.parent;
903 }
904
905 /**
906 * Get a validator for a schema.
907 * If we already have a validator for this schema available in cache
908 * then we will use it and remove it from cache. At the end it will be
909 * added back to the cache through releaseValidator.
910 * @param schema The schema we need a validaor for.
911 * @return A Validator for the given schema.
912 */
913 private Validator createValidator(Schema schema) {
914 Stack stack = (Stack)validatorHandlerCache.get(schema);
915 if (stack == null) {
916 stack = new Stack();
917 validatorHandlerCache.put(schema, stack);
918 }
919 if (stack.empty())
920 return schema.createValidator(properties);
921 return (Validator)stack.pop();
922 }
923
924 /**
925 * Releases a validator for a given schema. Put that validator in the
926 * cache so that further actions to validate against this schema will
927 * be able to use this validator instead of creating a new one.
928 * @param schema The schema the validator validates against
929 * @param vh The validator.
930 */
931 private void releaseValidator(Schema schema, Validator vh) {
932 if (vh == null)
933 return;
934 vh.reset();
935 ((Stack)validatorHandlerCache.get(schema)).push(vh);
936 }
937
938 /**
939 * Reset the NVDL validator so it can be used further on
940 * other sources.
941 */
942 public void reset() {
943 // iterrate all sections from the current section up to the root.
944 for (; currentSection != null; currentSection = currentSection.parent) {
945 // if we have validators in this section iterate them
946 for (int i = 0, len = currentSection.validators.size(); i < len; i++)
947 // release the validator
948 releaseValidator((Schema)currentSection.schemas.elementAt(i),
949 (Validator)currentSection.validators.elementAt(i));
950 }
951 // create the initial section in the start mode.
952 initCurrentSection();
953 }
954
955 /**
956 * Get the content handler for this NVDL validator.
957 */
958 public ContentHandler getContentHandler() {
959 return this;
960 }
961
962 /**
963 * Get the DTD handler for this NVDL validator.
964 */
965 public DTDHandler getDTDHandler() {
966 return this;
967 }
968 }