/******************************************************************************* * Copyright 2012 André Rouél * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package net.sf.uadetector.datareader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.charset.Charset; import javax.annotation.Nonnull; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import net.sf.qualitycheck.Check; import net.sf.uadetector.exception.CanNotOpenStreamException; import net.sf.uadetector.internal.data.Data; import net.sf.uadetector.internal.data.DataBuilder; import net.sf.uadetector.internal.data.XmlDataHandler; import net.sf.uadetector.internal.util.Closeables; import net.sf.uadetector.internal.util.UrlUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; /** * Reader for the XML data for UASparser from <a * href="http://user-agent-string.info/">http://user-agent-string.info</a>.<br> * <br> * This reader is safe when used concurrently by multiple threads. * * @author André Rouél */ public final class XmlDataReader implements DataReader { protected static final class XmlParser { private static final String MSG_NOT_PARSED_AS_EXPECTED = "The UAS data has not been parsed as expected."; public static void parse(@Nonnull final InputStream stream, @Nonnull final DataBuilder builder) throws ParserConfigurationException, SAXException, IOException { final SAXParserFactory factory = SAXParserFactory.newInstance(); // factory.setValidating(true); final SAXParser parser = factory.newSAXParser(); final XmlDataHandler handler = new XmlDataHandler(builder); parser.parse(stream, handler); validate(handler); } protected static void validate(@Nonnull final XmlDataHandler handler) { if (handler.hasError()) { throw new IllegalStateException(MSG_NOT_PARSED_AS_EXPECTED); } } private XmlParser() { // This class is not intended to create objects from it. } } /** * Default character set to read UAS data */ private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); /** * Corresponding default logger for this class */ private static final Logger LOG = LoggerFactory.getLogger(XmlDataReader.class); /** * Reads the <em>UAS data</em> in XML format based on the given URL.<br> * <br> * When during the reading errors occur which lead to a termination of the read operation, the information will be * written to a log. The termination of the read operation will not lead to a program termination and in this case * this method returns {@link Data#EMPTY}. * * @param inputStream * an input stream for reading <em>UAS data</em> * @param charset * the character set in which the data should be read * @return read in <em>UAS data</em> as {@code Data} instance * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException * if any of the given arguments is {@code null} * @throws net.sf.uadetector.exception.CanNotOpenStreamException * if no stream to the given {@code URL} can be established */ protected static Data readXml(@Nonnull final InputStream inputStream, @Nonnull final Charset charset) { Check.notNull(inputStream, "inputStream"); Check.notNull(charset, "charset"); final DataBuilder builder = new DataBuilder(); boolean hasErrors = false; try { XmlParser.parse(inputStream, builder); } catch (final ParserConfigurationException e) { hasErrors = true; LOG.warn(e.getLocalizedMessage()); } catch (final SAXException e) { hasErrors = true; LOG.warn(e.getLocalizedMessage()); } catch (final IOException e) { hasErrors = true; LOG.warn(e.getLocalizedMessage()); } catch (final IllegalStateException e) { hasErrors = true; LOG.warn(e.getLocalizedMessage()); } catch (final Exception e) { hasErrors = true; LOG.warn(e.getLocalizedMessage(), e); } finally { Closeables.closeAndConvert(inputStream, true); } return hasErrors ? Data.EMPTY : builder.build(); } /** * Reads the <em>UAS data</em> in XML format from the given string. * * @param data * <em>UAS data</em> as string * @return read in User-Agent data as {@code Data} instance otherwise {@link Data#EMPTY} * * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException * if any of the given argument is {@code null} */ @Override public Data read(@Nonnull final String data) { Check.notNull(data, "data"); return readXml(new ByteArrayInputStream(data.getBytes(DEFAULT_CHARSET)), DEFAULT_CHARSET); } /** * Reads the <em>UAS data</em> in XML format based on the given URL. * * @param url * {@code URL} to User-Agent informations * @param charset * the character set in which the data should be read * @return read in User-Agent data as {@code Data} instance otherwise {@link Data#EMPTY} * * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException * if any of the given arguments is {@code null} */ @Override public Data read(@Nonnull final URL url, @Nonnull final Charset charset) { Check.notNull(url, "url"); Check.notNull(charset, "charset"); Data data = Data.EMPTY; try { data = readXml(UrlUtil.open(url), charset); } catch (final CanNotOpenStreamException e) { LOG.warn(e.getLocalizedMessage()); } return data; } }