/*
* RHQ Management Platform
* Copyright (C) 2005-2011 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.sync;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPOutputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.configuration.definition.ConfigurationTemplate;
import org.rhq.core.domain.sync.ExporterMessages;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.enterprise.server.sync.exporters.Exporter;
import org.rhq.enterprise.server.sync.exporters.ExportingIterator;
import org.rhq.enterprise.server.sync.util.IndentingXMLStreamWriter;
import org.rhq.enterprise.server.sync.validators.ConsistencyValidator;
import org.rhq.enterprise.server.xmlschema.ConfigurationInstanceDescriptorUtil;
/**
* Reading from this input stream produces the export file in a lazy (and therefore memory efficient)
* manner.
*
* @author Lukas Krejci
*/
public class ExportingInputStream extends InputStream {
private static final Log LOG = LogFactory.getLog(ExportingInputStream.class);
private Set<Synchronizer<?, ?>> synchronizers;
private Map<String, ExporterMessages> messagesPerExporter;
private PipedInputStream inputStream;
private PipedOutputStream exportOutput;
private Thread exportRunner;
private Throwable unexpectedExporterException;
private boolean zipOutput;
private Marshaller configurationMarshaller;
/**
* Constructs a new exporting input stream with the default buffer size of 64KB that zips up
* the results.
*
* @see #ExportingInputStream(Set, Map, int, boolean)
*/
public ExportingInputStream(Set<Synchronizer<?, ?>> synchronizers, Map<String, ExporterMessages> messagesPerExporter)
throws IOException {
this(synchronizers, messagesPerExporter, 65536, true);
}
/**
* Constructs a new exporting input stream with the default buffer size of 64KB.
*
* @param synchronizers the synchronizers to invoke when producing the export file
* @param messagesPerExporter a reference to a map of messages that the exporters will use to produce additional info about the export
* @param size the size in bytes of the intermediate buffer
* @param zip whether to zip the export data
* @throws IOException on failure
*/
public ExportingInputStream(Set<Synchronizer<?, ?>> synchronizers,
Map<String, ExporterMessages> messagesPerExporter, int size, boolean zip) throws IOException {
this.synchronizers = synchronizers;
this.messagesPerExporter = messagesPerExporter;
inputStream = new PipedInputStream(size);
exportOutput = new PipedOutputStream(inputStream);
zipOutput = zip;
try {
JAXBContext context = JAXBContext.newInstance(DefaultImportConfigurationDescriptor.class);
configurationMarshaller = context.createMarshaller();
configurationMarshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
configurationMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
configurationMarshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
} catch (JAXBException e) {
throw new IOException(e);
}
}
@Override
public int read() throws IOException {
checkState();
return inputStream.read();
}
@Override
public int read(byte[] b) throws IOException {
checkState();
return inputStream.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
checkState();
return inputStream.read(b, off, len);
}
@Override
public synchronized void reset() throws IOException {
inputStream.reset();
}
@Override
public long skip(long n) throws IOException {
checkState();
return inputStream.skip(n);
}
@Override
public int available() throws IOException {
//the lack of checkState() is intentional here
//because the return value is only dependent
//on the PipedInputStream and no interaction
//with the PipedOutputStream is necessary
return inputStream.available();
}
@Override
public void close() throws IOException {
exportRunner.interrupt();
inputStream.close();
exportOutput.close();
}
@Override
public synchronized void mark(int readlimit) {
inputStream.mark(readlimit);
}
@Override
public boolean markSupported() {
return inputStream.markSupported();
}
private void checkState() throws IOException {
if (exportRunner == null) {
exportRunner = new Thread(new Runnable() {
public void run() {
exporterMain();
}
});
exportRunner.setDaemon(true);
exportRunner.setName("Configuration Export Thread");
exportRunner.setContextClassLoader(Thread.currentThread().getContextClassLoader());
exportRunner.start();
}
if (unexpectedExporterException != null) {
throw new IOException("The exporter thread failed with an uncaught exception.", unexpectedExporterException);
}
}
private void exporterMain() {
XMLStreamWriter wrt = null;
OutputStream out = null;
try {
XMLOutputFactory ofactory = XMLOutputFactory.newInstance();
ofactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
try {
out = exportOutput;
if (zipOutput) {
out = new GZIPOutputStream(out);
}
wrt = new IndentingXMLStreamWriter(ofactory.createXMLStreamWriter(out, "UTF-8"));
//wrt = ofactory.createXMLStreamWriter(out, "UTF-8");
} catch (XMLStreamException e) {
LOG.error("Failed to create the XML stream writer to output the export file to.", e);
return;
}
exportPrologue(wrt);
for (Synchronizer<?, ?> exp : synchronizers) {
exportSingle(wrt, exp);
}
exportEpilogue(wrt);
wrt.flush();
} catch (Exception e) {
LOG.error("Error while exporting.", e);
unexpectedExporterException = e;
} finally {
if (wrt != null) {
try {
wrt.close();
} catch (XMLStreamException e) {
LOG.warn("Failed to close the exporter XML stream.", e);
}
}
safeClose(out);
}
}
/**
* @param wrt
* @throws XMLStreamException
*/
private void exportPrologue(XMLStreamWriter wrt) throws XMLStreamException {
wrt.setDefaultNamespace(SynchronizationConstants.EXPORT_NAMESPACE);
wrt.writeStartDocument();
wrt.writeStartElement(SynchronizationConstants.EXPORT_NAMESPACE, SynchronizationConstants.CONFIGURATION_EXPORT_ELEMENT);
wrt.writeNamespace(SynchronizationConstants.CONFIGURATION_INSTANCE_NAMESPACE_PREFIX,
ConfigurationInstanceDescriptorUtil.NS_CONFIGURATION_INSTANCE);
wrt.writeNamespace(SynchronizationConstants.CONFIGURATION_NAMESPACE_PREFIX,
SynchronizationConstants.CONFIGURATION_NAMESPACE);
writeValidators(wrt);
}
/**
* @param wrt
*/
private void writeValidators(XMLStreamWriter wrt) throws XMLStreamException {
Set<ConsistencyValidator> allValidators = new HashSet<ConsistencyValidator>();
for (Synchronizer<?, ?> syn : synchronizers) {
allValidators.addAll(syn.getRequiredValidators());
}
for (ConsistencyValidator cv : allValidators) {
wrt.writeStartElement(SynchronizationConstants.VALIDATOR_ELEMENT);
wrt.writeAttribute(SynchronizationConstants.CLASS_ATTRIBUTE, cv.getClass().getName());
cv.exportState(new ExportWriter(wrt));
wrt.writeEndElement();
}
}
/**
* @param wrt
* @throws XMLStreamException
*/
private void exportEpilogue(XMLStreamWriter wrt) throws XMLStreamException {
wrt.writeEndDocument();
}
/**
* @param wrt
* @param syn
* @return
* @throws XMLStreamException
*/
private void exportSingle(XMLStreamWriter wrt, Synchronizer<?, ?> syn) throws XMLStreamException {
ExporterMessages messages = new ExporterMessages();
messagesPerExporter.put(syn.getClass().getName(), messages);
wrt.writeStartElement(SynchronizationConstants.EXPORT_NAMESPACE, SynchronizationConstants.ENTITIES_EXPORT_ELEMENT);
wrt.writeAttribute(SynchronizationConstants.ID_ATTRIBUTE, syn.getClass().getName());
Exporter<?, ?> exp = syn.getExporter();
ExportingIterator<?> it = exp.getExportingIterator();
DefaultImportConfigurationDescriptor importConfig = getDefaultImportConfiguraton(syn);
if (importConfig != null) {
try {
configurationMarshaller.marshal(importConfig, wrt);
} catch (JAXBException e) {
throw new XMLStreamException(e);
}
}
messages.setPerEntityErrorMessages(new ArrayList<String>());
messages.setPerEntityNotes(new ArrayList<String>());
while (it.hasNext()) {
it.next();
wrt.writeStartElement(SynchronizationConstants.EXPORT_NAMESPACE, SynchronizationConstants.ENTITY_EXPORT_ELEMENT);
wrt.writeStartElement(SynchronizationConstants.EXPORT_NAMESPACE, SynchronizationConstants.DATA_ELEMENT);
Exception exportError = null;
try {
it.export(new ExportWriter(wrt));
} catch (XMLStreamException e) {
//there's not much we can do about these but to give up.
throw e;
} catch (Exception e) {
exportError = e;
}
wrt.writeEndElement(); //data
if (exportError == null) {
String notes = it.getNotes();
if (notes != null) {
messages.getPerEntityNotes().add(notes);
wrt.writeStartElement(SynchronizationConstants.EXPORT_NAMESPACE, SynchronizationConstants.NOTES_ELEMENT);
wrt.writeCharacters(notes);
wrt.writeEndElement();
}
} else {
String message = ThrowableUtil.getStackAsString(exportError);
messages.getPerEntityErrorMessages().add(message);
wrt.writeStartElement(SynchronizationConstants.EXPORT_NAMESPACE, SynchronizationConstants.ERROR_MESSAGE_ELEMENT);
wrt.writeCharacters(message);
wrt.writeEndElement();
}
wrt.writeEndElement(); //entity
}
String notes = exp.getNotes();
messages.setExporterNotes(notes);
if (notes != null) {
wrt.writeStartElement(SynchronizationConstants.EXPORT_NAMESPACE, SynchronizationConstants.NOTES_ELEMENT);
wrt.writeCharacters(notes);
wrt.writeEndElement();
}
wrt.writeEndElement(); //entities
}
private static void safeClose(Closeable str) {
try {
if (str != null) {
str.close();
}
} catch (IOException e) {
LOG.error("Failed to close an output stream. This shouldn't happen.", e);
}
}
private DefaultImportConfigurationDescriptor getDefaultImportConfiguraton(Synchronizer<?, ?> synchronizer) {
DefaultImportConfigurationDescriptor ret = null;
ConfigurationDefinition def = synchronizer.getImporter().getImportConfigurationDefinition();
if (def != null) {
ConfigurationTemplate template = def.getDefaultTemplate();
if (template != null) {
ret = DefaultImportConfigurationDescriptor.create(ConfigurationInstanceDescriptorUtil
.createConfigurationInstance(def, template.getConfiguration()));
}
}
return ret;
}
}