/*
* Copyright (C) 2010 Teleal GmbH, Switzerland
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.teleal.cling.support.lastchange;
import org.teleal.cling.model.XMLUtil;
import org.teleal.cling.model.types.UnsignedIntegerFourBytes;
import org.teleal.cling.support.shared.AbstractMap;
import org.teleal.common.io.IO;
import org.teleal.common.util.Exceptions;
import org.teleal.common.xml.SAXParser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.teleal.cling.model.XMLUtil.appendNewElement;
/**
* Reads and writes the "LastChange" XML content.
* <p>
* Validates against a schema if the {@link #getSchemaSources()} method
* doesn't return <code>null</code>.
* </p>
*
* @author Christian Bauer
*/
public abstract class LastChangeParser extends SAXParser {
final private static Logger log = Logger.getLogger(LastChangeParser.class.getName());
public enum CONSTANTS {
Event,
InstanceID,
val;
public boolean equals(String s) {
return this.name().equals(s);
}
}
abstract protected String getNamespace();
protected Set<Class<? extends EventedValue>> getEventedVariables() {
return Collections.EMPTY_SET;
}
protected EventedValue createValue(String name, Map.Entry<String, String>[] attributes) throws Exception {
for (Class<? extends EventedValue> evType : getEventedVariables()) {
if (evType.getSimpleName().equals(name)) {
Constructor<? extends EventedValue> ctor = evType.getConstructor(Map.Entry[].class);
return ctor.newInstance(new Object[]{attributes});
}
}
return null;
}
/**
* Uses the current thread's context classloader to read and unmarshall the given resource.
*
* @param resource The resource on the classpath.
* @return The unmarshalled Event model.
* @throws Exception
*/
public Event parseResource(String resource) throws Exception {
InputStream is = null;
try {
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
return parse(IO.readLines(is));
} finally {
if (is != null) is.close();
}
}
public Event parse(String xml) throws Exception {
if (xml == null || xml.length() == 0) {
throw new RuntimeException("Null or empty XML");
}
Event event = new Event();
new RootHandler(event, this);
log.fine("Parsing 'LastChange' event XML content");
parse(new InputSource(new StringReader(xml)));
log.fine("Parsed event with instances IDs: " + event.getInstanceIDs().size());
if (log.isLoggable(Level.FINEST)) {
for (InstanceID instanceID : event.getInstanceIDs()) {
log.finest("InstanceID '" + instanceID.getId() + "' has values: " + instanceID.getValues().size());
for (EventedValue eventedValue : instanceID.getValues()) {
log.finest(eventedValue.getName() + " => " + eventedValue.getValue());
}
}
}
return event;
}
class RootHandler extends SAXParser.Handler<Event> {
RootHandler(Event instance, SAXParser parser) {
super(instance, parser);
}
RootHandler(Event instance) {
super(instance);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
if (CONSTANTS.InstanceID.equals(localName)) {
String valAttr = attributes.getValue(CONSTANTS.val.name());
if (valAttr != null) {
InstanceID instanceID = new InstanceID(new UnsignedIntegerFourBytes(valAttr));
getInstance().getInstanceIDs().add(instanceID);
new InstanceIDHandler(instanceID, this);
}
}
}
}
class InstanceIDHandler extends SAXParser.Handler<InstanceID> {
InstanceIDHandler(InstanceID instance, SAXParser.Handler parent) {
super(instance, parent);
}
@Override
public void startElement(String uri, String localName, String qName, final Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
Map.Entry[] attributeMap = new Map.Entry[attributes.getLength()];
for (int i = 0; i < attributeMap.length; i++) {
attributeMap[i] =
new AbstractMap.SimpleEntry<String, String>(
attributes.getLocalName(i),
attributes.getValue(i)
);
}
try {
EventedValue esv = createValue(localName, attributeMap);
if (esv != null)
getInstance().getValues().add(esv);
} catch (Exception ex) {
// Don't exit, just log a warning
log.warning("Error reading event XML, ignoring value: " + Exceptions.unwrap(ex));
}
}
@Override
protected boolean isLastElement(String uri, String localName, String qName) {
return CONSTANTS.InstanceID.equals(localName);
}
}
public String generate(Event event) throws Exception {
return XMLUtil.documentToFragmentString(buildDOM(event));
}
protected Document buildDOM(Event event) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
Document d = factory.newDocumentBuilder().newDocument();
generateRoot(event, d);
return d;
}
protected void generateRoot(Event event, Document descriptor) {
Element eventElement = descriptor.createElementNS(getNamespace(), CONSTANTS.Event.name());
descriptor.appendChild(eventElement);
generateInstanceIDs(event, descriptor, eventElement);
}
protected void generateInstanceIDs(Event event, Document descriptor, Element rootElement) {
for (InstanceID instanceID : event.getInstanceIDs()) {
if (instanceID.getId() == null) continue;
Element instanceIDElement = appendNewElement(descriptor, rootElement, CONSTANTS.InstanceID.name());
instanceIDElement.setAttribute(CONSTANTS.val.name(), instanceID.getId().toString());
for (EventedValue eventedValue : instanceID.getValues()) {
generateEventedValue(eventedValue, descriptor, instanceIDElement);
}
}
}
protected void generateEventedValue(EventedValue eventedValue, Document descriptor, Element parentElement) {
String name = eventedValue.getName();
Map.Entry<String, String>[] attributes = eventedValue.getAttributes();
if (attributes != null && attributes.length > 0) {
Element evElement = appendNewElement(descriptor, parentElement, name);
for (Map.Entry<String, String> attr : attributes) {
evElement.setAttribute(attr.getKey(), attr.getValue());
}
}
}
}