/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 li.strolch.command;
import static li.strolch.utils.helper.StringHelper.UNDERLINE;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import javanet.staxutils.IndentingXMLStreamWriter;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import li.strolch.agent.api.ActivityMap;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.OrderMap;
import li.strolch.agent.api.ResourceMap;
import li.strolch.exception.StrolchException;
import li.strolch.model.ActivityVisitor;
import li.strolch.model.ModelStatistics;
import li.strolch.model.Order;
import li.strolch.model.OrderVisitor;
import li.strolch.model.Resource;
import li.strolch.model.ResourceVisitor;
import li.strolch.model.Tags;
import li.strolch.model.activity.Activity;
import li.strolch.model.xml.ActivityToSaxWriterVisitor;
import li.strolch.model.xml.OrderToSaxWriterVisitor;
import li.strolch.model.xml.ResourceToSaxWriterVisitor;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.runtime.StrolchConstants;
import li.strolch.service.api.Command;
import li.strolch.utils.dbc.DBC;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public class XmlExportModelCommand extends Command {
public static final String XML_FILE_SUFFIX = ".xml";
private static final long LOG_INTERVAL = TimeUnit.SECONDS.toMillis(10);
// input
private File modelFile;
private boolean multiFile;
private boolean overwrite;
private boolean doOrders;
private boolean doResources;
private boolean doActivities;
private Set<String> orderTypes;
private Set<String> resourceTypes;
private Set<String> activityTypes;
// output
private ModelStatistics statistics;
private int elementsToWrite;
private int nrOfResourcesToExport;
private int nrOfOrdersToExport;
private int nrOfActivitiesToExport;
private long nextLogTime;
public XmlExportModelCommand(ComponentContainer container, StrolchTransaction tx) {
super(container, tx);
}
@Override
public void validate() {
if (!this.overwrite)
DBC.PRE.assertNotExists("Model may not already exist!", this.modelFile);
DBC.PRE.assertTrue("Model file must end with .xml", this.modelFile.getName().endsWith(XML_FILE_SUFFIX));
}
private void cleanUpExisting(final String exportName) {
File parentFile = this.modelFile.getParentFile();
File[] existingFiles = parentFile.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(exportName);
}
});
for (File file : existingFiles) {
file.delete();
}
}
@Override
public void doCommand() {
this.nextLogTime = System.currentTimeMillis() + LOG_INTERVAL;
String fileName = this.modelFile.getName();
long start = System.nanoTime();
this.statistics = new ModelStatistics();
this.statistics.startTime = new Date();
String exportName = fileName.substring(0, fileName.indexOf(XML_FILE_SUFFIX));
cleanUpExisting(exportName);
Set<File> createdFiles = new HashSet<>();
if (this.doResources) {
ResourceMap resourceMap = tx().getResourceMap();
Set<String> resourceTypesToExport = resourceMap.getTypes(tx());
if (!this.resourceTypes.isEmpty())
resourceTypesToExport.retainAll(this.resourceTypes);
for (String type : resourceTypesToExport) {
this.nrOfResourcesToExport += resourceMap.querySize(tx(), type);
}
}
if (this.doOrders) {
OrderMap orderMap = tx().getOrderMap();
Set<String> orderTypesToExport = orderMap.getTypes(tx());
if (!this.orderTypes.isEmpty())
orderTypesToExport.retainAll(this.orderTypes);
for (String type : orderTypesToExport) {
this.nrOfOrdersToExport += orderMap.querySize(tx(), type);
}
}
if (this.doActivities) {
ActivityMap activityMap = tx().getActivityMap();
Set<String> activityTypesToExport = activityMap.getTypes(tx());
if (!this.activityTypes.isEmpty())
activityTypesToExport.retainAll(this.activityTypes);
for (String type : activityTypesToExport) {
this.nrOfActivitiesToExport += activityMap.querySize(tx(), type);
}
}
this.elementsToWrite = this.nrOfResourcesToExport + this.nrOfOrdersToExport + this.nrOfActivitiesToExport;
logger.info("Exporting " + this.elementsToWrite + " Elements...");
logger.info("Exporting " + this.nrOfResourcesToExport + " Resources...");
logger.info("Exporting " + this.nrOfOrdersToExport + " Orders...");
logger.info("Exporting " + this.nrOfActivitiesToExport + " Activities...");
try (FileOutputStream out = new FileOutputStream(this.modelFile)) {
createdFiles.add(this.modelFile);
XMLStreamWriter writer = openXmlStreamWriter(out);
if (this.doResources) {
ResourceMap resourceMap = tx().getResourceMap();
Set<String> resourceTypesToExport = new TreeSet<>(resourceMap.getTypes(tx()));
if (!this.resourceTypes.isEmpty())
resourceTypesToExport.retainAll(this.resourceTypes);
for (String type : resourceTypesToExport) {
if (!this.multiFile) {
writeResourcesByType(writer, resourceMap, type);
} else {
String typeXmlFile = exportName + UNDERLINE + Tags.RESOURCE + UNDERLINE + type
+ XML_FILE_SUFFIX;
writer.writeEmptyElement(Tags.INCLUDE_FILE);
writer.writeAttribute(Tags.FILE, typeXmlFile);
File typeXmlFileF = new File(this.modelFile.getParentFile(), typeXmlFile);
DBC.INTERIM.assertNotExists("The type file should not exist with name.", typeXmlFileF);
logger.info("Writing " + resourceMap.querySize(tx(), type) + " " + type
+ " Resources to path: " + typeXmlFileF.getAbsolutePath() + "...");
try (FileOutputStream typeOut = new FileOutputStream(typeXmlFileF)) {
createdFiles.add(typeXmlFileF);
XMLStreamWriter typeWriter = openXmlStreamWriter(typeOut);
writeResourcesByType(typeWriter, resourceMap, type);
typeWriter.writeEndDocument();
}
}
}
}
if (this.doOrders) {
OrderMap orderMap = tx().getOrderMap();
Set<String> orderTypesToExport = new TreeSet<>(orderMap.getTypes(tx()));
if (!this.orderTypes.isEmpty())
orderTypesToExport.retainAll(this.orderTypes);
for (String type : orderTypesToExport) {
if (!this.multiFile) {
writeOrdersByType(writer, orderMap, type);
} else {
String typeXmlFile = exportName + UNDERLINE + Tags.ORDER + UNDERLINE + type + XML_FILE_SUFFIX;
writer.writeEmptyElement(Tags.INCLUDE_FILE);
writer.writeAttribute(Tags.FILE, typeXmlFile);
File typeXmlFileF = new File(this.modelFile.getParentFile(), typeXmlFile);
DBC.INTERIM.assertNotExists("The type file should not exist with name.", typeXmlFileF);
logger.info("Writing " + orderMap.querySize(tx(), type) + " " + type + " Orders to path: "
+ typeXmlFileF.getAbsolutePath() + "...");
try (FileOutputStream typeOut = new FileOutputStream(typeXmlFileF)) {
createdFiles.add(typeXmlFileF);
XMLStreamWriter typeWriter = openXmlStreamWriter(typeOut);
writeOrdersByType(typeWriter, orderMap, type);
typeWriter.writeEndDocument();
}
}
}
}
if (this.doActivities) {
ActivityMap activityMap = tx().getActivityMap();
Set<String> activityTypesToExport = new TreeSet<>(activityMap.getTypes(tx()));
if (!this.activityTypes.isEmpty())
activityTypesToExport.retainAll(this.activityTypes);
for (String type : activityTypesToExport) {
if (!this.multiFile) {
writeActivitiesByType(writer, activityMap, type);
} else {
String typeXmlFile = exportName + UNDERLINE + Tags.ACTIVITY + UNDERLINE + type
+ XML_FILE_SUFFIX;
writer.writeEmptyElement(Tags.INCLUDE_FILE);
writer.writeAttribute(Tags.FILE, typeXmlFile);
File typeXmlFileF = new File(this.modelFile.getParentFile(), typeXmlFile);
DBC.INTERIM.assertNotExists("The type file should not exist with name.", typeXmlFileF);
logger.info("Writing " + activityMap.querySize(tx(), type) + " " + type
+ " Activities to path: " + typeXmlFileF.getAbsolutePath() + "...");
try (FileOutputStream typeOut = new FileOutputStream(typeXmlFileF)) {
createdFiles.add(typeXmlFileF);
XMLStreamWriter typeWriter = openXmlStreamWriter(typeOut);
writeActivitiesByType(typeWriter, activityMap, type);
typeWriter.writeEndDocument();
}
}
}
}
// and now end
writer.writeEndElement();
writer.writeEndDocument();
writer.flush();
writer.close();
this.statistics.durationNanos = System.nanoTime() - start;
} catch (Exception e) {
for (File createdFile : createdFiles) {
if (createdFile.exists())
createdFile.delete();
}
String msg = "Failed to write model to file {0} due to {1}";
msg = MessageFormat.format(msg, this.modelFile, e.getMessage());
throw new StrolchException(msg, e);
}
}
@Override
public void undo() {
logger.warn("Not undoing export to file " + this.modelFile);
}
private void writeOrdersByType(XMLStreamWriter writer, OrderMap orderMap, String type) {
OrderVisitor<?> visitor = new OrderToSaxWriterVisitor(writer);
Set<String> keysByType = new TreeSet<>(orderMap.getKeysBy(tx(), type));
for (String id : keysByType) {
Order order = orderMap.getBy(tx(), type, id);
visitor.visit(order);
this.statistics.nrOfOrders++;
logElementsWritten();
}
}
private void logElementsWritten() {
if (this.nextLogTime < System.currentTimeMillis()) {
logger.info("Wrote " + this.statistics.getNrOfElements() + " of " + this.elementsToWrite + " Elements.");
this.nextLogTime = System.currentTimeMillis() + LOG_INTERVAL;
}
}
private void writeResourcesByType(XMLStreamWriter writer, ResourceMap resourceMap, String type) {
ResourceVisitor<?> visitor = new ResourceToSaxWriterVisitor(writer);
Set<String> keysByType = new TreeSet<>(resourceMap.getKeysBy(tx(), type));
for (String id : keysByType) {
Resource resource = resourceMap.getBy(tx(), type, id);
visitor.visit(resource);
this.statistics.nrOfResources++;
logElementsWritten();
}
}
private void writeActivitiesByType(XMLStreamWriter writer, ActivityMap activityMap, String type) {
ActivityVisitor<?> visitor = new ActivityToSaxWriterVisitor(writer);
Set<String> keysByType = new TreeSet<>(activityMap.getKeysBy(tx(), type));
for (String id : keysByType) {
Activity activity = activityMap.getBy(tx(), type, id);
visitor.visit(activity);
this.statistics.nrOfActivities++;
logElementsWritten();
}
}
private XMLStreamWriter openXmlStreamWriter(FileOutputStream out) throws FactoryConfigurationError,
XMLStreamException {
XMLOutputFactory factory = XMLOutputFactory.newInstance();
XMLStreamWriter writer = factory.createXMLStreamWriter(out, StrolchConstants.DEFAULT_ENCODING);
writer = new IndentingXMLStreamWriter(writer);
// start document
writer.writeStartDocument(StrolchConstants.DEFAULT_ENCODING, StrolchConstants.DEFAULT_XML_VERSION);
writer.writeStartElement(Tags.STROLCH_MODEL);
return writer;
}
/**
* @param modelFile
* the modelFile to set
*/
public void setModelFile(File modelFile) {
this.modelFile = modelFile;
}
/**
* @param multiFile
*/
public void setMultiFile(boolean multiFile) {
this.multiFile = multiFile;
}
/**
* @param doOrders
* the doOrders to set
*/
public void setDoOrders(boolean doOrders) {
this.doOrders = doOrders;
}
/**
* @param doResources
* the doResources to set
*/
public void setDoResources(boolean doResources) {
this.doResources = doResources;
}
/**
* @param doActivities
* the doActivities to set
*/
public void setDoActivities(boolean doActivities) {
this.doActivities = doActivities;
}
/**
* @param orderTypes
* the orderTypes to set
*/
public void setOrderTypes(Set<String> orderTypes) {
this.orderTypes = orderTypes;
}
/**
* @param resourceTypes
* the resourceTypes to set
*/
public void setResourceTypes(Set<String> resourceTypes) {
this.resourceTypes = resourceTypes;
}
/**
* @param activityTypes
* the activityTypes to set
*/
public void setActivityTypes(Set<String> activityTypes) {
this.activityTypes = activityTypes;
}
/**
* @return the statistics
*/
public ModelStatistics getStatistics() {
return this.statistics;
}
/**
* @param overwrite
*/
public void setOverwrite(boolean overwrite) {
this.overwrite = overwrite;
}
}