1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.xerces.impl;
19
20 import java.util.Hashtable;
21 import java.util.Locale;
22
23 import org.apache.xerces.util.DefaultErrorHandler;
24 import org.apache.xerces.util.ErrorHandlerProxy;
25 import org.apache.xerces.util.MessageFormatter;
26 import org.apache.xerces.xni.XMLLocator;
27 import org.apache.xerces.xni.XNIException;
28 import org.apache.xerces.xni.parser.XMLComponent;
29 import org.apache.xerces.xni.parser.XMLComponentManager;
30 import org.apache.xerces.xni.parser.XMLConfigurationException;
31 import org.apache.xerces.xni.parser.XMLErrorHandler;
32 import org.apache.xerces.xni.parser.XMLParseException;
33 import org.xml.sax.ErrorHandler;
34
35 /**
36 * This class is a common element of all parser configurations and is
37 * used to report errors that occur. This component can be queried by
38 * parser components from the component manager using the following
39 * property ID:
40 * <pre>
41 * http://apache.org/xml/properties/internal/error-reporter
42 * </pre>
43 * <p>
44 * Errors are separated into domains that categorize a class of errors.
45 * In a parser configuration, the parser would register a
46 * <code>MessageFormatter</code> for each domain that is capable of
47 * localizing error messages and formatting them based on information
48 * about the error. Any parser component can invent new error domains
49 * and register additional message formatters to localize messages in
50 * those domains.
51 * <p>
52 * This component requires the following features and properties from the
53 * component manager that uses it:
54 * <ul>
55 * <li>http://apache.org/xml/properties/internal/error-handler</li>
56 * </ul>
57 * <p>
58 * This component can use the following features and properties but they
59 * are not required:
60 * <ul>
61 * <li>http://apache.org/xml/features/continue-after-fatal-error</li>
62 * </ul>
63 *
64 * @xerces.internal
65 *
66 * @see MessageFormatter
67 *
68 * @author Eric Ye, IBM
69 * @author Andy Clark, IBM
70 *
71 * @version $Id: XMLErrorReporter.java 575002 2007-09-12 16:02:04Z mrglavas $
72 */
73 public class XMLErrorReporter
74 implements XMLComponent {
75
76 //
77 // Constants
78 //
79
80 // severity
81
82 /**
83 * Severity: warning. Warnings represent informational messages only
84 * that should not be considered serious enough to stop parsing or
85 * indicate an error in the document's validity.
86 */
87 public static final short SEVERITY_WARNING = 0;
88
89 /**
90 * Severity: error. Common causes of errors are document structure and/or
91 * content that that does not conform to the grammar rules specified for
92 * the document. These are typically validation errors.
93 */
94 public static final short SEVERITY_ERROR = 1;
95
96 /**
97 * Severity: fatal error. Fatal errors are errors in the syntax of the
98 * XML document or invalid byte sequences for a given encoding. The
99 * XML 1.0 Specification mandates that errors of this type are not
100 * recoverable.
101 * <p>
102 * <strong>Note:</strong> The parser does have a "continue after fatal
103 * error" feature but it should be used with extreme caution and care.
104 */
105 public static final short SEVERITY_FATAL_ERROR = 2;
106
107 // feature identifiers
108
109 /** Feature identifier: continue after fatal error. */
110 protected static final String CONTINUE_AFTER_FATAL_ERROR =
111 Constants.XERCES_FEATURE_PREFIX + Constants.CONTINUE_AFTER_FATAL_ERROR_FEATURE;
112
113 // property identifiers
114
115 /** Property identifier: error handler. */
116 protected static final String ERROR_HANDLER =
117 Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_HANDLER_PROPERTY;
118
119 // recognized features and properties
120
121 /** Recognized features. */
122 private static final String[] RECOGNIZED_FEATURES = {
123 CONTINUE_AFTER_FATAL_ERROR,
124 };
125
126 /** Feature defaults. */
127 private static final Boolean[] FEATURE_DEFAULTS = {
128 null,
129 };
130
131 /** Recognized properties. */
132 private static final String[] RECOGNIZED_PROPERTIES = {
133 ERROR_HANDLER,
134 };
135
136 /** Property defaults. */
137 private static final Object[] PROPERTY_DEFAULTS = {
138 null,
139 };
140
141 //
142 // Data
143 //
144
145 /** The locale to be used to format error messages. */
146 protected Locale fLocale;
147
148 /** Mapping of Message formatters for domains. */
149 protected Hashtable fMessageFormatters;
150
151 /** Error handler. */
152 protected XMLErrorHandler fErrorHandler;
153
154 /** Document locator. */
155 protected XMLLocator fLocator;
156
157 /** Continue after fatal error feature. */
158 protected boolean fContinueAfterFatalError;
159
160 /**
161 * Default error handler. This error handler is only used in the
162 * absence of a registered error handler so that errors are not
163 * "swallowed" silently. This is one of the most common "problems"
164 * reported by users of the parser.
165 */
166 protected XMLErrorHandler fDefaultErrorHandler;
167
168 /** A SAX proxy to the error handler contained in this error reporter. */
169 private ErrorHandler fSaxProxy = null;
170
171 //
172 // Constructors
173 //
174
175 /** Constructs an error reporter with a locator. */
176 public XMLErrorReporter() {
177
178 // REVISIT: [Q] Should the locator be passed to the reportError
179 // method? Otherwise, there is no way for a parser
180 // component to store information about where an
181 // error occurred so as to report it later.
182 //
183 // An example would be to record the location of
184 // IDREFs so that, at the end of the document, if
185 // there is no associated ID declared, the error
186 // could report the location information of the
187 // reference. -Ac
188 //
189 // NOTE: I added another reportError method that allows the
190 // caller to specify the location of the error being
191 // reported. -Ac
192
193 fMessageFormatters = new Hashtable();
194
195 } // <init>()
196
197 //
198 // Methods
199 //
200
201 /**
202 * Sets the current locale.
203 *
204 * @param locale The new locale.
205 */
206 public void setLocale(Locale locale) {
207 fLocale = locale;
208 } // setLocale(Locale)
209
210 /**
211 * Gets the current locale.
212 *
213 * @return the current Locale
214 */
215 public Locale getLocale() {
216 return fLocale ;
217 } // getLocale(): Locale
218
219 /**
220 * Sets the document locator.
221 *
222 * @param locator The locator.
223 */
224 public void setDocumentLocator(XMLLocator locator) {
225 fLocator = locator;
226 } // setDocumentLocator(XMLLocator)
227
228 /**
229 * Registers a message formatter for the specified domain.
230 * <p>
231 * <strong>Note:</strong> Registering a message formatter for a domain
232 * when there is already a formatter registered will cause the previous
233 * formatter to be lost. This method replaces any previously registered
234 * message formatter for the specified domain.
235 *
236 * @param domain
237 * @param messageFormatter
238 */
239 public void putMessageFormatter(String domain,
240 MessageFormatter messageFormatter) {
241 fMessageFormatters.put(domain, messageFormatter);
242 } // putMessageFormatter(String,MessageFormatter)
243
244 /**
245 * Returns the message formatter associated with the specified domain,
246 * or null if no message formatter is registered for that domain.
247 *
248 * @param domain The domain of the message formatter.
249 */
250 public MessageFormatter getMessageFormatter(String domain) {
251 return (MessageFormatter)fMessageFormatters.get(domain);
252 } // getMessageFormatter(String):MessageFormatter
253
254 /**
255 * Removes the message formatter for the specified domain and
256 * returns the removed message formatter.
257 *
258 * @param domain The domain of the message formatter.
259 */
260 public MessageFormatter removeMessageFormatter(String domain) {
261 return (MessageFormatter) fMessageFormatters.remove(domain);
262 } // removeMessageFormatter(String):MessageFormatter
263
264 /**
265 * Reports an error. The error message passed to the error handler
266 * is formatted for the locale by the message formatter installed
267 * for the specified error domain.
268 *
269 * @param domain The error domain.
270 * @param key The key of the error message.
271 * @param arguments The replacement arguments for the error message,
272 * if needed.
273 * @param severity The severity of the error.
274 *
275 * @see #SEVERITY_WARNING
276 * @see #SEVERITY_ERROR
277 * @see #SEVERITY_FATAL_ERROR
278 */
279 public void reportError(String domain, String key, Object[] arguments,
280 short severity) throws XNIException {
281 reportError(fLocator, domain, key, arguments, severity);
282 } // reportError(String,String,Object[],short)
283
284 /**
285 * Reports an error. The error message passed to the error handler
286 * is formatted for the locale by the message formatter installed
287 * for the specified error domain.
288 *
289 * @param domain The error domain.
290 * @param key The key of the error message.
291 * @param arguments The replacement arguments for the error message,
292 * if needed.
293 * @param severity The severity of the error.
294 * @param exception The exception to wrap.
295 *
296 * @see #SEVERITY_WARNING
297 * @see #SEVERITY_ERROR
298 * @see #SEVERITY_FATAL_ERROR
299 */
300 public void reportError(String domain, String key, Object[] arguments,
301 short severity, Exception exception) throws XNIException {
302 reportError(fLocator, domain, key, arguments, severity, exception);
303 } // reportError(String,String,Object[],short,Exception)
304
305 /**
306 * Reports an error at a specific location.
307 *
308 * @param location The error location.
309 * @param domain The error domain.
310 * @param key The key of the error message.
311 * @param arguments The replacement arguments for the error message,
312 * if needed.
313 * @param severity The severity of the error.
314 *
315 * @see #SEVERITY_WARNING
316 * @see #SEVERITY_ERROR
317 * @see #SEVERITY_FATAL_ERROR
318 */
319 public void reportError(XMLLocator location,
320 String domain, String key, Object[] arguments,
321 short severity) throws XNIException {
322 reportError(location, domain, key, arguments, severity, null);
323 } // reportError(XMLLocator,String,String,Object[],short)
324
325 /**
326 * Reports an error at a specific location.
327 *
328 * @param location The error location.
329 * @param domain The error domain.
330 * @param key The key of the error message.
331 * @param arguments The replacement arguments for the error message,
332 * if needed.
333 * @param severity The severity of the error.
334 * @param exception The exception to wrap.
335 *
336 * @see #SEVERITY_WARNING
337 * @see #SEVERITY_ERROR
338 * @see #SEVERITY_FATAL_ERROR
339 */
340 public void reportError(XMLLocator location,
341 String domain, String key, Object[] arguments,
342 short severity, Exception exception) throws XNIException {
343
344 // REVISIT: [Q] Should we do anything about invalid severity
345 // parameter? -Ac
346
347 // format error message and create parse exception
348 MessageFormatter messageFormatter = getMessageFormatter(domain);
349 String message;
350 if (messageFormatter != null) {
351 message = messageFormatter.formatMessage(fLocale, key, arguments);
352 }
353 else {
354 StringBuffer str = new StringBuffer();
355 str.append(domain);
356 str.append('#');
357 str.append(key);
358 int argCount = arguments != null ? arguments.length : 0;
359 if (argCount > 0) {
360 str.append('?');
361 for (int i = 0; i < argCount; i++) {
362 str.append(arguments[i]);
363 if (i < argCount -1) {
364 str.append('&');
365 }
366 }
367 }
368 message = str.toString();
369 }
370 XMLParseException parseException = (exception != null) ?
371 new XMLParseException(location, message, exception) :
372 new XMLParseException(location, message);
373
374 // get error handler
375 XMLErrorHandler errorHandler = fErrorHandler;
376 if (errorHandler == null) {
377 if (fDefaultErrorHandler == null) {
378 fDefaultErrorHandler = new DefaultErrorHandler();
379 }
380 errorHandler = fDefaultErrorHandler;
381 }
382
383 // call error handler
384 switch (severity) {
385 case SEVERITY_WARNING: {
386 errorHandler.warning(domain, key, parseException);
387 break;
388 }
389 case SEVERITY_ERROR: {
390 errorHandler.error(domain, key, parseException);
391 break;
392 }
393 case SEVERITY_FATAL_ERROR: {
394 errorHandler.fatalError(domain, key, parseException);
395 if (!fContinueAfterFatalError) {
396 throw parseException;
397 }
398 break;
399 }
400 }
401
402 } // reportError(XMLLocator,String,String,Object[],short,Exception)
403
404 //
405 // XMLComponent methods
406 //
407
408 /**
409 * Resets the component. The component can query the component manager
410 * about any features and properties that affect the operation of the
411 * component.
412 *
413 * @param componentManager The component manager.
414 *
415 * @throws SAXException Thrown by component on initialization error.
416 * For example, if a feature or property is
417 * required for the operation of the component, the
418 * component manager may throw a
419 * SAXNotRecognizedException or a
420 * SAXNotSupportedException.
421 */
422 public void reset(XMLComponentManager componentManager)
423 throws XNIException {
424
425 // features
426 try {
427 fContinueAfterFatalError = componentManager.getFeature(CONTINUE_AFTER_FATAL_ERROR);
428 }
429 catch (XNIException e) {
430 fContinueAfterFatalError = false;
431 }
432
433 // properties
434 fErrorHandler = (XMLErrorHandler)componentManager.getProperty(ERROR_HANDLER);
435
436 } // reset(XMLComponentManager)
437
438 /**
439 * Returns a list of feature identifiers that are recognized by
440 * this component. This method may return null if no features
441 * are recognized by this component.
442 */
443 public String[] getRecognizedFeatures() {
444 return (String[])(RECOGNIZED_FEATURES.clone());
445 } // getRecognizedFeatures():String[]
446
447 /**
448 * Sets the state of a feature. This method is called by the component
449 * manager any time after reset when a feature changes state.
450 * <p>
451 * <strong>Note:</strong> Components should silently ignore features
452 * that do not affect the operation of the component.
453 *
454 * @param featureId The feature identifier.
455 * @param state The state of the feature.
456 *
457 * @throws SAXNotRecognizedException The component should not throw
458 * this exception.
459 * @throws SAXNotSupportedException The component should not throw
460 * this exception.
461 */
462 public void setFeature(String featureId, boolean state)
463 throws XMLConfigurationException {
464
465 //
466 // Xerces features
467 //
468
469 if (featureId.startsWith(Constants.XERCES_FEATURE_PREFIX)) {
470 final int suffixLength = featureId.length() - Constants.XERCES_FEATURE_PREFIX.length();
471
472 //
473 // http://apache.org/xml/features/continue-after-fatal-error
474 // Allows the parser to continue after a fatal error.
475 // Normally, a fatal error would stop the parse.
476 //
477 if (suffixLength == Constants.CONTINUE_AFTER_FATAL_ERROR_FEATURE.length() &&
478 featureId.endsWith(Constants.CONTINUE_AFTER_FATAL_ERROR_FEATURE)) {
479 fContinueAfterFatalError = state;
480 }
481 }
482
483 } // setFeature(String,boolean)
484
485 // return state of given feature or false if unsupported.
486 public boolean getFeature(String featureId)
487 throws XMLConfigurationException {
488
489 //
490 // Xerces features
491 //
492
493 if (featureId.startsWith(Constants.XERCES_FEATURE_PREFIX)) {
494 final int suffixLength = featureId.length() - Constants.XERCES_FEATURE_PREFIX.length();
495
496 //
497 // http://apache.org/xml/features/continue-after-fatal-error
498 // Allows the parser to continue after a fatal error.
499 // Normally, a fatal error would stop the parse.
500 //
501 if (suffixLength == Constants.CONTINUE_AFTER_FATAL_ERROR_FEATURE.length() &&
502 featureId.endsWith(Constants.CONTINUE_AFTER_FATAL_ERROR_FEATURE)) {
503 return fContinueAfterFatalError ;
504 }
505 }
506 return false;
507
508 } // setFeature(String,boolean)
509
510 /**
511 * Returns a list of property identifiers that are recognized by
512 * this component. This method may return null if no properties
513 * are recognized by this component.
514 */
515 public String[] getRecognizedProperties() {
516 return (String[])(RECOGNIZED_PROPERTIES.clone());
517 } // getRecognizedProperties():String[]
518
519 /**
520 * Sets the value of a property. This method is called by the component
521 * manager any time after reset when a property changes value.
522 * <p>
523 * <strong>Note:</strong> Components should silently ignore properties
524 * that do not affect the operation of the component.
525 *
526 * @param propertyId The property identifier.
527 * @param value The value of the property.
528 *
529 * @throws SAXNotRecognizedException The component should not throw
530 * this exception.
531 * @throws SAXNotSupportedException The component should not throw
532 * this exception.
533 */
534 public void setProperty(String propertyId, Object value)
535 throws XMLConfigurationException {
536
537 //
538 // Xerces properties
539 //
540
541 if (propertyId.startsWith(Constants.XERCES_PROPERTY_PREFIX)) {
542 final int suffixLength = propertyId.length() - Constants.XERCES_PROPERTY_PREFIX.length();
543
544 if (suffixLength == Constants.ERROR_HANDLER_PROPERTY.length() &&
545 propertyId.endsWith(Constants.ERROR_HANDLER_PROPERTY)) {
546 fErrorHandler = (XMLErrorHandler)value;
547 }
548 }
549
550 } // setProperty(String,Object)
551
552 /**
553 * Returns the default state for a feature, or null if this
554 * component does not want to report a default value for this
555 * feature.
556 *
557 * @param featureId The feature identifier.
558 *
559 * @since Xerces 2.2.0
560 */
561 public Boolean getFeatureDefault(String featureId) {
562 for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
563 if (RECOGNIZED_FEATURES[i].equals(featureId)) {
564 return FEATURE_DEFAULTS[i];
565 }
566 }
567 return null;
568 } // getFeatureDefault(String):Boolean
569
570 /**
571 * Returns the default state for a property, or null if this
572 * component does not want to report a default value for this
573 * property.
574 *
575 * @param propertyId The property identifier.
576 *
577 * @since Xerces 2.2.0
578 */
579 public Object getPropertyDefault(String propertyId) {
580 for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
581 if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
582 return PROPERTY_DEFAULTS[i];
583 }
584 }
585 return null;
586 } // getPropertyDefault(String):Object
587
588 /**
589 * Get the internal XMLErrrorHandler.
590 */
591 public XMLErrorHandler getErrorHandler() {
592 return fErrorHandler;
593 }
594
595 /**
596 * Gets the internal XMLErrorHandler
597 * as SAX ErrorHandler.
598 */
599 public ErrorHandler getSAXErrorHandler() {
600 if (fSaxProxy == null) {
601 fSaxProxy = new ErrorHandlerProxy() {
602 protected XMLErrorHandler getErrorHandler() {
603 return fErrorHandler;
604 }
605 };
606 }
607 return fSaxProxy;
608 }
609
610 } // class XMLErrorReporter