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 }