001 package com.thaiopensource.validate.mns; 002 003 import com.thaiopensource.util.Localizer; 004 import com.thaiopensource.util.PropertyMap; 005 import com.thaiopensource.util.Uri; 006 import com.thaiopensource.util.PropertyMapBuilder; 007 import com.thaiopensource.validate.IncorrectSchemaException; 008 import com.thaiopensource.validate.Schema; 009 import com.thaiopensource.validate.ValidateProperty; 010 import com.thaiopensource.validate.Validator; 011 import com.thaiopensource.validate.AbstractSchema; 012 import com.thaiopensource.validate.auto.SchemaFuture; 013 import com.thaiopensource.xml.sax.XmlBaseHandler; 014 import com.thaiopensource.xml.sax.DelegatingContentHandler; 015 import com.thaiopensource.xml.sax.CountingErrorHandler; 016 import com.thaiopensource.xml.util.Name; 017 import com.thaiopensource.xml.util.StringSplitter; 018 import com.thaiopensource.xml.util.WellKnownNamespaces; 019 import org.xml.sax.Attributes; 020 import org.xml.sax.ErrorHandler; 021 import org.xml.sax.InputSource; 022 import org.xml.sax.Locator; 023 import org.xml.sax.SAXException; 024 import org.xml.sax.SAXParseException; 025 import org.xml.sax.XMLReader; 026 import org.xml.sax.helpers.LocatorImpl; 027 028 import java.io.IOException; 029 import java.util.Enumeration; 030 import java.util.Hashtable; 031 import java.util.Stack; 032 033 class SchemaImpl extends AbstractSchema { 034 static final String MNS_URI = "http://www.thaiopensource.com/ns/mns"; 035 private final Hashtable modeMap = new Hashtable(); 036 private Mode startMode; 037 private static final String DEFAULT_MODE_NAME = "#default"; 038 private final boolean attributesSchema; 039 040 static private final class WrappedIOException extends RuntimeException { 041 private final IOException exception; 042 043 private WrappedIOException(IOException exception) { 044 this.exception = exception; 045 } 046 047 private IOException getException() { 048 return exception; 049 } 050 } 051 052 static class ElementAction { 053 private final Schema schema; 054 private final Mode mode; 055 private final ContextMap contextMap; 056 private final ElementsOrAttributes prune; 057 private final Hashset covered = new Hashset(); 058 059 ElementAction(String ns, Schema schema, Mode mode, ContextMap contextMap, ElementsOrAttributes prune) { 060 this.schema = schema; 061 this.mode = mode; 062 this.contextMap = contextMap; 063 this.prune = prune; 064 covered.add(ns); 065 } 066 067 Mode getMode() { 068 return mode; 069 } 070 071 ContextMap getContextMap() { 072 return contextMap; 073 } 074 075 Schema getSchema() { 076 return schema; 077 } 078 079 ElementsOrAttributes getPrune() { 080 return prune; 081 } 082 083 Hashset getCoveredNamespaces() { 084 return covered; 085 } 086 } 087 088 static class Mode { 089 private Locator whereDefined; 090 private boolean defined = false; 091 private ElementsOrAttributes lax; 092 private boolean strictDefined = false; 093 private final Hashtable elementMap = new Hashtable(); 094 private final Hashtable attributesMap = new Hashtable(); 095 096 Mode(ElementsOrAttributes lax) { 097 this.lax = lax; 098 } 099 100 ElementsOrAttributes getLax() { 101 return lax; 102 } 103 104 Schema getAttributesSchema(String ns) { 105 return (Schema)attributesMap.get(ns); 106 } 107 108 ElementAction getElementAction(String ns) { 109 return (ElementAction)elementMap.get(ns); 110 } 111 } 112 113 private class Handler extends DelegatingContentHandler implements SchemaFuture { 114 private final SchemaReceiverImpl sr; 115 private ElementAction currentElementAction; 116 private boolean hadError = false; 117 private final ErrorHandler eh; 118 private final CountingErrorHandler ceh; 119 private final Localizer localizer = new Localizer(SchemaImpl.class); 120 private Locator locator; 121 private final XmlBaseHandler xmlBaseHandler = new XmlBaseHandler(); 122 private int foreignDepth = 0; 123 private String contextNs; 124 private Mode contextMode; 125 private String elementNs; 126 private String defaultSchemaType; 127 private final Stack nameStack = new Stack(); 128 private boolean isRoot; 129 private int pathDepth = 0; 130 private Validator validator; 131 132 133 Handler(SchemaReceiverImpl sr) { 134 this.sr = sr; 135 this.eh = ValidateProperty.ERROR_HANDLER.get(sr.getProperties()); 136 this.ceh = new CountingErrorHandler(eh); 137 } 138 139 public void setDocumentLocator(Locator locator) { 140 xmlBaseHandler.setLocator(locator); 141 this.locator = locator; 142 } 143 144 public void startDocument() throws SAXException { 145 try { 146 PropertyMapBuilder builder = new PropertyMapBuilder(sr.getProperties()); 147 ValidateProperty.ERROR_HANDLER.put(builder, ceh); 148 validator = sr.getMnsSchema().createValidator(builder.toPropertyMap()); 149 } 150 catch (IOException e) { 151 throw new WrappedIOException(e); 152 } 153 catch (IncorrectSchemaException e) { 154 throw new RuntimeException("internal error in RNG schema for MNS"); 155 } 156 setDelegate(validator.getContentHandler()); 157 if (locator != null) 158 super.setDocumentLocator(locator); 159 super.startDocument(); 160 } 161 162 public Schema getSchema() throws IncorrectSchemaException, SAXException { 163 if (validator == null || ceh.getHadErrorOrFatalError()) 164 throw new IncorrectSchemaException(); 165 for (Enumeration en = modeMap.keys(); en.hasMoreElements();) { 166 String modeName = (String)en.nextElement(); 167 Mode mode = (Mode)modeMap.get(modeName); 168 if (!mode.defined && !modeName.equals(DEFAULT_MODE_NAME)) 169 error("undefined_mode", modeName, mode.whereDefined); 170 } 171 if (hadError) 172 throw new IncorrectSchemaException(); 173 return SchemaImpl.this; 174 } 175 176 public RuntimeException unwrapException(RuntimeException e) throws SAXException, IOException, IncorrectSchemaException { 177 if (e instanceof WrappedIOException) 178 throw ((WrappedIOException)e).getException(); 179 return e; 180 } 181 182 public void startElement(String uri, String localName, 183 String qName, Attributes attributes) 184 throws SAXException { 185 super.startElement(uri, localName, qName, attributes); 186 xmlBaseHandler.startElement(); 187 String xmlBase = attributes.getValue(WellKnownNamespaces.XML, "base"); 188 if (xmlBase != null) 189 xmlBaseHandler.xmlBaseAttribute(xmlBase); 190 if (!MNS_URI.equals(uri) || foreignDepth > 0) { 191 foreignDepth++; 192 return; 193 } 194 if (ceh.getHadErrorOrFatalError()) 195 return; 196 if (localName.equals("rules")) 197 parseRules(attributes); 198 else if (localName.equals("cover")) 199 parseCover(attributes); 200 else if (localName.equals("context")) 201 parseContext(attributes); 202 else if (localName.equals("root")) 203 parseRoot(attributes); 204 else if (localName.equals("element")) 205 parseElement(attributes); 206 else if (localName.equals("lax")) 207 parseLax(attributes); 208 else 209 parseValidate(localName.equals("validateAttributes"), attributes); 210 } 211 212 public void endElement(String namespaceURI, String localName, 213 String qName) 214 throws SAXException { 215 super.endElement(namespaceURI, localName, qName); 216 xmlBaseHandler.endElement(); 217 if (foreignDepth > 0) { 218 foreignDepth--; 219 return; 220 } 221 if (pathDepth > 0) { 222 pathDepth--; 223 if (pathDepth == 0) 224 endPath(); 225 } 226 } 227 228 229 private void parseRules(Attributes attributes) { 230 String modeName = attributes.getValue("", "startMode"); 231 if (modeName == null) 232 modeName = DEFAULT_MODE_NAME; 233 defaultSchemaType = getSchemaType(attributes); 234 startMode = lookupCreateMode(modeName); 235 } 236 237 private void parseCover(Attributes attributes) throws SAXException { 238 String ns = getNs(attributes, false); 239 currentElementAction.covered.add(ns); 240 } 241 242 private void parseLax(Attributes attributes) throws SAXException { 243 String[] modeNames = getInModes(attributes); 244 Mode[] modes = getModes(modeNames); 245 ElementsOrAttributes lax = toElementsOrAttributes(attributes.getValue("", "allow"), 246 ElementsOrAttributes.BOTH); 247 for (int i = 0; i < modes.length; i++) { 248 if (modes[i].strictDefined) 249 error("lax_multiply_defined", modeNames[i]); 250 else { 251 modes[i].lax = lax; 252 modes[i].strictDefined = true; 253 } 254 } 255 } 256 257 private void parseValidate(boolean isAttribute, Attributes attributes) throws SAXException { 258 String[] modeNames = getInModes(attributes); 259 Mode[] modes = getModes(modeNames); 260 String ns = getNs(attributes, isAttribute); 261 String schemaUri = getSchema(attributes); 262 String schemaType = getSchemaType(attributes); 263 if (schemaType == null) 264 schemaType = defaultSchemaType; 265 try { 266 if (isAttribute) { 267 Schema schema = sr.createChildSchema(new InputSource(schemaUri), schemaType, true); 268 for (int i = 0; i < modes.length; i++) { 269 if (modes[i].attributesMap.get(ns) != null) 270 error("validate_attributes_multiply_defined", modeNames[i], ns); 271 else 272 modes[i].attributesMap.put(ns, schema); 273 } 274 } 275 else { 276 Schema schema = sr.createChildSchema(new InputSource(schemaUri), schemaType, false); 277 currentElementAction = new ElementAction(ns, 278 schema, 279 getUseMode(attributes), 280 new ContextMap(), 281 getPrune(attributes)); 282 contextNs = ns; 283 for (int i = 0; i < modes.length; i++) { 284 if (modes[i].elementMap.get(ns) != null) 285 error("validate_element_multiply_defined", modeNames[i], ns); 286 else 287 modes[i].elementMap.put(ns, currentElementAction); 288 } 289 } 290 } 291 catch (IncorrectSchemaException e) { 292 hadError = true; 293 } 294 catch (IOException e) { 295 throw new WrappedIOException(e); 296 } 297 } 298 299 private void parseContext(Attributes attributes) throws SAXException { 300 String ns = getNs(attributes, false); 301 if (ns != null) 302 contextNs = ns; 303 elementNs = contextNs; 304 contextMode = getUseMode(attributes); 305 } 306 307 private void parseRoot(Attributes attributes) throws SAXException { 308 String ns = getNs(attributes, false); 309 if (ns != null) 310 elementNs = ns; 311 isRoot = true; 312 pathDepth++; 313 } 314 315 private void parseElement(Attributes attributes) throws SAXException { 316 String ns = getNs(attributes, false); 317 if (ns != null) 318 elementNs = ns; 319 if (!currentElementAction.covered.contains(elementNs)) 320 error("context_ns_not_covered", elementNs); 321 nameStack.push(new Name(elementNs, attributes.getValue("", "name").trim())); 322 pathDepth++; 323 } 324 325 private void endPath() throws SAXException { 326 if (!currentElementAction.contextMap.put(isRoot, nameStack, contextMode)) 327 error("path_multiply_defined", displayPath(isRoot, nameStack)); 328 elementNs = contextNs; 329 isRoot = false; 330 nameStack.setSize(0); 331 } 332 333 private String displayPath(boolean isRoot, Stack nameStack) { 334 StringBuffer buf = new StringBuffer(); 335 for (int i = 0, len = nameStack.size(); i < len; i++) { 336 if (i > 0 || isRoot) 337 buf.append('/'); 338 Name name = (Name)nameStack.elementAt(i); 339 if (name.getNamespaceUri().length() > 0) { 340 buf.append('{'); 341 buf.append(name.getNamespaceUri()); 342 buf.append('}'); 343 } 344 buf.append(name.getLocalName()); 345 } 346 return buf.toString(); 347 } 348 349 private String getSchema(Attributes attributes) throws SAXException { 350 String schemaUri = attributes.getValue("", "schema"); 351 if (Uri.hasFragmentId(schemaUri)) 352 error("schema_fragment_id"); 353 return Uri.resolve(xmlBaseHandler.getBaseUri(), 354 Uri.escapeDisallowedChars(schemaUri)); 355 } 356 357 private String getSchemaType(Attributes attributes) { 358 return attributes.getValue("", "schemaType"); 359 } 360 361 private ElementsOrAttributes getPrune(Attributes attributes) { 362 return toElementsOrAttributes(attributes.getValue("", "prune"), 363 ElementsOrAttributes.NEITHER); 364 } 365 366 private ElementsOrAttributes toElementsOrAttributes(String value, ElementsOrAttributes defaultValue) { 367 if (value == null) 368 return defaultValue; 369 ElementsOrAttributes eoa = ElementsOrAttributes.NEITHER; 370 if (value.indexOf("elements") >= 0) 371 eoa = eoa.addElements(); 372 if (value.indexOf("attributes") >= 0) 373 eoa = eoa.addAttributes(); 374 return eoa; 375 } 376 377 private Mode getUseMode(Attributes attributes) { 378 String modeName = attributes.getValue("", "useMode"); 379 if (modeName == null) 380 modeName = DEFAULT_MODE_NAME; 381 Mode mode = lookupCreateMode(modeName); 382 if (mode.whereDefined == null && locator != null) 383 mode.whereDefined = new LocatorImpl(locator); 384 return mode; 385 } 386 387 private String getNs(Attributes attributes, boolean forbidEmpty) throws SAXException { 388 String ns = attributes.getValue("", "ns"); 389 if (ns != null && !Uri.isAbsolute(ns) && (forbidEmpty || !ns.equals(""))) 390 error("ns_absolute"); 391 return ns; 392 } 393 394 private Mode[] getModes(String[] modeNames) { 395 Mode[] modes = new Mode[modeNames.length]; 396 for (int i = 0; i < modes.length; i++) { 397 modes[i] = lookupCreateMode(modeNames[i]); 398 modes[i].defined = true; 399 } 400 return modes; 401 } 402 403 private String[] getInModes(Attributes attributes) { 404 String inModes = attributes.getValue("", "inModes"); 405 if (inModes == null) 406 return new String[] { DEFAULT_MODE_NAME }; 407 return StringSplitter.split(inModes); 408 } 409 410 411 void error(String key) throws SAXException { 412 hadError = true; 413 if (eh == null) 414 return; 415 eh.error(new SAXParseException(localizer.message(key), locator)); 416 } 417 418 void error(String key, String arg) throws SAXException { 419 hadError = true; 420 if (eh == null) 421 return; 422 eh.error(new SAXParseException(localizer.message(key, arg), locator)); 423 } 424 425 void error(String key, String arg, Locator locator) throws SAXException { 426 hadError = true; 427 if (eh == null) 428 return; 429 eh.error(new SAXParseException(localizer.message(key, arg), locator)); 430 } 431 432 void error(String key, String arg1, String arg2) throws SAXException { 433 hadError = true; 434 if (eh == null) 435 return; 436 eh.error(new SAXParseException(localizer.message(key, arg1, arg2), locator)); 437 } 438 439 } 440 441 SchemaImpl(boolean attributesSchema) { 442 this.attributesSchema = attributesSchema; 443 } 444 445 SchemaFuture installHandlers(XMLReader in, SchemaReceiverImpl sr) { 446 Handler h = new Handler(sr); 447 in.setContentHandler(h); 448 return h; 449 } 450 451 public Validator createValidator(PropertyMap properties) { 452 return new ValidatorImpl(startMode, properties); 453 } 454 455 private Mode lookupCreateMode(String name) { 456 Mode mode = (Mode)modeMap.get(name); 457 if (mode == null) { 458 mode = new Mode(attributesSchema ? ElementsOrAttributes.ELEMENTS : ElementsOrAttributes.NEITHER); 459 modeMap.put(name, mode); 460 } 461 return mode; 462 } 463 464 }