/**
* Copyright (C) 2012 52°North Initiative for Geospatial Open Source Software GmbH
*
* 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 org.n52.sos.db.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import org.n52.oxf.valueDomains.time.ITimePosition;
import org.n52.oxf.valueDomains.time.TimeConverter;
import org.n52.sos.SosSoe;
import org.n52.sos.dataTypes.ContactDescription;
import org.n52.sos.dataTypes.ServiceDescription;
import org.n52.sos.db.AccessGDB;
import org.n52.sos.db.AccessGdbForAnalysis;
import org.n52.sos.db.AccessGdbForFeatures;
import org.n52.sos.db.AccessGdbForObservations;
import org.n52.sos.db.AccessGdbForOfferings;
import org.n52.sos.db.AccessGdbForProcedures;
import org.n52.sos.db.InsertGdbForObservations;
import org.n52.util.logging.Logger;
import com.esri.arcgis.carto.IMapServer3;
import com.esri.arcgis.carto.IMapServerDataAccess;
import com.esri.arcgis.datasourcesGDB.SdeWorkspaceFactory;
import com.esri.arcgis.datasourcesGDB.SqlWorkspace;
import com.esri.arcgis.geodatabase.Feature;
import com.esri.arcgis.geodatabase.FeatureClass;
import com.esri.arcgis.geodatabase.Fields;
import com.esri.arcgis.geodatabase.ICursor;
import com.esri.arcgis.geodatabase.IFeature;
import com.esri.arcgis.geodatabase.IFeatureClass;
import com.esri.arcgis.geodatabase.IFeatureCursor;
import com.esri.arcgis.geodatabase.IFields;
import com.esri.arcgis.geodatabase.IMemoryRelationshipClassFactory;
import com.esri.arcgis.geodatabase.IObjectClass;
import com.esri.arcgis.geodatabase.IObjectClassProxy;
import com.esri.arcgis.geodatabase.IRelQueryTableFactory;
import com.esri.arcgis.geodatabase.IRelationshipClass;
import com.esri.arcgis.geodatabase.IRow;
import com.esri.arcgis.geodatabase.ISpatialFilter;
import com.esri.arcgis.geodatabase.ITable;
import com.esri.arcgis.geodatabase.IWorkspaceFactory;
import com.esri.arcgis.geodatabase.MemoryRelationshipClassFactory;
import com.esri.arcgis.geodatabase.RelQueryTableFactory;
import com.esri.arcgis.geodatabase.SpatialFilter;
import com.esri.arcgis.geodatabase.Workspace;
import com.esri.arcgis.geodatabase.esriFieldType;
import com.esri.arcgis.geodatabase.esriRelCardinality;
import com.esri.arcgis.geodatabase.esriSpatialRelEnum;
import com.esri.arcgis.geometry.IGeometry;
import com.esri.arcgis.interop.AutomationException;
import com.esri.arcgis.server.json.JSONObject;
import com.esri.arcgis.system.ServerUtilities;
/**
* This class is responsible for the database connection and access.
*
* @author <a href="mailto:broering@52north.org">Arne Broering</a>
* @author <a href="mailto:j.schulte@52north.org">Jan Schulte</a>
*
*/
public class AccessGDBImpl implements AccessGDB {
static Logger LOGGER = Logger.getLogger(AccessGDBImpl.class.getName());
private SosSoe sos;
private Properties props;
private int maxNumberOfResults;
private ServiceDescription serviceDescription;
private AccessGdbForObservations observationAccess;
private AccessGdbForFeatures featureAccess;
private AccessGdbForProcedures procedureAccess;
private AccessGdbForOfferings offeringAccess;
private AccessGdbForAnalysis analysisAccess;
private InsertGdbForObservations observationInsert;
private String databaseName;
private WorkspaceWrapper workspaceWrapper;
/**
* Creates an AccessObservationGDB object and connects to the DB specified
* in the arcGisSosLocal.properties file.
*
* This constructor is for testing this class NOT for usage in the ArcGIS
* Server.
*
* @throws AutomationException
* @throws IOException
*/
public AccessGDBImpl() throws AutomationException, IOException {
LOGGER.info("Creating AccessGDBImpl.");
init("/arcGisSosLocal.properties", 0);
// Load property for data access
String dbPath = props.getProperty("database.path");
LOGGER.info("Using database connection '" + dbPath + "'.");
// Workspace creation
IWorkspaceFactory factory = new SdeWorkspaceFactory();// FileGDBWorkspaceFactory();
workspaceWrapper = new WorkspaceWrapper();
workspaceWrapper.setWorkspace(new Workspace(factory.openFromFile(dbPath, 0)));
}
/**
* Creates an AccessObservationGDB object and connects to the DB of the
* ArcGIS MapServer handed over as a parameter.
*
* @throws AutomationException
* @throws IOException
*/
public AccessGDBImpl(SosSoe sos) throws AutomationException, IOException {
LOGGER.info("Creating AccessGDBImpl.");
long start = System.currentTimeMillis();
this.sos = sos;
// Workspace creation
IMapServer3 ms = (IMapServer3) sos.getMapServerDataAccess();
String mapName = ms.getDefaultMapName();
LOGGER.info("Using mapName: "+mapName);
IMapServerDataAccess mapServerDataAccess = sos.getMapServerDataAccess();
LOGGER.info("Using IMapServerDataAccess: "+mapServerDataAccess);
Object dataSource= mapServerDataAccess.getDataSource(mapName, 0);
LOGGER.info("Using dataSource: "+dataSource.getClass());
FeatureClass fc = new FeatureClass(dataSource);
resolveDatabaseName(fc);
Workspace workspace = new Workspace(fc.getWorkspace());
this.workspaceWrapper = new WorkspaceWrapper();
// logConnectionProperties(fc.getWorkspace());
if (fc.getWorkspace() instanceof SqlWorkspace) {
this.workspaceWrapper.setSqlWorkspace((SqlWorkspace) fc.getWorkspace());
this.workspaceWrapper.setWorkspace(workspace);
}
else {
// SqlWorkspaceFactory fac = new SqlWorkspaceFactory();
// SqlWorkspace sqlW = (SqlWorkspace) fac.open(fc.getWorkspace().getConnectionProperties(), fc.getWorkspace().getType());
// workspace = new Workspace(sqlW);
// this.workspaceWrapper.setSqlWorkspace(sqlW);
this.workspaceWrapper.setWorkspace(workspace);
}
LOGGER.info("workspace: "+this.workspaceWrapper.toString());
init("/arcGisSos.properties", sos.getMaximumRecordCount());
long delta = System.currentTimeMillis() - start;
LOGGER.info("End of creating AccessGDBImpl. Created in " + delta/1000 + " seconds.");
}
// private void logConnectionProperties(IWorkspace workspace) throws IOException {
// Object[] target1 = new Object[1];
// Object[] target2 = new Object[1];
// workspace.getConnectionProperties().getAllProperties(target1, target2);
//
// int i = 0;
// for (Object object : target1) {
// LOGGER.info(i +"a="+createPresentation(object));
// i++;
// }
//
// i = 0;
// for (Object object : target2) {
// LOGGER.info(i +"b="+createPresentation(object));
// i++;
// }
// }
//
// private String createPresentation(Object object) {
// StringBuilder sb = new StringBuilder();
// if (object == null) {
// return "n/a";
// }
// if (object instanceof CharSequence) {
// return object.toString();
// }
// if (object instanceof Object[]) {
// sb.append("[");
// for (Object o : (Object[]) object) {
// sb.append(o);
// sb.append(", ");
// }
// sb.append("]");
// return sb.toString();
// }
// return object.toString();
// }
private void resolveDatabaseName(FeatureClass fc) throws IOException {
String name = fc.getName();
int lastIndex = name.lastIndexOf(".");
this.databaseName = name.substring(0, lastIndex).trim();
LOGGER.info("databaseName = "+this.databaseName);
}
/**
* initialization of local variables.
*
* @param propsResourceName
* @param maxRecords
* @throws IOException
*/
public void init(String propsResourceName, int maxRecords) throws IOException {
props = new Properties();
// Load properties
props.load(AccessGDBImpl.class.getResourceAsStream(propsResourceName));
// init the table names for accessing the geodatabase:
Table.initTableNames(props, this.databaseName);
// init the field names:
SubField.initSubfieldNames(props);
// init maxNumberOfResults:
if (maxRecords == 0) {
maxNumberOfResults = Integer.parseInt(props.getProperty("database.maxNumberOfResults"));
} else {
maxNumberOfResults = maxRecords;
}
observationAccess = new AccessGdbForObservationsImpl(this);
featureAccess = new AccessGdbForFeaturesImpl(this);
procedureAccess = new AccessGdbForProceduresImpl(this);
offeringAccess = new AccessGdbForOfferingsImpl(this);
observationInsert = new InsertGdbForObservationsImpl(this);
}
@Override
public String getDatabaseName() {
return this.databaseName;
}
/**
* This method can be used to retrieve a {@link ServiceDescription} of the
* SOS.
*
* @throws IOException
*/
public ServiceDescription getServiceDescription() throws IOException
{
if (this.serviceDescription == null) {
LOGGER.verbose("Creating new ServiceDescription.");
String title = sos.getSosTitle();
String description = sos.getSosDescription();
String[] keywordArray = sos.getSosKeywords().split(",");
String providerName = sos.getSosProviderName();
String providerSite = sos.getSosProviderSite();
String individualName = sos.getSosContactPersonName();
String positionName = sos.getSosContactPersonPosition();
String phone = sos.getSosContactPersonPhone();
String facsimile = sos.getSosContactPersonFax();
String deliveryPoint = sos.getSosContactPersonAddress();
String city = sos.getSosContactPersonCity();
String administrativeArea = sos.getSosContactPersonAdminArea();
String postalCode = sos.getSosContactPersonPostalCode();
String country = sos.getSosContactPersonCountry();
String electronicMailAddress = sos.getSosContactPersonEmail();
ContactDescription serviceContact = new ContactDescription(individualName, positionName, phone, facsimile, deliveryPoint, city, administrativeArea, postalCode, country, electronicMailAddress);
// request procedure IDs
List<String> procedureIdList = procedureAccess.getProcedureIdList();
this.serviceDescription = new ServiceDescription(title, description, keywordArray, providerName, providerSite, new ContactDescription[]{serviceContact}, procedureIdList);
}
return this.serviceDescription;
}
@Override
public AccessGdbForProcedures getProcedureAccess() {
return procedureAccess;
}
@Override
public AccessGdbForFeatures getFeatureAccess() {
return featureAccess;
}
@Override
public AccessGdbForObservations getObservationAccess() {
return observationAccess;
}
@Override
public AccessGdbForOfferings getOfferingAccess() {
return offeringAccess;
}
@Override
public AccessGdbForAnalysis getAnalysisAccess() {
return analysisAccess;
}
@Override
public InsertGdbForObservations getObservationInsert() {
return observationInsert;
}
///////////////////////////////
// //////////////////////////// Helper Methods:
///////////////////////////////
public static ITimePosition createTimePosition(Object startValue)
{
ITimePosition timePosition = null;
if (startValue != null && startValue instanceof Date) {
return TimeConverter.createTimePosition((Date) startValue);
}
return timePosition;
}
/**
* This method creates a comma separated string of a list of string entries
*
* @param list
* List of entries
* @return a comma separated string
*/
public static String createCommaSeparatedList(List<String> list)
{
StringBuilder sb = new StringBuilder();
for (String entry : list) {
sb.append(entry);
sb.append(",");
}
return sb.substring(0, sb.length() - 1);
}
public static String createCommaSeparatedList(String... list) {
return createCommaSeparatedList(Arrays.asList(list));
}
/**
* This method creates an or-clause out of a given field and a list of
* strings, where all entries of the list will be compared with the field.
*
* @param field
* a database field
* @param list
* a String array representing the list of values that are
* compared with the field.
* @return the created or-clause
*/
public static String createOrClause(String field,
String[] list)
{
StringBuilder sb = new StringBuilder();
sb.append("(");
for (String entry : list) {
sb.append(field);
sb.append(" = '");
sb.append(entry);
sb.append("'");
sb.append(" OR ");
}
sb.delete(sb.length() - 4, sb.length());
sb.append(")");
return sb.toString();
}
/**
* This method creates an or-clause out of a given field and a Collection of
* Integers, where all Integer values will be compared with the field.
*
* @param field
* a database field
* @param list
* a Collection of Integers representing the list of values that
* are compared with the field.
* @return the created or-clause
*/
public static String createOrClause(String field,
Collection<Integer> list)
{
StringBuilder sb = new StringBuilder();
sb.append("(");
for (Integer entry : list) {
sb.append(field + " = '" + entry + "'");
sb.append(" OR ");
}
sb.delete(sb.length() - 4, sb.length());
sb.append(")");
return sb.toString();
}
/**
*
* @param spatialFilter
* @return
* @throws Exception
*/
protected Collection<String> queryFeatureIDsForSpatialFilter(String spatialFilter) throws IOException
{
IGeometry geometry;
try {
geometry = ServerUtilities.getGeometryFromJSON(new JSONObject(spatialFilter));
} catch (Exception e) {
throw new IOException(e);
}
IFeatureClass features = workspaceWrapper.getWorkspace().openFeatureClass(Table.FEATUREOFINTEREST);
ISpatialFilter spatialQuery = new SpatialFilter();
spatialQuery.setGeometryByRef(geometry);
spatialQuery.setGeometryField(features.getShapeFieldName());
spatialQuery.setSpatialRel(esriSpatialRelEnum.esriSpatialRelIntersects);
spatialQuery.setSubFields(SubField.FEATUREOFINTEREST_ID);
IFeatureCursor featureCursor = features.search(spatialQuery, true);
IFeature feature = featureCursor.nextFeature();
List<String> featureList = new ArrayList<String>();
while (feature != null) {
featureList.add((String)feature.getValue(0));
feature = featureCursor.nextFeature();
}
return featureList;
}
/**
* Concatenates names of a database table and field with a '.'.
*/
public static String concatTableAndField(String table,
String field)
{
return String.format("%s.%s", table, field);
}
/**
* @return table1.field1 = table2.field2
*/
public static String join(String table1, String field1, String table2, String field2) {
return String.format("%s = %s", concatTableAndField(table1, field1), concatTableAndField(table2, field2));
}
public static String innerJoin(String table1, String field1, String table2, String field2) {
StringBuilder sb = new StringBuilder();
sb.append("INNER JOIN ");
sb.append(table2);
sb.append(" ON ");
sb.append(join(table1, field1, table2, field2));
return sb.toString();
}
/**
*
* @param targetTable
* @param joinTable
* @param fromField
* @param toField
* @return
* @throws Exception
*/
public static ITable createRelQueryTable(ITable targetTable,
ITable joinTable,
String fromField,
String toField) throws Exception
{
// Build a memory relationship class.
IMemoryRelationshipClassFactory memRelClassFactory = new MemoryRelationshipClassFactory();
// Create object class for the tables since table cannot be cast to
// object class
IObjectClass targetClass = new IObjectClassProxy(targetTable);
IObjectClass joinClass = new IObjectClassProxy(joinTable);
IRelationshipClass relationshipClass = memRelClassFactory.open("MemRelClass", targetClass, fromField, joinClass, toField, "forward", "backward", esriRelCardinality.esriRelCardinalityOneToOne);
// Open the RelQueryTable as a feature class.
IRelQueryTableFactory rqtFactory = new RelQueryTableFactory();
ITable relQueryTable = (ITable) rqtFactory.open(relationshipClass, true, null, null, "", false, false);
return relQueryTable;
}
/**
*
* @param cursor
* @throws AutomationException
* @throws IOException
*/
protected void printOut(ICursor cursor) throws AutomationException, IOException
{
// Get the number of fields in the feature class
Fields fields = (Fields) cursor.getFields();
int fieldCount = fields.getFieldCount();
// Use the feature cursor to iterate over all elements in the feature
// class,
// printing the values of the fields. All field types are shown here.
// Simple
// values are printed as Strings. Complex elements are shown as the type
// name.
IRow feature = (IRow) cursor.nextRow();
while (feature != null) {
LOGGER.debug("");
LOGGER.debug("#################################");
for (int index = 0; index < fieldCount; index++) {
String tmp = fields.getField(index).getName();
int fieldType = feature.getFields().getField(index).getType();
switch (fieldType) {
case esriFieldType.esriFieldTypeDate:
case esriFieldType.esriFieldTypeDouble:
case esriFieldType.esriFieldTypeGlobalID:
case esriFieldType.esriFieldTypeGUID:
case esriFieldType.esriFieldTypeInteger:
case esriFieldType.esriFieldTypeOID:
case esriFieldType.esriFieldTypeSingle:
case esriFieldType.esriFieldTypeSmallInteger:
case esriFieldType.esriFieldTypeString:
tmp = tmp + " = " + feature.getValue(index);
break;
case esriFieldType.esriFieldTypeBlob:
tmp = tmp + " = (blob)";
break;
case esriFieldType.esriFieldTypeGeometry:
tmp = tmp + " = (geometry)";
break;
case esriFieldType.esriFieldTypeRaster:
tmp = tmp + " = (raster)";
break;
}
LOGGER.debug(tmp);
}
feature = (IRow) cursor.nextRow();
}
}
protected WorkspaceWrapper getWorkspace() {
return workspaceWrapper;
}
protected int getMaxNumberOfResults() {
return maxNumberOfResults;
}
/**
* Support method.
*/
public static String featureToString(Feature f) throws AutomationException, IOException
{
String featureAsString = "";
IFields fields = f.getFields();
for (int i = 0; i < fields.getFieldCount(); i++) {
String fieldName = fields.getField(i).getName();
String value = (String) f.getValue(i);
featureAsString += "[" + fieldName + ":->" + value + "]";
}
return featureAsString;
}
@Override
public boolean isResolveGeometriesFromStations() {
return true;
}
}