/*
* Copyright (c) 2013 Fraunhofer IGD
*
* All rights reserved. This program and the accompanying materials are made
* available 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Fraunhofer IGD
*/
package eu.esdihumboldt.hale.app.bgis.ade.propagate;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.xml.namespace.QName;
import org.eclipse.core.runtime.content.IContentType;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import eu.esdihumboldt.hale.app.bgis.ade.common.BGISAppConstants;
import eu.esdihumboldt.hale.app.bgis.ade.common.BGISAppUtil;
import eu.esdihumboldt.hale.app.bgis.ade.propagate.config.ExcelFeatureMap;
import eu.esdihumboldt.hale.app.bgis.ade.propagate.config.FeatureMap;
import eu.esdihumboldt.hale.common.align.io.AlignmentWriter;
import eu.esdihumboldt.hale.common.align.model.Alignment;
import eu.esdihumboldt.hale.common.align.model.Cell;
import eu.esdihumboldt.hale.common.align.model.CellUtil;
import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
import eu.esdihumboldt.hale.common.align.model.MutableAlignment;
import eu.esdihumboldt.hale.common.align.model.MutableCell;
import eu.esdihumboldt.hale.common.align.model.impl.DefaultAlignment;
import eu.esdihumboldt.hale.common.align.model.impl.TypeEntityDefinition;
import eu.esdihumboldt.hale.common.core.HalePlatform;
import eu.esdihumboldt.hale.common.core.io.HaleIO;
import eu.esdihumboldt.hale.common.core.io.IOProviderConfigurationException;
import eu.esdihumboldt.hale.common.core.io.extension.IOProviderDescriptor;
import eu.esdihumboldt.hale.common.core.io.impl.NullProgressIndicator;
import eu.esdihumboldt.hale.common.core.io.report.IOReport;
import eu.esdihumboldt.hale.common.core.io.supplier.DefaultInputSupplier;
import eu.esdihumboldt.hale.common.core.io.supplier.FileIOSupplier;
import eu.esdihumboldt.hale.common.core.io.supplier.LocatableInputSupplier;
import eu.esdihumboldt.hale.common.core.report.Report;
import eu.esdihumboldt.hale.common.core.report.ReportHandler;
import eu.esdihumboldt.hale.common.headless.impl.ProjectTransformationEnvironment;
import eu.esdihumboldt.hale.common.schema.SchemaSpaceID;
import eu.esdihumboldt.hale.common.schema.io.SchemaReader;
import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition;
import eu.esdihumboldt.hale.common.schema.model.Schema;
import eu.esdihumboldt.hale.common.schema.model.SchemaSpace;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.io.gml.CityGMLConstants;
/**
* Generates an extended mapping for BGIS CityGML ADE based on an example
* mapping.
*
* @author Simon Templer
*/
public class CityGMLPropagate implements BGISAppConstants, CityGMLConstants {
private SchemaSpace sourceSchema;
private SchemaSpace targetSchema;
private Schema cityGMLSource;
private Alignment examples;
private Alignment alignment;
private FeatureMap config;
private CityGMLPropagateContext context;
/**
* Generate the default value mapping based on the given configuration.
*
* @param context the configuration for the mapping generation
* @throws Exception if an unrecoverable error occurs during the generation
*/
public void generate(CityGMLPropagateContext context) throws Exception {
this.context = context;
// load project
if (loadProject()) {
// load CityGML schema
loadCityGML();
// load the feature map
loadConfig();
// generate mapping
generateMapping();
// write alignment
writeAlignment();
}
}
private boolean loadProject() throws IOException {
final AtomicBoolean success = new AtomicBoolean(true);
LocatableInputSupplier<? extends InputStream> projectIn = new DefaultInputSupplier(
context.getProject());
ProjectTransformationEnvironment env = new ProjectTransformationEnvironment("sample",
projectIn, new ReportHandler() {
@Override
public void publishReport(Report<?> report) {
if (report.isSuccess() && report.getErrors().isEmpty()) {
System.out.println(report.getSummary());
}
else {
System.err.println("Error loading project: " + report.getSummary());
success.set(false);
}
}
});
if (success.get()) {
this.sourceSchema = env.getSourceSchema();
this.targetSchema = env.getTargetSchema();
this.examples = env.getAlignment();
return true;
}
return false;
}
private void loadCityGML() throws IOProviderConfigurationException, IOException {
System.out.println("Loading schema...");
LocatableInputSupplier<? extends InputStream> schemaIn = new DefaultInputSupplier(
context.getSourceSchema());
SchemaReader schemaReader = HaleIO.findIOProvider(SchemaReader.class, schemaIn, context
.getSourceSchema().getPath());
schemaReader.setSource(schemaIn);
IOReport report = schemaReader.execute(new NullProgressIndicator());
if (!report.isSuccess() || !report.getErrors().isEmpty()) {
throw new IllegalStateException("Failed to load schema");
}
cityGMLSource = schemaReader.getSchema();
}
private void loadConfig() {
System.out.println("Loading feature map...");
try {
config = new ExcelFeatureMap(context.getConfig());
} catch (Exception e) {
throw new IllegalStateException("Failed to load feature map configuration", e);
}
}
private void generateMapping() {
System.out.println("Indexing example cells...");
// index all cells based on the target property name
SetMultimap<String, Cell> bgisExamples = HashMultimap.create();
SetMultimap<QName, Cell> cityGMLExamples = HashMultimap.create();
for (Cell cell : examples.getCells()) {
if (cell.getTarget().size() == 1) {
// only supports cells with one target
EntityDefinition entityDef = CellUtil.getFirstEntity(cell.getTarget())
.getDefinition();
// XXX check source?!
if (entityDef.getDefinition() instanceof PropertyDefinition) {
QName name = entityDef.getDefinition().getName();
if (ADE_NS.equals(name.getNamespaceURI())) {
bgisExamples.put(name.getLocalPart(), cell);
}
else if (name.getNamespaceURI().startsWith(CITYGML_NAMESPACE_CORE)) {
// XXX only support level 1 properties?
cityGMLExamples.put(name, cell);
}
else
System.out
.println("WARNING: ignoring cell with target property neither from CityGML nor from BGIS ADE");
}
else
System.out.println("WARNING: ignoring type cell");
}
else
System.out.println("WARNING: ignoring cell with multiple or no targets");
}
// collect all ADE feature types
List<TypeDefinition> featureTypes = BGISAppUtil.getADEFeatureTypes(targetSchema);
// collect ADE display names
Set<String> adeTypeNames = new HashSet<String>();
for (TypeDefinition type : featureTypes) {
adeTypeNames.add(type.getDisplayName());
}
// collect possibly relevant target CityGML feature types
for (TypeDefinition type : targetSchema.getTypes()) {
if (type.getName().getNamespaceURI().startsWith(CITYGML_NAMESPACE_CORE)
&& BGISAppUtil.isFeatureType(type)) {
if (!adeTypeNames.contains(type.getDisplayName())) {
/*
* But ensure to only add those that do not share the
* display name with an ADE type, as in the feature map the
* type identification is only done on based on the display
* name, and ADE types take precedent.
*/
featureTypes.add(type);
}
}
}
// visit ADE properties and create cells
System.out.println("Generating mapping from example cells for");
String cellNote = MessageFormat.format(
"Generated through propagation of example cells on CityGML and BGIS ADE feature types.\n"
+ "{0,date,medium}", new Date());
CityGMLPropagateVisitor visitor = new CityGMLPropagateVisitor(cityGMLSource, bgisExamples,
cityGMLExamples, config, cellNote);
for (TypeDefinition type : featureTypes) {
System.out.println(type.getDisplayName() + "...");
visitor.accept(new TypeEntityDefinition(type, SchemaSpaceID.TARGET, null));
}
if (visitor.getCells().isEmpty()) {
System.out.println("WARNING: no cells were created");
}
else {
System.out.println(visitor.getCells().size() + " cells were created.");
}
// create alignment
MutableAlignment align = new DefaultAlignment();
for (MutableCell cell : visitor.getCells()) {
align.addCell(cell);
}
this.alignment = align;
}
private void writeAlignment() throws Exception {
System.out.println("Writing alignment to " + context.getOut().getAbsolutePath());
// create alignment writer
IContentType contentType = HalePlatform.getContentTypeManager().getContentType(
ALIGNMENT_CONTENT_TYPE);
IOProviderDescriptor factory = HaleIO.findIOProviderFactory(AlignmentWriter.class,
contentType, null);
AlignmentWriter writer = (AlignmentWriter) factory.createExtensionObject();
// configure alignment writer
writer.setSourceSchema(sourceSchema);
writer.setTargetSchema(targetSchema);
writer.setTarget(new FileIOSupplier(context.getOut()));
writer.setAlignment(alignment);
IOReport report = writer.execute(new NullProgressIndicator());
if (!report.isSuccess() || !report.getErrors().isEmpty()) {
throw new IllegalStateException("Errors while writing the alignment.");
}
else {
System.out.println("Completed successfully.");
}
}
}