/* (c) 2013 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.importer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.geotools.data.FeatureReader;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.util.logging.Logging;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import com.vividsolutions.jts.geom.Geometry;
/**
* Converts feature between two feature data sources.
* <p>
* </p>
* @author Justin Deoliveira, OpenGeo
*
*/
public class FeatureDataConverter {
static Logger LOGGER = Logging.getLogger(Importer.class);
private static final Set<String> ORACLE_RESERVED_WORDS;
static {
final HashSet<String> words = new HashSet<String>();
final InputStream wordStream = FeatureDataConverter.class.getResourceAsStream("oracle_reserved_words.txt");
final Reader wordReader = new InputStreamReader(wordStream, Charset.forName("UTF-8"));
final BufferedReader bufferedWordReader = new BufferedReader(wordReader);
String word;
try {
while ((word = bufferedWordReader.readLine()) != null) {
words.add(word);
}
} catch (IOException e) {
throw new RuntimeException("Unable to load Oracle reserved words", e);
} finally {
try {
bufferedWordReader.close();
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Error while closing Oracle reserved words file", e);
}
}
ORACLE_RESERVED_WORDS = Collections.unmodifiableSet(words);
}
/**
* characters we strip out of type names and attribute names because they can't be represented
* as xml
*/
static final Pattern UNSAFE_CHARS = Pattern.compile("(^[^a-zA-Z\\._]+)|([^a-zA-Z\\._0-9]+)");
private FeatureDataConverter() {
}
public SimpleFeatureType convertType(SimpleFeatureType featureType, VectorFormat format,
ImportData data, ImportTask task) {
SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
typeBuilder.setName(convertTypeName(task != null && task.getLayer().getName() != null ? task.getLayer().getName() : featureType.getTypeName()));
AttributeTypeBuilder attBuilder = new AttributeTypeBuilder();
for (AttributeDescriptor att : featureType.getAttributeDescriptors()) {
attBuilder.init(att);
typeBuilder.add(attBuilder.buildDescriptor(convertAttributeName(att.getLocalName())));
}
return typeBuilder.buildFeatureType();
}
public void convert(SimpleFeature from, SimpleFeature to) {
Set<String> fromAttrNames = attributeNames(from);
Set<String> toAttrNames = attributeNames(to);
Set<String> commonNames = new HashSet<String>(fromAttrNames);
commonNames.retainAll(toAttrNames);
for (String attrName : commonNames) {
to.setAttribute(convertAttributeName(attrName), from.getAttribute(attrName));
}
}
protected String convertTypeName(String typeName) {
StringBuilder builder = new StringBuilder();
// instead of allowing prefix digits to 'collapse', prepend a valid char
if (Character.isDigit(typeName.charAt(0))) {
builder.append('_');
}
builder.append(typeName);
return UNSAFE_CHARS.matcher(builder).replaceAll("_");
}
protected String convertAttributeName(String attName) {
return convertTypeName(attName);
}
protected Set<String> attributeNames(SimpleFeature feature) {
List<AttributeDescriptor> attributeDescriptors = feature.getType().getAttributeDescriptors();
Set<String> attrNames = new HashSet<String>(attributeDescriptors.size());
for (AttributeDescriptor attr : attributeDescriptors) {
attrNames.add(attr.getLocalName());
}
return attrNames;
}
public static FeatureDataConverter DEFAULT = new FeatureDataConverter();
public static FeatureDataConverter TO_SHAPEFILE = new FeatureDataConverter() {
@Override
public SimpleFeatureType convertType(SimpleFeatureType featureType, VectorFormat format,
ImportData data, ImportTask item) {
//for shapefile we always ensure the geometry is the first type, and we have to deal
// with the max field name length of 10
SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
typeBuilder.setName(convertTypeName(featureType.getTypeName()));
GeometryDescriptor gd = featureType.getGeometryDescriptor();
if (gd != null) {
Class binding = gd.getType().getBinding();
if (Geometry.class.equals(binding)) {
try {
FeatureReader r = (FeatureReader) format.read(data, item);
try {
if (r.hasNext()) {
SimpleFeature f = (SimpleFeature) r.next();
if (f.getDefaultGeometry() != null) {
binding = f.getDefaultGeometry().getClass();
}
}
}
finally {
r.close();
}
} catch (IOException e) {
LOGGER.warning("Unable to determine concrete geometry type");
}
}
typeBuilder.add(attName(gd.getLocalName()), binding);
}
for (AttributeDescriptor att : featureType.getAttributeDescriptors()) {
if (att.equals(gd)) {
continue;
}
typeBuilder.add(attName(att.getLocalName()), att.getType().getBinding());
}
return typeBuilder.buildFeatureType();
}
@Override
public void convert(SimpleFeature from, SimpleFeature to) {
for (AttributeDescriptor att : from.getType().getAttributeDescriptors()) {
Object obj = from.getAttribute(att.getLocalName());
if (att instanceof GeometryDescriptor) {
to.setDefaultGeometry(obj);
}
else if (containsAttribute(to, attName(att.getLocalName()))) {
to.setAttribute(attName(att.getLocalName()), obj);
}
}
}
String attName(String name) {
name = convertAttributeName(name);
return name.length() > 10 ? name.substring(0,10) : name;
}
private boolean containsAttribute(SimpleFeature ft, String attName) {
for (AttributeDescriptor att : ft.getType().getAttributeDescriptors()) {
if (att.getLocalName().equals(attName)) {
return true;
}
}
return false;
}
};
public static final FeatureDataConverter TO_POSTGIS = new FeatureDataConverter() {
@Override
public SimpleFeatureType convertType(SimpleFeatureType featureType, VectorFormat format,
ImportData data, ImportTask item) {
SimpleFeatureType converted = DEFAULT.convertType(featureType, format, data, item );
String featureTypeName = convertTypeName(featureType.getTypeName());
// trim the length of the name
// by default, postgis table/index names need to fit in 64 characters
// with the "spatial_" prefix and "_geometry" suffix, that leaves 47 chars
// and we should leave room to append integers to make the name unique too
if (featureTypeName.length() > 45) {
SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
featureTypeName = featureTypeName.substring(0, 45);
typeBuilder.setName(featureTypeName);
typeBuilder.addAll(featureType.getAttributeDescriptors());
converted = typeBuilder.buildFeatureType();
}
return converted;
}
};
public static final FeatureDataConverter TO_ORACLE = new FeatureDataConverter() {
public void convert(SimpleFeature from, SimpleFeature to) {
//for oracle the target names are always uppercase
Set<String> fromAttrNames = attributeNames(from);
Set<String> toAttrNames = attributeNames(to);
for (String name : fromAttrNames) {
String toName = name.toUpperCase();
if (toAttrNames.contains(toName)) {
to.setAttribute(convertAttributeName(toName), from.getAttribute(name));
}
}
};
public SimpleFeatureType convertType(SimpleFeatureType featureType, VectorFormat format,
ImportData data, ImportTask task) {
SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
AttributeTypeBuilder attributeBuilder = new AttributeTypeBuilder();
typeBuilder.setName(ensureOracleSafe(featureType.getTypeName()));
for (AttributeDescriptor att : featureType.getAttributeDescriptors()) {
attributeBuilder.init(att);
final String name = (ensureOracleSafe(att.getName().getLocalPart()));
typeBuilder.add(attributeBuilder.buildDescriptor(name));
}
return typeBuilder.buildFeatureType();
}
private final String ensureOracleSafe(final String identifier) {
final String capitalized = convertTypeName(identifier).toUpperCase();
final String notReserved;
if (ORACLE_RESERVED_WORDS.contains(capitalized)) {
notReserved = capitalized + "_";
} else {
notReserved = capitalized;
}
return notReserved;
}
};
}