/* ********************************************************************** **
** Copyright notice **
** **
** (c) 2005-2009 RSSOwl Development Team **
** http://www.rssowl.org/ **
** **
** All rights reserved **
** **
** This program and the accompanying materials are made available under **
** the terms of the Eclipse Public License v1.0 which accompanies this **
** distribution, and is available at: **
** http://www.rssowl.org/legal/epl-v10.html **
** **
** A copy is found in the file epl-v10.html and important notices to the **
** license from the team is found in the textfile LICENSE.txt distributed **
** in this package. **
** **
** This copyright notice MUST APPEAR in all copies of the file! **
** **
** Contributors: **
** RSSOwl Development Team - initial API and implementation **
** **
** ********************************************************************** */
package org.rssowl.core.internal.interpreter;
import org.jdom.Document;
import org.jdom.JDOMException;
import org.jdom.input.JDOMParseException;
import org.jdom.input.SAXBuilder;
import org.rssowl.core.connection.IAbortable;
import org.rssowl.core.internal.Activator;
import org.rssowl.core.internal.connection.DefaultProtocolHandler;
import org.rssowl.core.interpreter.EncodingException;
import org.rssowl.core.interpreter.IXMLParser;
import org.rssowl.core.interpreter.ParserException;
import org.rssowl.core.util.StringUtils;
import org.xml.sax.InputSource;
import org.xml.sax.ext.EntityResolver2;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Map;
/**
* Default Implementation of the ISAXParser Interface using the JDKs default XML
* Parser. The implementation is smart enough to try the platforms default
* encoding in case a first attempt of parsing the XML fails.
*
* @author bpasero
*/
public class DefaultSaxParserImpl implements IXMLParser {
/* Feature in Xerces to allow Java Encoding Names */
private static final String ALLOW_JAVA_ENCODINGS = "http://apache.org/xml/features/allow-java-encodings"; //$NON-NLS-1$
/* DTD to use for all XMLs */
private static final String DEFAULT_DTD = "entities.dtd"; //$NON-NLS-1$
/* A Stream that overrides close() to do nothing */
private static class KeepAliveInputStream extends BufferedInputStream {
KeepAliveInputStream(InputStream in) {
super(in);
}
@Override
public void close() {
// Disable, because Xerces is automatically closing the Stream
}
void reallyClose() throws IOException {
super.close();
}
}
/*
* @see org.rssowl.core.interpreter.ISAXParser#init()
*/
public void init() {
/* Nothing to do here */
}
/*
* @see org.rssowl.core.interpreter.IXMLParser#parse(java.io.InputStream,
* java.util.Map)
*/
public Document parse(InputStream inS, Map<Object, Object> properties) throws ParserException {
Document document = null;
Exception ex = null;
SAXBuilder builder = getBuilder();
boolean encodingIssue = false;
boolean usePlatformEncoding = (properties != null && properties.containsKey(DefaultProtocolHandler.USE_PLATFORM_ENCODING));
/* Set a Mark to support a 2d Run */
KeepAliveInputStream keepAliveIns = new KeepAliveInputStream(inS);
keepAliveIns.mark(0);
/* First Run */
try {
if (!usePlatformEncoding)
document = builder.build(keepAliveIns);
else
document = builder.build(new InputStreamReader(keepAliveIns));
} catch (JDOMException e) {
ex = e;
} catch (IOException e) {
ex = e;
}
/* Second Run - Try with Platform Default Encoding from existing Stream */
if (!usePlatformEncoding && isEncodingIssue(ex)) {
encodingIssue = true;
/* Try to reset the Stream to 0 */
boolean reset = false;
try {
keepAliveIns.reset();
reset = true;
} catch (IOException e) {
/* Reset Failed, do not override previous exception */
}
/* In case reset-operation was successfull */
if (reset) {
try {
document = builder.build(new InputStreamReader(keepAliveIns));
} catch (JDOMException e) {
ex = e;
} catch (IOException e) {
ex = e;
}
}
}
/* Close Stream */
try {
if (ex != null && inS instanceof IAbortable)
((IAbortable) inS).abort();
else
keepAliveIns.reallyClose();
} catch (IOException e) {
ex = e;
}
/* In case of an exception */
if (ex != null && document == null && Activator.getDefault() != null) {
if (!usePlatformEncoding && encodingIssue)
throw new EncodingException(Activator.getDefault().createErrorStatus(ex.getMessage(), ex));
throw new ParserException(Activator.getDefault().createErrorStatus(ex.getMessage(), ex));
}
/* Return Document */
return document;
}
private boolean isEncodingIssue(Exception ex) {
if (ex == null)
return false;
if (ex instanceof JDOMParseException || ex instanceof UnsupportedEncodingException)
return true;
String name = ex.getClass().getName();
return (StringUtils.isSet(name) && name.contains("MalformedByteSequenceException")); //$NON-NLS-1$
}
private SAXBuilder getBuilder() {
SAXBuilder builder = new SAXBuilder();
/* Support Java Encoding Names */
builder.setFeature(ALLOW_JAVA_ENCODINGS, true);
/* Custom Entitiy Resolution */
builder.setEntityResolver(new EntityResolver2() {
/*
* @see
* org.xml.sax.ext.EntityResolver2#getExternalSubset(java.lang.String,
* java.lang.String)
*/
public InputSource getExternalSubset(String name, String baseURI) {
return new InputSource(getClass().getResourceAsStream(DEFAULT_DTD));
}
/*
* @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String,
* java.lang.String)
*/
public InputSource resolveEntity(String publicId, String systemId) {
return resolveEntity(null, publicId, null, systemId);
}
/*
* @see org.xml.sax.ext.EntityResolver2#resolveEntity(java.lang.String,
* java.lang.String, java.lang.String, java.lang.String)
*/
public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) {
return new InputSource(getClass().getResourceAsStream(DEFAULT_DTD));
}
});
return builder;
}
}