KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > spi > xml > cookies > SharedXMLSupport


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.spi.xml.cookies;
20
21 import java.io.*;
22 import java.net.*;
23 import java.util.*;
24 import java.security.ProtectionDomain JavaDoc;
25 import java.security.CodeSource JavaDoc;
26
27 import javax.xml.parsers.SAXParser JavaDoc;
28 import javax.xml.parsers.SAXParserFactory JavaDoc;
29 import javax.swing.text.Document JavaDoc;
30
31 import org.openide.cookies.*;
32 import org.openide.util.*;
33 import org.openide.filesystems.FileStateInvalidException;
34 import org.openide.ErrorManager;
35
36 import org.xml.sax.*;
37 import org.xml.sax.helpers.DefaultHandler JavaDoc;
38
39 import org.netbeans.api.xml.cookies.*;
40 import org.netbeans.api.xml.services.*;
41 import org.netbeans.api.xml.parsers.*;
42
43
44 /**
45  * <code>CheckXMLCookie</code> and <code>ValidateXMLCookie</code> cookie
46  * implementation support simplifing cookie providers based on
47  * <code>InputSource</code>s representing XML documents and entities.
48  *
49  * @author Petr Kuzel
50  * @see CheckXMLSupport
51  * @see ValidateXMLSupport
52  */

53 class SharedXMLSupport {
54
55     // it will viasualize our results
56
private CookieObserver console;
57     
58     // associated input source
59
private final InputSource inputSource;
60     
61     // one of above modes CheckXMLSupport modes
62
private final int mode;
63     
64     // error locator or null
65
private Locator locator;
66
67     // fatal error counter
68
private int fatalErrors;
69     
70     // error counter
71
private int errors;
72     
73     /**
74      * Xerces parser tries to search for every namespace declaration
75      * related Schema. It causes trouble it DTD like XHTML defines
76      * default xmlns attribute. It is then inherited by all descendants
77      * and grammar is loaded again and again. Entity resolver set this
78      * flag once it spots (null, null) resolution request that is typical
79      * for bogus Schema location resolution requests.
80      */

81     private boolean bogusSchemaRequest;
82     
83     // If true then the first bogust schema grammar request is reported
84
// all subsequent ones are supressed.
85
private boolean reportBogusSchemaRequest =
86         Boolean.getBoolean("netbeans.xml.reportBogusSchemaLocation"); // NOI18N
87

88     /**
89      * Create new CheckXMLSupport for given InputSource in DOCUMENT_MODE.
90      * @param inputSource Supported InputSource.
91      */

92     public SharedXMLSupport(InputSource inputSource) {
93         this(inputSource, CheckXMLSupport.DOCUMENT_MODE);
94     }
95     
96     /**
97      * Create new CheckXMLSupport for given data object
98      * @param inputSource Supported InputSource.
99      * @param mode one of <code>*_MODE</code> constants
100      */

101     public SharedXMLSupport(InputSource inputSource, int mode) {
102
103         if (inputSource == null) throw new NullPointerException JavaDoc();
104         if (mode < CheckXMLSupport.CHECK_ENTITY_MODE || mode > CheckXMLSupport.DOCUMENT_MODE) {
105             throw new IllegalArgumentException JavaDoc();
106         }
107         
108         this.inputSource = inputSource;
109         this.mode = mode;
110     }
111
112     // inherit JavaDoc
113
boolean checkXML(CookieObserver l) {
114         try {
115             console = l;
116
117             parse(false);
118
119             return fatalErrors == 0;
120         } finally {
121             console = null;
122             locator = null;
123         }
124     }
125     
126     // inherit JavaDoc
127
boolean validateXML(CookieObserver l) {
128         try {
129             console = l;
130
131             if (mode != CheckXMLSupport.DOCUMENT_MODE) {
132                 sendMessage(Util.THIS.getString("MSG_not_a_doc"));
133                 return false;
134             } else {
135                 parse(true);
136                 return errors == 0 && fatalErrors == 0;
137             }
138         } finally {
139             console = null;
140             locator = null;
141         }
142     }
143                 
144
145     
146     /**
147      * Perform parsing in current thread.
148      */

149     private void parse (boolean validate) {
150         
151         fatalErrors = 0;
152         errors = 0;
153                 
154         String JavaDoc checkedFile = inputSource.getSystemId();
155         sendMessage(Util.THIS.getString("MSG_checking", checkedFile));
156
157         Handler JavaDoc handler = new Handler JavaDoc();
158  
159
160         InputSource input = null;
161         
162         try {
163             // set up parser
164
XMLReader parser = createParser(validate);
165             if (parser == null) {
166                 fatalErrors++;
167                 console.receive(new CookieMessage(
168                     Util.THIS.getString("MSG_cannot_create_parser"),
169                     CookieMessage.FATAL_ERROR_LEVEL
170                 ));
171                 return;
172             }
173             
174             if (validate) {
175                 // get all naemspaces for the parser
176
input=ShareableInputSource.create(createInputSource());
177                 String JavaDoc[] schemaLocations=getSchemaLocations(input);
178                 try {
179                     ((ShareableInputSource)input).reset();
180                 }catch(IOException e) {
181                     //mark invalidated - we overlapped the buffer size. Ok, recreate the InputSource
182
//no need to use the shareable - it is read only once
183
input=createInputSource();
184                 }
185                 if (schemaLocations!=null && schemaLocations.length>0) {
186                     boolean first=true;
187                     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
188                     for (int i=0;i<schemaLocations.length;i++) {
189                         sb.append(first?schemaLocations[i]:" "+schemaLocations[i]);
190                         first=false;
191                     }
192                     parser.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", sb.toString()); //NOI18N
193
}
194             } else input = createInputSource();
195             
196             parser.setErrorHandler(handler);
197             parser.setContentHandler(handler);
198             
199             if ( Util.THIS.isLoggable()) {
200                 Util.THIS.debug(checkedFile + ":" + parserDescription(parser));
201             }
202
203             // parse
204
if (mode == CheckXMLSupport.CHECK_ENTITY_MODE) {
205                 new SAXEntityParser(parser, true).parse(input);
206             } else if (mode == CheckXMLSupport.CHECK_PARAMETER_ENTITY_MODE) {
207                 new SAXEntityParser(parser, false).parse(input);
208             } else {
209                 parser.parse (input);
210             }
211
212         } catch (SAXException ex) {
213
214             // same as one catched by ErrorHandler
215
// because we do not have content handler
216

217         } catch (FileStateInvalidException ex) {
218
219             // bad luck report as fatal error
220
handler.fatalError(new SAXParseException(ex.getLocalizedMessage(), locator, ex));
221
222         } catch (IOException ex) {
223
224             // bad luck probably because cannot resolve entity
225
// report as error at -1,-1 if we do not have Locator
226
handler.fatalError(new SAXParseException (ex.getLocalizedMessage(), locator, ex));
227
228         } catch (RuntimeException JavaDoc ex) {
229
230             handler.runtimeError(ex);
231         } finally {
232         if (input instanceof ShareableInputSource)
233             try {
234                 ((ShareableInputSource)input).closeAll();
235             } catch (IOException ex) {}
236         }
237         
238     }
239
240     /**
241      * Parametrizes default parser creatin process. Default implementation
242      * takes user's catalog entity resolver.
243      * @return EntityResolver entity resolver or <code>null</code>
244      */

245     protected EntityResolver createEntityResolver() {
246         UserCatalog catalog = UserCatalog.getDefault();
247         return catalog == null ? null : catalog.getEntityResolver();
248     }
249     
250     /**
251      * Create InputSource to be checked.
252      * @throws IOException if I/O error occurs.
253      * @return InputSource never <code>null</code>
254      */

255     protected InputSource createInputSource() throws IOException {
256         return inputSource;
257     }
258
259     /**
260      * Create and preconfigure new parser. Default implementation uses JAXP.
261      * @param validate true if validation module is required
262      * @return SAX reader that is used for command performing or <code>null</code>
263      * @see #createEntityResolver
264      */

265     protected XMLReader createParser(boolean validate) {
266        
267         XMLReader ret = null;
268         final String JavaDoc XERCES_FEATURE_PREFIX = "http://apache.org/xml/features/"; // NOI18N
269
final String JavaDoc XERCES_PROPERTY_PREFIX = "http://apache.org/xml/properties/"; // NOI18N
270

271        // JAXP plugin parser (bastarded by core factories!)
272

273         SAXParserFactory JavaDoc factory = SAXParserFactory.newInstance();
274         factory.setNamespaceAware(true);
275         factory.setValidating(validate);
276
277         //??? It is Xerces specifics, but no general API for XML Schema based validation exists
278
if (validate) {
279             try {
280                 factory.setFeature(XERCES_FEATURE_PREFIX + "validation/schema", validate); // NOI18N
281
} catch (Exception JavaDoc ex) {
282                 sendMessage(Util.THIS.getString("MSG_parser_no_schema"));
283             }
284         }
285     
286         try {
287             SAXParser JavaDoc parser = factory.newSAXParser();
288             ret = parser.getXMLReader();
289         } catch (Exception JavaDoc ex) {
290             sendMessage(Util.THIS.getString("MSG_parser_err_1"));
291             return null;
292         }
293
294
295         if (ret != null) {
296             EntityResolver res = createEntityResolver();
297             if (res != null) ret.setEntityResolver(new VerboseEntityResolver(res));
298         }
299         
300         return ret;
301         
302     }
303
304     /**
305      * It may be helpfull for tracing down some oddities.
306      */

307     private String JavaDoc parserDescription(XMLReader parser) {
308
309         // report which parser implementation is used
310

311         Class JavaDoc klass = parser.getClass();
312         try {
313             ProtectionDomain JavaDoc domain = klass.getProtectionDomain();
314             CodeSource JavaDoc source = domain.getCodeSource();
315             
316             if (source == null && (klass.getClassLoader() == null || klass.getClassLoader().equals(Object JavaDoc.class.getClassLoader()))) {
317                 return Util.THIS.getString("MSG_platform_parser");
318             } else if (source == null) {
319                 return Util.THIS.getString("MSG_unknown_parser", klass.getName());
320             } else {
321                 URL location = source.getLocation();
322                 return Util.THIS.getString("MSG_parser_plug", location.toExternalForm());
323             }
324             
325         } catch (SecurityException JavaDoc ex) {
326             return Util.THIS.getString("MSG_unknown_parser", klass.getName());
327         }
328         
329     }
330     
331     // Content & ErrorHandler implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
332

333
334     private class Handler extends DefaultHandler JavaDoc {
335     
336         public void warning (SAXParseException ex) {
337             
338             // heuristics to detect bogus schema loading requests
339

340             String JavaDoc msg = ex.getLocalizedMessage();
341             if (bogusSchemaRequest) {
342                 bogusSchemaRequest = false;
343                 if (msg != null && msg.indexOf("schema_reference.4") != -1) { // NOI18N
344
if (reportBogusSchemaRequest) {
345                         reportBogusSchemaRequest = false;
346                     } else {
347                         return;
348                     }
349                 }
350             }
351             
352             CookieMessage message = new CookieMessage(
353                 msg,
354                 CookieMessage.WARNING_LEVEL,
355                 new DefaultXMLProcessorDetail(ex)
356             );
357             if (console != null) console.receive(message);
358         }
359
360         /**
361          * Report maximally getMaxErrorCount() errors then stop the parser.
362          */

363         public void error (SAXParseException ex) throws SAXException {
364             if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("Just diagnostic exception", ex); // NOI18N
365
if (errors++ == getMaxErrorCount()) {
366                 String JavaDoc msg = Util.THIS.getString("MSG_too_many_errs");
367                 sendMessage(msg);
368                 throw ex; // stop the parser
369
} else {
370                 CookieMessage message = new CookieMessage(
371                     ex.getLocalizedMessage(),
372                     CookieMessage.ERROR_LEVEL,
373                     new DefaultXMLProcessorDetail(ex)
374                 );
375                 if (console != null) console.receive(message);
376             }
377         }
378
379         /**
380          * Log runtime exception cause
381          */

382         private void runtimeError (RuntimeException JavaDoc ex) {
383             Util.THIS.debug("Parser runtime exception", ex );
384
385             // probably an internal parser error
386
String JavaDoc msg = Util.THIS.getString("EX_parser_ierr", ex.getMessage());
387             fatalError(new SAXParseException (msg, SharedXMLSupport.this.locator, ex));
388         }
389         
390         public void fatalError (SAXParseException ex) {
391             if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug("Just diagnostic exception", ex); // NOI18N
392
fatalErrors++;
393             CookieMessage message = new CookieMessage(
394                 ex.getLocalizedMessage(),
395                 CookieMessage.FATAL_ERROR_LEVEL,
396                 new DefaultXMLProcessorDetail(ex)
397             );
398             if (console != null) console.receive(message);
399         }
400         
401         public void setDocumentLocator(Locator locator) {
402             SharedXMLSupport.this.locator = locator;
403         }
404
405         private int getMaxErrorCount() {
406             return 20; //??? load from option
407
}
408         
409     }
410
411
412     /**
413      * EntityResolver that reports unresolved entities.
414      */

415     private class VerboseEntityResolver implements EntityResolver {
416         
417         private final EntityResolver peer;
418         
419         public VerboseEntityResolver(EntityResolver res) {
420             if (res == null) throw new NullPointerException JavaDoc();
421             peer = res;
422         }
423         
424         public InputSource resolveEntity(String JavaDoc pid, String JavaDoc sid) throws SAXException, IOException {
425                                     
426             InputSource result = peer.resolveEntity(pid, sid);
427             
428             // null result may be suspicious, may be no Schema location found etc.
429

430             if (result == null) {
431                 bogusSchemaRequest = pid == null && sid == null;
432                 if (bogusSchemaRequest) return null;
433                 
434                 String JavaDoc warning;
435                 String JavaDoc pidLabel = pid != null ? pid : Util.THIS.getString("MSG_no_pid");
436                 try {
437                     String JavaDoc file = new URL(sid).getFile();
438                     if (file != null) {
439                         warning = Util.THIS.getString("MSG_resolver_1", pidLabel, sid);
440                     } else { // probably NS id
441
warning = Util.THIS.getString("MSG_resolver_2", pidLabel, sid);
442                     }
443                 } catch (MalformedURLException ex) {
444                     warning = Util.THIS.getString("MSG_resolver_3", pidLabel, sid);
445                 }
446                 sendMessage(warning);
447             }
448             return result;
449         }
450         
451     }
452     
453     private void sendMessage(String JavaDoc message) {
454         if (console != null) {
455             console.receive(new CookieMessage(message));
456         }
457     }
458
459     private String JavaDoc[] getSchemaLocations(InputSource is) {
460         EntityResolver res = createEntityResolver();
461         if (res==null) return null;
462         NsHandler nsHandler = getNamespaces(is);
463         String JavaDoc[] namespaces = nsHandler.getNamespaces();
464         List loc = new ArrayList();
465         for (int i=0;i<namespaces.length;i++) {
466             String JavaDoc ns = namespaces[i];
467             if (nsHandler.mapping.containsKey(ns)) {
468                 loc.add(ns + " " + nsHandler.mapping.get(ns)); //NOI18N
469
} else {
470                 try {
471                     javax.xml.transform.Source JavaDoc src = ((javax.xml.transform.URIResolver JavaDoc)res).resolve(ns, null);
472                     if (src!=null) loc.add(ns+" "+src.getSystemId()); //NOI18N
473
} catch (Exception JavaDoc ex) {}
474             }
475         }
476         String JavaDoc[] schemaLocations = new String JavaDoc[loc.size()];
477         loc.toArray(schemaLocations);
478         return schemaLocations;
479     }
480     
481     private NsHandler getNamespaces(InputSource is) {
482         NsHandler handler = new NsHandler();
483         try {
484             XMLReader xmlReader = org.openide.xml.XMLUtil.createXMLReader(false, true);
485             xmlReader.setContentHandler(handler);
486
487             // XXX dumb resolver always returning empty stream would be better but
488
// parsing could fail on resolving general entities defined in DTD.
489
// Check XML spec if non-validation parser must resolve general entities
490
// Ccc: I think so, there is Xerces property to relax it but we get here Crimson
491
UserCatalog userCatalog = UserCatalog.getDefault();
492             if (userCatalog != null) {
493                 EntityResolver resolver = userCatalog.getEntityResolver();
494                 if (resolver != null) {
495                     xmlReader.setEntityResolver(resolver);
496                 }
497             }
498             xmlReader.parse(is);
499         } catch (IOException ex) {
500             ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
501         } catch (SAXException ex) {
502             ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
503         }
504         return handler;
505     }
506     
507     private static class NsHandler extends org.xml.sax.helpers.DefaultHandler JavaDoc {
508         Set namespaces;
509         private Map mapping;
510
511         NsHandler() {
512             namespaces=new HashSet();
513             mapping = new HashMap();
514         }
515         
516         public void startElement(String JavaDoc uri, String JavaDoc localName, String JavaDoc rawName, Attributes atts) throws SAXException {
517             if (atts.getLength()>0) { //NOI18N
518
// parse XMLSchema location attribute
519
String JavaDoc locations = atts.getValue("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation"); // NOI18N
520
if (locations != null) {
521                     StringTokenizer tokenizer = new StringTokenizer(locations);
522                     if ((tokenizer.countTokens() % 2) == 0) {
523                         while (tokenizer.hasMoreElements()) {
524                             String JavaDoc nsURI = tokenizer.nextToken();
525                             String JavaDoc nsLocation = tokenizer.nextToken();
526                             mapping.put(nsURI, nsLocation);
527                         }
528                     }
529                 }
530             }
531         }
532
533         public void startPrefixMapping(String JavaDoc prefix, String JavaDoc uri) throws SAXException {
534             if ("http://www.w3.org/2001/XMLSchema-instance".equals(uri)) { // NOIi8N
535
return; // it's build in into parser
536
}
537             namespaces.add(uri);
538         }
539
540         String JavaDoc[] getNamespaces() {
541             String JavaDoc[] ns = new String JavaDoc[namespaces.size()];
542             namespaces.toArray(ns);
543             return ns;
544         }
545
546     }
547
548 }
549
Popular Tags