/*
* $URL:https://secure.revolsys.com/svn/open.revolsys.com/GIS/trunk/src/main/java/com/revolsys/gis/format/saif/io/SaifWriter.java $
* $Author:paul.austin@revolsys.com $
* $Date:2007-06-09 09:28:28 -0700 (Sat, 09 Jun 2007) $
* $Revision:265 $
* Copyright 2004-2005 Revolution Systems Inc.
*
* 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 com.revolsys.record.io.format.saif;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.apache.log4j.Logger;
import com.revolsys.io.AbstractRecordWriter;
import com.revolsys.io.FileUtil;
import com.revolsys.io.PathUtil;
import com.revolsys.io.ZipUtil;
import com.revolsys.record.Record;
import com.revolsys.record.io.format.saif.util.ObjectSetUtil;
import com.revolsys.record.io.format.saif.util.OsnConverterRegistry;
import com.revolsys.record.io.format.saif.util.OsnSerializer;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.record.schema.RecordDefinitionFactory;
import com.revolsys.spring.resource.Resource;
import com.revolsys.util.Property;
/**
* <p>
* The SaifWriter.
* </p>
*
* @author Paul Austin
* @see SaifReader
*/
public class SaifWriter extends AbstractRecordWriter {
private static final String GLOBAL_METADATA = "/GlobalMetadata";
private static final Logger log = Logger.getLogger(SaifWriter.class);
private RecordDefinition annotatedSpatialDataSetType;
private final Map<String, String> compositeTypeNames = new HashMap<>();
protected OsnConverterRegistry converters = new OsnConverterRegistry();
private final Set<String> exportedTypes = new LinkedHashSet<>();
private final Map<String, Map<String, Object>> exports = new TreeMap<>();
private File file;
private boolean indentEnabled = false;
private boolean initialized;
private int maxSubsetSize = Integer.MAX_VALUE;
private final Map<String, String> objectIdentifiers = new HashMap<>();
private final Map<String, String> objectSetNames = new HashMap<>();
private RecordDefinitionFactory recordDefinitionFactory;
private List<Resource> schemaFileNames;
private String schemaResource;
private final Map<String, OsnSerializer> serializers = new HashMap<>();
private RecordDefinition spatialDataSetType;
private File tempDirectory;
public SaifWriter() {
}
public SaifWriter(final File file) throws IOException {
setFile(file);
}
public SaifWriter(final File file, final RecordDefinitionFactory recordDefinitionFactory)
throws IOException {
this(file);
setRecordDefinitionFactory(recordDefinitionFactory);
}
public SaifWriter(final String fileName) throws IOException {
this(new File(fileName));
}
public void addCompositeTypeName(final String typePath, final String compositeTypeName) {
this.compositeTypeNames.put(String.valueOf(typePath), compositeTypeName);
}
protected void addExport(final String typePath, final String compositeType,
final String objectSubset) {
if (!this.exports.containsKey(typePath)) {
final Map<String, Object> export = new HashMap<>();
this.exports.put(typePath, export);
final String referenceId = getObjectIdentifier(typePath);
export.put("referenceId", referenceId);
export.put("compositeType", compositeType);
export.put("objectSubset", objectSubset);
}
}
@Override
public synchronized void close() {
if (this.tempDirectory != null) {
try {
if (log.isInfoEnabled()) {
log.info("Closing SAIF archive '" + this.file.getCanonicalPath() + "'");
}
writeExports();
writeMissingDirObject("InternallyReferencedObjects", "internal.dir");
writeMissingDirObject("ImportedObjects", "imports.dir");
writeMissingGlobalMetadata();
if (log.isInfoEnabled()) {
log.info(" Closing serializers");
}
for (final OsnSerializer serializer : this.serializers.values()) {
try {
serializer.close();
} catch (final Throwable e) {
log.error(e.getMessage(), e);
}
}
if (log.isDebugEnabled()) {
log.debug(" Compressing SAIF archive");
}
if (!this.file.isDirectory()) {
ZipUtil.zipDirectory(this.file, this.tempDirectory);
}
} catch (final RuntimeException e) {
this.file.delete();
throw e;
} catch (final Error e) {
this.file.delete();
throw e;
} catch (final IOException e) {
log.error(" Unable to compress SAIF archive: " + e.getMessage(), e);
e.printStackTrace();
} finally {
if (log.isDebugEnabled()) {
log.debug(" Deleting temporary files");
}
if (!this.file.isDirectory()) {
FileUtil.deleteDirectory(this.tempDirectory);
if (log.isDebugEnabled()) {
log.debug(" Finished closing file");
}
}
this.tempDirectory = null;
}
}
}
@Override
public void flush() {
}
private RecordDefinition getCompositeType(final String typePath) {
String compositeTypeName = this.compositeTypeNames.get(typePath);
if (compositeTypeName == null) {
compositeTypeName = typePath + "Composite";
}
final RecordDefinition compisteType = this.recordDefinitionFactory
.getRecordDefinition(String.valueOf(compositeTypeName));
return compisteType;
}
public File getFile() {
return this.file;
}
public int getMaxSubsetSize() {
return this.maxSubsetSize;
}
public String getObjectIdentifier(final String typePath) {
String objectIdentifier = this.objectIdentifiers.get(typePath);
if (objectIdentifier == null) {
objectIdentifier = PathUtil.getName(typePath);
this.objectIdentifiers.put(typePath, objectIdentifier);
}
return objectIdentifier;
}
/**
* @return the objectIdentifiers
*/
public Map<String, String> getObjectIdentifiers() {
return this.objectIdentifiers;
}
/**
* Get the object set name (file name) within a SAIF archive file name for the
* specified type name. The null value will be returned if a object set name
* has not been set for that type name.
*
* @param typePath The type name.
* @return The object set name for the type name.
*/
public String getObjectSetName(final String typePath) {
return this.objectSetNames.get(typePath);
}
public Map<String, String> getObjectSetNames() {
return this.objectSetNames;
}
private String getObjectSubsetName(final String typePath) {
String objectSubsetName = getObjectSetName(typePath);
if (objectSubsetName == null) {
objectSubsetName = PathUtil.getName(typePath);
if (objectSubsetName.length() > 6) {
objectSubsetName = objectSubsetName.substring(0, 6);
}
objectSubsetName += "00.osn";
this.objectSetNames.put(typePath, objectSubsetName);
}
return objectSubsetName;
}
public String getSchemaResource() {
return this.schemaResource;
}
private OsnSerializer getSerializer(final String typePath) throws IOException {
OsnSerializer serializer = this.serializers.get(typePath);
if (serializer == null) {
initialize();
try {
final RecordDefinition compositeType = getCompositeType(typePath);
if (compositeType != null) {
final String objectSubsetName = getObjectSubsetName(typePath);
if (this.maxSubsetSize != Long.MAX_VALUE) {
FileUtil.deleteFiles(this.tempDirectory,
ObjectSetUtil.getObjectSubsetPrefix(objectSubsetName) + "...osn");
serializer = newSerializer(typePath, new File(this.tempDirectory, objectSubsetName),
this.maxSubsetSize);
} else {
serializer = newSerializer(typePath, new File(this.tempDirectory, objectSubsetName),
Long.MAX_VALUE);
}
if (compositeType.isInstanceOf(this.annotatedSpatialDataSetType)) {
serializer.startObject(compositeType.getPath());
serializer.fieldName("objectIdentifier");
final String objectIdentifier = getObjectIdentifier(typePath);
serializer.attributeValue(objectIdentifier);
serializer.endLine();
serializer.serializeIndent();
serializer.fieldName("annotationComponents");
serializer.startCollection("Set");
} else if (compositeType.isInstanceOf(this.spatialDataSetType)) {
serializer.startObject(compositeType.getPath());
serializer.fieldName("objectIdentifier");
final String objectIdentifier = getObjectIdentifier(typePath);
serializer.attributeValue(objectIdentifier);
serializer.endLine();
serializer.serializeIndent();
serializer.fieldName("geoComponents");
serializer.startCollection("Set");
}
addExport(typePath, compositeType.getPath(), objectSubsetName);
this.serializers.put(typePath, serializer);
} else if (typePath.equals("/ImportedObjects")) {
serializer = newSerializer("/ImportedObject", new File(this.tempDirectory, "imports.dir"),
Long.MAX_VALUE);
this.serializers.put(typePath, serializer);
} else if (PathUtil.getName(typePath).endsWith("InternallyReferencedObjects")) {
serializer = newSerializer("/InternallyReferencedObject",
new File(this.tempDirectory, "internal.dir"), Long.MAX_VALUE);
this.serializers.put(typePath, serializer);
} else if (PathUtil.getName(typePath).endsWith("GlobalMetadata")) {
serializer = newSerializer(GLOBAL_METADATA, new File(this.tempDirectory, "globmeta.osn"),
Long.MAX_VALUE);
addExport(typePath, typePath, "globmeta.osn");
this.serializers.put(typePath, serializer);
}
} catch (final IOException e) {
log.error("Unable to create serializer: " + e.getMessage(), e);
}
}
return serializer;
}
public File getTempDirectory() {
return this.tempDirectory;
}
private void initialize() throws IOException {
if (!this.initialized) {
this.initialized = true;
if (this.schemaResource != null) {
final InputStream in = getClass().getResourceAsStream(this.schemaResource);
if (in != null) {
FileUtil.copy(in, new File(this.tempDirectory, "clasdefs.csn"));
}
}
if (this.schemaFileNames != null) {
try {
final OutputStream out = new FileOutputStream(
new File(this.tempDirectory, "clasdefs.csn"));
try {
for (final Resource resource : this.schemaFileNames) {
final InputStream in = resource.getInputStream();
final SaifSchemaReader reader = new SaifSchemaReader();
setRecordDefinitionFactory(reader.loadSchemas(this.schemaFileNames));
try {
FileUtil.copy(in, out);
} finally {
in.close();
}
}
} finally {
out.close();
}
} catch (final IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
}
public boolean isIndentEnabled() {
return this.indentEnabled;
}
protected OsnSerializer newSerializer(final String typePath, final File file, final long maxSize)
throws IOException {
final OsnSerializer serializer = new OsnSerializer(typePath, file, maxSize, this.converters);
serializer.setIndentEnabled(this.indentEnabled);
return serializer;
}
public void setCompositeTypeNames(final Map<String, String> compositeTypeNames) {
for (final Entry<String, String> entry : compositeTypeNames.entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();
addCompositeTypeName(key, value);
}
}
public void setFile(final File file) throws IOException {
if (!file.isDirectory()) {
final File parentDir = file.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}
String fileName = FileUtil.getFileName(file);
String filePrefix = fileName;
final int extensionIndex = fileName.lastIndexOf('.');
if (extensionIndex != -1) {
filePrefix = fileName.substring(0, extensionIndex);
final String extension = fileName.substring(extensionIndex + 1);
if (!extension.equals(".saf") && !extension.equals(".zip")) {
fileName = filePrefix + ".saf";
}
} else {
fileName = filePrefix + ".saf";
}
this.file = new File(file.getCanonicalFile().getParentFile(), fileName);
if (log.isInfoEnabled()) {
log.info("Creating SAIF archive '" + file.getAbsolutePath() + "'");
}
this.tempDirectory = FileUtil.newTempDirectory(filePrefix, ".saf");
FileUtil.deleteFileOnExit(this.tempDirectory);
} else {
this.file = file;
this.tempDirectory = file;
}
initialize();
}
public void setIndentEnabled(final boolean indentEnabled) {
this.indentEnabled = indentEnabled;
}
public void setMaxSubsetSize(final int maxSubsetSize) {
this.maxSubsetSize = maxSubsetSize;
}
/**
* @param objectIdentifiers the objectIdentifiers to set
*/
public void setObjectIdentifiers(final Map<String, String> objectIdentifiers) {
for (final Entry<String, String> entry : objectIdentifiers.entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();
final String qName = String.valueOf(key);
this.objectIdentifiers.put(qName, value);
}
}
/**
* Set the full object set name (file name) within a SAIF archive file name
* for the specified type name. The name must include the .osn (or other)
* extension (e.g. globmeta.osn). If the file is to be split into multiple
* object sub sets (for large files) include {$partNum} before the file
* extension (e.g. roads{$segment}.osn) and the file names will include the
* object sub set number. If a value is not set the file name will be the
* first 6 characters of the type name, followed by a object subset number
* starting at 00 with the .osn suffix (e.g. BreakLines would be
* breakl00.osn).
*
* @param typePath The type name
* @param subSetName The sub set name for the type name.
*/
public void setObjectSetName(final String typePath, final String subSetName) {
this.objectSetNames.put(typePath, subSetName);
}
public void setObjectSetNames(final Map<String, String> objectSetNames) {
for (final Entry<String, String> entry : objectSetNames.entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();
setObjectSetName(key, value);
}
}
public void setRecordDefinitionFactory(final RecordDefinitionFactory schema) {
this.recordDefinitionFactory = schema;
if (schema != null) {
this.spatialDataSetType = schema.getRecordDefinition("/SpatialDataSet");
this.annotatedSpatialDataSetType = schema.getRecordDefinition("/AnnotatedSpatialDataSet");
}
}
public void setSchemaFileNames(final List<Resource> schemaFileNames) {
this.schemaFileNames = schemaFileNames;
}
public void setSchemaResource(final String schemaResource) throws IOException {
this.schemaResource = schemaResource;
}
@Override
public String toString() {
return this.file.getAbsolutePath();
}
@Override
public void write(final Record object) {
try {
final RecordDefinition type = object.getRecordDefinition();
final OsnSerializer serializer = getSerializer(type.getPath());
if (serializer != null) {
serializer.serializeRecord(object);
if (this.indentEnabled) {
serializer.endLine();
}
} else {
log.error("No serializer for type '" + type.getPath() + "'");
}
} catch (final IOException e) {
log.error(e.getMessage(), e);
}
}
public void writeExport(final OsnSerializer exportsSerializer, final String referenceId,
final String compositeTypeName, final String objectSubset) throws IOException {
exportsSerializer.startObject("ExportedObjectHandle");
exportsSerializer.attribute("referenceID", referenceId, true);
exportsSerializer.attribute("type", compositeTypeName, true);
exportsSerializer.attribute("objectSubset", objectSubset, true);
exportsSerializer.attribute("offset", new BigDecimal("0"), true);
exportsSerializer.attribute("sharable", Boolean.FALSE, true);
exportsSerializer.endObject();
}
private void writeExports() throws IOException {
final File exportsFile = new File(this.tempDirectory, "exports.dir");
final OsnSerializer exportsSerializer = newSerializer("/ExportedObject", exportsFile,
Long.MAX_VALUE);
exportsSerializer.startObject("/ExportedObjects");
exportsSerializer.fieldName("handles");
exportsSerializer.startCollection("Set");
writeExport(exportsSerializer, "GlobalMetadata", "GlobalMetadata", "globmeta.osn");
for (final Map<String, Object> export : this.exports.values()) {
final String compositeType = (String)export.get("compositeType");
final String referenceId = (String)export.get("referenceId");
final String objectSubset = (String)export.get("objectSubset");
String compositeTypeName = PathUtil.getName(compositeType);
final String compositeNamespace = PathUtil.getPath(compositeType).replaceAll("/", "");
if (Property.hasValue(compositeNamespace)) {
compositeTypeName += "::" + compositeNamespace;
}
writeExport(exportsSerializer, referenceId, compositeTypeName, objectSubset);
}
exportsSerializer.close();
}
private void writeMissingDirObject(final String typePath, final String fileName)
throws IOException {
if (!this.serializers.containsKey(typePath)) {
final File file = new File(this.tempDirectory, fileName);
final PrintStream out = new PrintStream(new FileOutputStream(file));
try {
out.print(typePath);
out.print("(handles:Set{})");
} finally {
out.close();
}
}
}
private void writeMissingGlobalMetadata() {
if (!this.exports.containsKey(GLOBAL_METADATA)) {
try {
addExport(GLOBAL_METADATA, GLOBAL_METADATA, "globmeta.osn");
final File metaFile = new File(this.tempDirectory, "globmeta.osn");
final OsnSerializer serializer = newSerializer(GLOBAL_METADATA, metaFile, Long.MAX_VALUE);
serializer.startObject("/GlobalMetadata");
serializer.attribute("objectIdentifier", "GlobalMetadata", true);
serializer.fieldName("creationTime");
serializer.startObject("/TimeStamp");
final Date creationTimestamp = new Date(System.currentTimeMillis());
serializer.attribute("year", new BigDecimal(creationTimestamp.getYear() + 1900), true);
serializer.attribute("month", new BigDecimal(creationTimestamp.getMonth() + 1), true);
serializer.attribute("day", new BigDecimal(creationTimestamp.getDate()), true);
serializer.attribute("hour", new BigDecimal(creationTimestamp.getHours()), true);
serializer.attribute("minute", new BigDecimal(creationTimestamp.getMinutes()), true);
serializer.attribute("second", new BigDecimal(creationTimestamp.getSeconds()), true);
serializer.endObject();
serializer.fieldName("saifProfile");
serializer.startObject("/Profile");
serializer.attribute("authority", "Government of British Columbia", true);
serializer.attribute("idName", "SAIFLite", true);
serializer.attribute("version", "Release 1.1", true);
serializer.endObject();
serializer.attribute("saifRelease", "SAIF 3.2", true);
serializer.attribute("toolkitVersion", "SAIF Toolkit Version 1.4.0 (May 05, 1997)", true);
serializer.fieldName("userProfile");
serializer.startObject("/UserProfile");
serializer.fieldName("coordDefs");
serializer.startObject("/LocationalDefinitions");
serializer.attributeEnum("c1", "real32", true);
serializer.attributeEnum("c2", "real32", true);
serializer.attributeEnum("c3", "real32", true);
serializer.endObject();
serializer.attribute("organization", new BigDecimal("4"), true);
serializer.endObject();
serializer.endObject();
serializer.close();
} catch (final IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
}