001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.api.xml.parsers;
043:
044: import java.io.*;
045: import java.net.MalformedURLException;
046: import java.net.URL;
047: import org.openide.ErrorManager;
048:
049: import org.xml.sax.*;
050:
051: /**
052: * SAX parser wrapper allowing to parse parsed XML entities (including DTDs) for
053: * wellformedness.
054: * <p>
055: * Default implementation cannot be used for parsing of XML document entities!
056: * It wraps client's parser that it actually used for performing the parsing task.
057: * <p>
058: * <b>Primary use case (parse general entity):</b>
059: * <pre>
060: * XMLReader entityParser = new SAXEntityParser(xmlReader);
061: * entityParser.setErrorHandler(errorHandler);
062: * entityParser.parse(inputSource);
063: * </pre>
064: * <b>Secondary use case (delegating parser):</b> It requires subclassing and
065: * allow subclass entirely define internal wrapping logic.
066: *
067: * <b>Warning:</b> Implementation gurantees only proper ErrorHandler callbacks.
068: *
069: * @author Petr Kuzel
070: */
071: public class SAXEntityParser implements XMLReader {
072:
073: //??? we are not fully bullet proof
074: private static final long RANDOM = System.currentTimeMillis();
075:
076: private static final String FAKE_SYSTEM_ID = "NetBeans:Fake-System-ID-"
077: + RANDOM; // NOI18N
078:
079: private static final String FAKE_PUBLIC_ID = "-//NetBeans//Fake Public ID "
080: + RANDOM + "//EN"; // NOI18N
081:
082: // we delegate almost everything on it
083: private final XMLReader peer;
084:
085: // defines wrapping logic
086: private final boolean generalEntity;
087:
088: // was client parser already used
089: private boolean used = false;
090:
091: /**
092: * Creates a new instance of general entity parser.
093: * @param peer parser that will be used for parsing. Wrapped parser is
094: * exclusively owned by this class no other clients can share it.
095: */
096: public SAXEntityParser(XMLReader peer) {
097: this (peer, true);
098: }
099:
100: /**
101: * Creates a new instance of SAXEntityParser.
102: * @param peer parser that will be used for parsing
103: * @param generalEntity if <code>false</code> treat entity as parameter
104: * entity (i.e. DTD entities).
105: */
106: public SAXEntityParser(XMLReader peer, boolean generalEntity) {
107: if (peer == null)
108: throw new NullPointerException();
109: this .peer = peer;
110: this .generalEntity = generalEntity;
111: }
112:
113: /**
114: * Start entity parsing using peer parser. Staring from this moment
115: * all other methods calls are not supported.
116: * @param entity entity input source
117: */
118: public void parse(InputSource entity) throws IOException,
119: SAXException {
120:
121: if (entity == null)
122: throw new NullPointerException();
123:
124: synchronized (this ) {
125: checkUsed();
126: used = true;
127: }
128:
129: // log warning for common errors
130:
131: String originalSID = entity.getSystemId();
132: if (originalSID == null) {
133: ErrorManager err = Util.THIS.getErrorManager();
134: if (err.isLoggable(err.WARNING)) {
135: StringWriter writer = new StringWriter();
136: PrintWriter out = new PrintWriter(writer);
137: new IllegalArgumentException(
138: "WARNING: Missing system ID may cause serious errors while resolving relative references!")
139: .printStackTrace(out); // NOI18N
140: out.flush();
141: err.log(err.WARNING, writer.getBuffer().toString());
142: }
143: }
144:
145: // provide fake entity resolver and input source
146:
147: EntityResolver resolver = peer.getEntityResolver();
148: peer.setEntityResolver(new ER(resolver, entity));
149:
150: ErrorHandler errorHandler = peer.getErrorHandler();
151: if (errorHandler != null) {
152: peer.setErrorHandler(new EH(errorHandler));
153: }
154:
155: InputSource fakeInput = wrapInputSource(entity);
156: if (fakeInput.getSystemId() == null) {
157: fakeInput.setSystemId(originalSID);
158: }
159: if (fakeInput.getPublicId() == null) {
160: fakeInput.setPublicId(FAKE_PUBLIC_ID);
161: }
162: peer.parse(fakeInput);
163:
164: }
165:
166: /**
167: * Create wrapper input source. Default implementation utilizes fact that
168: * default <code>EntityResolver</code> redirects the first query to wrapped
169: * <code>InputSource</code>.
170: * @param input InputSource to be wrapped.
171: * @return InputSource that hosts of client's one
172: * @since 0.6
173: */
174: protected InputSource wrapInputSource(InputSource input) {
175: String sid = input.getSystemId();
176: InputSource fakeInput = new InputSource(FAKE_SYSTEM_ID);
177: String fakeDocument;
178: if (generalEntity) {
179: StringBuffer buffer = new StringBuffer();
180: buffer.append("<!DOCTYPE fakeDocument" + RANDOM + " [\n"); // NOI18N
181: String entityRef = " PUBLIC '" + FAKE_PUBLIC_ID + "' '"
182: + sid + "'";// NOI18N
183: buffer.append("<!ENTITY fakeEntity" + RANDOM + entityRef
184: + ">\n"); // NOI18N
185: buffer.append("]>\n"); // NOI18N
186: buffer.append("<fakeDocument" + RANDOM + ">\n"); // NOI18N
187: buffer.append("&fakeEntity" + RANDOM + ";\n"); // NOI18N
188: buffer.append("</fakeDocument" + RANDOM + ">\n"); // NOI18N
189: fakeDocument = buffer.toString();
190: } else {
191: StringBuffer buffer = new StringBuffer();
192: String extRef = " PUBLIC '" + FAKE_PUBLIC_ID + "' '" + sid
193: + "'"; // NOI18N
194: buffer.append("<!DOCTYPE fakeDocument" + RANDOM + extRef
195: + ">\n"); // NOI18N
196: buffer.append("<fakeDocument" + RANDOM + "/>\n"); // NOI18N
197: fakeDocument = buffer.toString();
198: }
199: fakeInput.setCharacterStream(new StringReader(fakeDocument));
200: return fakeInput;
201: }
202:
203: /**
204: * Examine if the exception should be propagated into client's <code>ErrorHandler</code>.
205: * @param ex examined exception
206: * @return <code>true</code> if the exception originates from client's
207: * <code>InputSource</code> and should be propagated.
208: */
209: protected boolean propagateException(SAXParseException ex) {
210: if (ex == null)
211: return false;
212: return (FAKE_SYSTEM_ID.equals(ex.getSystemId()) == false);
213: }
214:
215: public org.xml.sax.ContentHandler getContentHandler() {
216: return peer.getContentHandler();
217: }
218:
219: public org.xml.sax.DTDHandler getDTDHandler() {
220: return peer.getDTDHandler();
221: }
222:
223: public org.xml.sax.EntityResolver getEntityResolver() {
224: return peer.getEntityResolver();
225: }
226:
227: public org.xml.sax.ErrorHandler getErrorHandler() {
228: return peer.getErrorHandler();
229: }
230:
231: public boolean getFeature(String name)
232: throws org.xml.sax.SAXNotRecognizedException,
233: org.xml.sax.SAXNotSupportedException {
234: return peer.getFeature(name);
235: }
236:
237: public Object getProperty(String name)
238: throws org.xml.sax.SAXNotRecognizedException,
239: org.xml.sax.SAXNotSupportedException {
240: return peer.getProperty(name);
241: }
242:
243: public void parse(String sid) throws java.io.IOException,
244: org.xml.sax.SAXException {
245: this .parse(new InputSource(sid));
246: }
247:
248: public void setContentHandler(
249: org.xml.sax.ContentHandler contentHandler) {
250: peer.setContentHandler(contentHandler);
251: }
252:
253: public void setDTDHandler(org.xml.sax.DTDHandler dTDHandler) {
254: peer.setDTDHandler(dTDHandler);
255: }
256:
257: public void setEntityResolver(
258: org.xml.sax.EntityResolver entityResolver) {
259: peer.setEntityResolver(entityResolver);
260: }
261:
262: public void setErrorHandler(org.xml.sax.ErrorHandler errorHandler) {
263: peer.setErrorHandler(errorHandler);
264: }
265:
266: public void setFeature(String name, boolean val)
267: throws org.xml.sax.SAXNotRecognizedException,
268: org.xml.sax.SAXNotSupportedException {
269: peer.setFeature(name, val);
270: }
271:
272: public void setProperty(String name, Object val)
273: throws org.xml.sax.SAXNotRecognizedException,
274: org.xml.sax.SAXNotSupportedException {
275: peer.setProperty(name, val);
276: }
277:
278: private synchronized void checkUsed() {
279: if (used == true)
280: throw new IllegalStateException();
281: }
282:
283: /**
284: * Redirect to entity input source, it is always the first request.
285: * Pure FAKE_SYSTEM_ID approach have problems with some parser implementations.
286: */
287: private class ER implements EntityResolver {
288:
289: private boolean entityResolved;
290: private final EntityResolver peer;
291: private final InputSource entity;
292:
293: public ER(EntityResolver peer, InputSource entity) {
294: this .peer = peer;
295: this .entity = entity;
296: }
297:
298: public InputSource resolveEntity(String pid, String sid)
299: throws SAXException, IOException {
300:
301: Util.THIS.debug("SAXEntityParser:resolving PID: " + pid
302: + " SID: " + sid);
303:
304: if (isFirstRequest()) {
305:
306: // normalize passed entity InputSource using parent entity resolver
307: Util.THIS.debug("SAXEntityParser:redirecting to "
308: + entity + " SID: " + entity.getSystemId());
309:
310: if (peer != null && entity.getByteStream() == null
311: && entity.getCharacterStream() == null) {
312: return peer.resolveEntity(entity.getPublicId(),
313: entity.getSystemId());
314: } else {
315: return entity;
316: }
317: } else {
318: if (peer == null) {
319: return null;
320: } else {
321: return peer.resolveEntity(pid, sid);
322: }
323: }
324: }
325:
326: private synchronized boolean isFirstRequest() {
327: if (entityResolved == false) {
328: entityResolved = true;
329: return true;
330: } else {
331: return false;
332: }
333: }
334: }
335:
336: /**
337: * Filter out errors in our fake document
338: */
339: private class EH implements ErrorHandler {
340:
341: private final ErrorHandler peer;
342:
343: public EH(ErrorHandler peer) {
344: this .peer = peer;
345: }
346:
347: public void error(SAXParseException ex) throws SAXException {
348: if (propagateException(ex)) {
349: peer.error(ex);
350: } else {
351: Util.THIS.debug("SAXEntityParser: filtering out:", ex);
352: }
353: }
354:
355: public void fatalError(SAXParseException ex)
356: throws SAXException {
357: if (propagateException(ex)) {
358: peer.fatalError(ex);
359: } else {
360: Util.THIS.debug("SAXEntityParser: filtering out:", ex);
361: }
362: }
363:
364: public void warning(SAXParseException ex) throws SAXException {
365: if (propagateException(ex)) {
366: peer.warning(ex);
367: } else {
368: Util.THIS.debug("SAXEntityParser: filtering out:", ex);
369: }
370: }
371: }
372:
373: }
|