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    }