/* * $URL:https://secure.revolsys.com/svn/open.revolsys.com/GIS/trunk/src/main/java/com/revolsys/gis/format/saif/io/SaifReader.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.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeMap; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.log4j.Logger; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.io.AbstractReader; import com.revolsys.io.FileUtil; import com.revolsys.io.PathUtil; import com.revolsys.record.ArrayRecord; import com.revolsys.record.Record; import com.revolsys.record.RecordFactory; import com.revolsys.record.io.RecordReader; import com.revolsys.record.io.format.saif.util.PathCache; import com.revolsys.record.property.FieldProperties; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.record.schema.RecordDefinitionFactory; import com.revolsys.record.schema.RecordDefinitionFactoryImpl; import com.revolsys.spring.resource.ClassPathResource; import com.revolsys.spring.resource.Resource; /** * <p> * The SaifReader. * </p> * * @author Paul Austin * @see SaifWriter */ public class SaifReader extends AbstractReader<Record> implements Iterator<Record>, RecordDefinitionFactory, RecordReader { /** The logging instance. */ private static final Logger log = Logger.getLogger(SaifReader.class); /** The current data object that was read. */ private Record currentRecord; /** The schema definition declared in the SAIF archive. */ private RecordDefinitionFactory declaredRecordDefinitionFactory; /** List of type names to exclude from reading. */ private final Set<String> excludeTypeNames = new LinkedHashSet<>(); /** The list of exported objects. */ private Record exportedObjects; private RecordFactory factory = ArrayRecord.FACTORY; /** The SAIF archive file. */ private File file; /** Mapping between file names and type names. */ private final Map<String, String> fileNameTypeNameMap = new HashMap<>(); /** The global metatdata for the archive. */ private Record globalMetadata; /** Flag indicating if the iterator has more objects. */ private boolean hasNext; /** The list of imported objects. */ private Record importedObjects; /** List of type names to include for reading. */ private final Set<String> includeTypeNames = new LinkedHashSet<>(); /** The list of internally referenced objects. */ private Record internallyReferencedObjects; /** Flag indicating if a new data object should be read. */ private boolean loadNewObject = true; private boolean opened = false; /** The iterator for the current object set. */ private OsnReader osnReader; /** The schema definition that will be set on each data object. */ private RecordDefinitionFactory recordDefinitionFactory; /** The directory the SAIF archive is extracted to. */ private File saifArchiveDirectory; private int srid = 26910; /** Mapping between type names and file names. */ private final Map<String, String> typePathFileNameMap = new HashMap<>(); /** The iterator of object subsets for the archive. */ private Iterator<String> typePathIterator; private List<String> typePaths; /** The zip file. */ private ZipFile zipFile; public SaifReader() { } /** * Construct a new new SaifReader to read the SAIF archive from the specified file . * If the file is a directory, then in must contain an expanded SAIF archive, * otherwise the file must be a compressed SAIF archive (.zip or.saf). * * @param file The SAIF archive file to read. */ public SaifReader(final File file) { setFile(file); } public SaifReader(final Resource resource) { setFile(Resource.getFileOrCreateTempFile(resource)); } /** * Construct a new new SaifReader to read the SAIF archive from the specified file * name. If the file is a directory, then in must contain an expanded SAIF * archive, otherwise the file must be a compressed SAIF archive (.zip * or.saf). * * @param fileName The name of the SAIF archive file to read. */ public SaifReader(final String fileName) { this(new File(fileName)); } /** * Close the SAIF archive. */ @Override public void close() { if (log.isDebugEnabled()) { log.debug("Closing SAIF archive '" + this.file.getAbsolutePath() + "'"); } closeCurrentReader(); if (!this.file.isDirectory() && this.saifArchiveDirectory != null) { if (log.isDebugEnabled()) { log.debug(" Deleting temporary files"); } FileUtil.deleteDirectory(this.saifArchiveDirectory); } if (log.isDebugEnabled()) { log.debug(" Finished closing file"); } } private void closeCurrentReader() { if (this.osnReader != null) { this.osnReader.close(); this.osnReader = null; } } @Override public int getCoordinateSystemId() { return this.srid; } /** * Get the schema definition declared in the SAIF archive. * * @return The schema definition. */ public RecordDefinitionFactory getDeclaredRecordDefinitionFactory() { return this.declaredRecordDefinitionFactory; } /** * Get the list of exported objects for the SAIF archive. * * @return The exported objects. */ public Record getExportedObjects() { return this.exportedObjects; } /** * @return the factory */ public RecordFactory getFactory() { return this.factory; } public File getFile() { return this.file; } private String getFileName(final String typePath) { return this.typePathFileNameMap.get(typePath); } /** * Get the global metatdata for the SAIF archive. * * @return The global metadata. */ public Record getGlobalMetadata() { if (this.globalMetadata == null) { try { loadGlobalMetadata(); } catch (final IOException e) { throw new RuntimeException("Unable to load globmeta.osn: " + e.getMessage()); } } return this.globalMetadata; } /** * Get the list of imported objects for the SAIF archive. * * @return The imported objects. */ public Record getImportedObjects() { if (this.importedObjects == null) { try { loadImportedObjects(); } catch (final IOException e) { throw new RuntimeException("Unable to load imports.dir: " + e.getMessage()); } } return this.importedObjects; } private InputStream getInputStream(final String fileName) throws IOException { if (this.zipFile != null) { final ZipEntry entry = this.zipFile.getEntry(fileName); return this.zipFile.getInputStream(entry); } else { return new FileInputStream(new File(this.saifArchiveDirectory, fileName)); } } /** * Get the list of internally referenced objects for the SAIF archive. * * @return The internally referenced objects. */ public Record getInternallyReferencedObjects() { if (this.internallyReferencedObjects == null) { try { loadInternallyReferencedObjects(); } catch (final IOException e) { throw new RuntimeException("Unable to load internal.dir: " + e.getMessage()); } } return this.internallyReferencedObjects; } private <D extends Record> OsnReader getOsnReader( final RecordDefinitionFactory recordDefinitionFactory, final RecordFactory factory, final String className) throws IOException { String fileName = this.typePathFileNameMap.get(className); if (fileName == null) { fileName = PathUtil.getName(className); } OsnReader reader; if (this.zipFile != null) { reader = new OsnReader(recordDefinitionFactory, this.zipFile, fileName, this.srid); } else { reader = new OsnReader(recordDefinitionFactory, this.saifArchiveDirectory, fileName, this.srid); } reader.setFactory(factory); return reader; } public OsnReader getOsnReader(final String className) throws IOException { return getOsnReader(className, this.factory); } public <D extends Record> OsnReader getOsnReader(final String className, final RecordFactory factory) throws IOException { final RecordDefinitionFactory recordDefinitionFactory = this.recordDefinitionFactory; return getOsnReader(recordDefinitionFactory, factory, className); } @Override public RecordDefinition getRecordDefinition() { // TODO Auto-generated method stub return null; } @Override public RecordDefinition getRecordDefinition(final String typePath) { return this.recordDefinitionFactory.getRecordDefinition(typePath); } /** * Get the schema definition that will be set on each data object. * * @return The schema definition. */ public RecordDefinitionFactory getRecordDefinitionFactory() { return this.recordDefinitionFactory; } private String getTypeName(final String fileName) { return this.fileNameTypeNameMap.get(fileName); } /** * @return the typePathObjectSetMap */ public Map<String, String> getTypeNameFileNameMap() { return this.typePathFileNameMap; } public List<String> getTypeNames() { return this.typePaths; } private boolean hasData(final String typePath) { final String fileName = getFileName(typePath); if (fileName == null) { return false; } else if (this.zipFile != null) { return this.zipFile.getEntry(fileName) != null; } else { return new File(this.saifArchiveDirectory, fileName).exists(); } } /** * Check to see if the reader has more data objects to be read. * * @return True if the reader has more data objects to be read. */ @Override public boolean hasNext() { if (this.loadNewObject) { return loadNextRecord(); } return this.hasNext; } @Override public Iterator<Record> iterator() { open(); return this; } /** * Load the exported objects for the SAIF archive. * * @throws IOException If there was an I/O error. */ @SuppressWarnings("unchecked") private void loadExportedObjects() throws IOException { final boolean setNames = this.includeTypeNames.isEmpty(); final ClassPathResource resource = new ClassPathResource("com/revolsys/io/saif/saifzip.csn"); final RecordDefinitionFactory schema = new SaifSchemaReader().loadSchema(resource); final OsnReader reader = getOsnReader(schema, this.factory, "/exports.dir"); try { final Map<String, String> names = new TreeMap<>(); if (reader.hasNext()) { this.exportedObjects = reader.next(); final Set<Record> handles = (Set<Record>)this.exportedObjects.getValue("handles"); for (final Record exportedObject : handles) { final String fileName = (String)exportedObject.getValue("objectSubset"); if (fileName != null && !fileName.equals("globmeta.osn")) { String typePath = getTypeName(fileName); if (typePath == null) { final String name = (String)exportedObject.getValue("type"); typePath = PathCache.getName(name); if (!this.fileNameTypeNameMap.containsKey(fileName)) { this.fileNameTypeNameMap.put(fileName, typePath); this.typePathFileNameMap.put(typePath, fileName); } } if (setNames && !fileName.equals("metdat00.osn") && !fileName.equals("refsys00.osn")) { names.put(typePath.toString(), typePath); } } } if (setNames) { this.typePaths = new ArrayList<>(names.values()); } else { this.typePaths = new ArrayList<>(this.includeTypeNames); } this.typePaths.removeAll(this.excludeTypeNames); } } finally { reader.close(); } } /** * Load the global metatdata for the SAIF archive. * * @throws IOException If there was an I/O error. */ private void loadGlobalMetadata() throws IOException { final OsnReader reader = getOsnReader("/globmeta.osn", this.factory); try { if (reader.hasNext()) { this.globalMetadata = this.osnReader.next(); } } finally { reader.close(); } } /** * Load the imported objects for the SAIF archive. * * @throws IOException If there was an I/O error. */ private void loadImportedObjects() throws IOException { final OsnReader reader = getOsnReader("/imports.dir", this.factory); try { if (reader.hasNext()) { this.importedObjects = this.osnReader.next(); } } finally { reader.close(); } } /** * Load the internally referenced objects for the SAIF archive. * * @throws IOException If there was an I/O error. */ private void loadInternallyReferencedObjects() throws IOException { final OsnReader reader = getOsnReader("/internal.dir", this.factory); try { if (reader.hasNext()) { this.internallyReferencedObjects = this.osnReader.next(); } } finally { reader.close(); } } /** * Load the next data object from the archive. A new subset will be loaded if * required or if there was an error reading from one of the subsets. * * @return True if an object was loaded. */ private boolean loadNextRecord() { boolean useCurrentFile = true; if (this.osnReader == null) { useCurrentFile = false; } else if (!this.osnReader.hasNext()) { useCurrentFile = false; } if (!useCurrentFile) { if (!openNextObjectSet()) { this.currentRecord = null; this.hasNext = false; return false; } } do { try { this.currentRecord = this.osnReader.next(); this.hasNext = true; this.loadNewObject = false; return true; } catch (final Throwable e) { log.error(e.getMessage(), e); } } while (openNextObjectSet()); this.currentRecord = null; this.hasNext = false; return false; } /** * Load the schema from the SAIF archive. * * @throws IOException If there was an I/O error. */ private void loadSchema() throws IOException { final SaifSchemaReader parser = new SaifSchemaReader(); final InputStream in = getInputStream("clasdefs.csn"); try { this.declaredRecordDefinitionFactory = parser.loadSchema("clasdefs.csn", in); } finally { FileUtil.closeSilent(in); } if (this.recordDefinitionFactory == null) { setRecordDefinitionFactory(this.declaredRecordDefinitionFactory); } } private void loadSrid() throws IOException { final OsnReader reader = getOsnReader("/refsys00.osn", this.factory); try { if (reader.hasNext()) { final Record spatialReferencing = reader.next(); final Record coordinateSystem = spatialReferencing.getValue("coordSystem"); if (coordinateSystem.getRecordDefinition().getPath().equals("/UTM")) { final Number srid = coordinateSystem.getValue("zone"); setSrid(26900 + srid.intValue()); } } } finally { reader.close(); } } /** * Get the next data object read by this reader. . * * @return The next Record. * @exception NoSuchElementException If the reader has no more data objects. */ @Override public Record next() { if (hasNext()) { this.loadNewObject = true; return this.currentRecord; } else { throw new NoSuchElementException(); } } /** * Open a SAIF archive, extracting compressed archives to a temporary * directory. */ @Override public void open() { if (!this.opened) { this.opened = true; try { if (log.isDebugEnabled()) { log.debug("Opening SAIF archive '" + this.file.getCanonicalPath() + "'"); } if (this.file.isDirectory()) { this.saifArchiveDirectory = this.file; } else if (!this.file.exists()) { throw new IllegalArgumentException("SAIF file " + this.file + " does not exist"); } else { this.zipFile = new ZipFile(this.file); } if (log.isDebugEnabled()) { log.debug(" Finished opening archive"); } loadSchema(); loadExportedObjects(); loadSrid(); final GeometryFactory geometryFactory = GeometryFactory.fixed(this.srid, 1.0, 1.0, 1.0); for (final RecordDefinition recordDefinition : ((RecordDefinitionFactoryImpl)this.recordDefinitionFactory) .getRecordDefinitions()) { final FieldDefinition geometryField = recordDefinition.getGeometryField(); if (geometryField != null) { geometryField.setProperty(FieldProperties.GEOMETRY_FACTORY, geometryFactory); } } } catch (final IOException e) { throw new RuntimeException(e.getMessage(), e); } } } /** * Open the iterator for the next object set. * * @return True if an object set iterator was loaded. */ private boolean openNextObjectSet() { try { closeCurrentReader(); if (this.typePathIterator == null) { this.typePathIterator = this.typePaths.iterator(); } if (this.typePathIterator.hasNext()) { do { final String typePath = this.typePathIterator.next(); if (hasData(typePath)) { this.osnReader = getOsnReader(typePath, this.factory); this.osnReader.setFactory(this.factory); if (this.osnReader.hasNext()) { return true; } } } while (this.typePathIterator.hasNext()); } } catch (final IOException e) { throw new RuntimeException(e.getMessage(), e); } this.osnReader = null; return false; } protected Record readObject(final String className, final RecordFactory factory) throws IOException { final OsnReader reader = getOsnReader(className, factory); try { final Record object = reader.next(); return object; } finally { reader.close(); } } /** * Removing SAIF objects is not supported. * * @throws UnsupportedOperationException */ @Override public void remove() { throw new UnsupportedOperationException("Removing SAIF objects is not supported"); } /** * Set the schema definition declared in the SAIF archive. * * @param declaredSchema The schema definition. */ public void setDeclaredRecordDefinitionFactory( final RecordDefinitionFactory declaredRecordDefinitionFactory) { this.declaredRecordDefinitionFactory = declaredRecordDefinitionFactory; } /** * @param excludeTypeNames the excludeTypeNames to set */ public void setExcludeTypeNames(final Collection<String> excludeTypeNames) { this.excludeTypeNames.clear(); for (final String typePath : excludeTypeNames) { this.excludeTypeNames.add(String.valueOf(typePath)); } } /** * @param factory the factory to set */ public void setFactory(final RecordFactory factory) { this.factory = factory; } public void setFile(final File file) { this.file = file; } /** * @param includeTypeNames the includeTypeNames to set */ public void setIncludeTypeNames(final Collection<String> includeTypeNames) { this.includeTypeNames.clear(); for (final String typePath : includeTypeNames) { this.includeTypeNames.add(String.valueOf(typePath)); } } /** * Set the schema definition that will be set on each data object. * * @param schema The schema definition. */ public void setRecordDefinitionFactory(final RecordDefinitionFactory recordDefinitionFactory) { if (recordDefinitionFactory != null) { this.recordDefinitionFactory = recordDefinitionFactory; } else { this.recordDefinitionFactory = this.declaredRecordDefinitionFactory; } } public void setSrid(final int srid) { this.srid = srid; } /** * @param typePathObjectSetMap the typePathObjectSetMap to set */ public void setTypeNameFileNameMap(final Map<String, String> typePathObjectSetMap) { this.typePathFileNameMap.clear(); this.fileNameTypeNameMap.clear(); for (final Entry<String, String> entry : typePathObjectSetMap.entrySet()) { final String key = entry.getKey(); final String value = entry.getValue(); this.typePathFileNameMap.put(key, value); this.fileNameTypeNameMap.put(value, key); } } @Override public String toString() { return this.file.getAbsolutePath(); } }