package mil.nga.giat.geowave.adapter.vector; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import mil.nga.giat.geowave.adapter.vector.util.FeatureDataUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.hadoop.io.Writable; import org.geotools.data.DataUtilities; import org.geotools.feature.SchemaException; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKBReader; import com.vividsolutions.jts.io.WKBWriter; /** * This class is used by FeatureDataAdapter to persist SimpleFeature and its * SimpleFeatureType. The attribute types of the feature must be understood * before the feature can be deserialized so therefore each SimpleFeature * serializes its type. * * NOTE: This class caches feature type information. If the feature type * changes, then the cache should be emptied using the clearCache() method. */ public class FeatureWritable implements Writable, java.io.Serializable { private static final Map<Pair<String, String>, SimpleFeatureType> FeatureTypeCache = new ConcurrentHashMap<Pair<String, String>, SimpleFeatureType>(); /** * */ private static final long serialVersionUID = 286616522680871139L; private SimpleFeatureType featureType; private SimpleFeature feature; public FeatureWritable() {} public FeatureWritable( final SimpleFeatureType featureType ) { this.featureType = featureType; } public FeatureWritable( final SimpleFeatureType featureType, final SimpleFeature feature ) { this.featureType = featureType; this.feature = feature; } public SimpleFeature getFeature() { return feature; } public void setFeature( SimpleFeature feature ) { this.feature = feature; } @Override public void readFields( final DataInput input ) throws IOException { try { String ns = input.readUTF(); featureType = FeatureDataUtils.decodeType( "-".equals(ns) ? "" : ns, input.readUTF(), input.readUTF(), input.readUTF()); } catch (final SchemaException e) { throw new IOException( "Failed to parse the encoded feature type", e); } final SimpleFeatureBuilder builder = new SimpleFeatureBuilder( featureType); // read the fid final String fid = input.readUTF(); // read the other attributes, build the feature for (final AttributeDescriptor ad : featureType.getAttributeDescriptors()) { final Object att = readAttribute( ad, input); builder.add(att); } // build the feature feature = builder.buildFeature(fid); } @Override public void write( final DataOutput output ) throws IOException { output .writeUTF(featureType.getName().getNamespaceURI() == null ? "-" : featureType .getName() .getNamespaceURI()); output.writeUTF(featureType.getTypeName()); output.writeUTF(DataUtilities.encodeType(featureType)); output.writeUTF(FeatureDataUtils.getAxis(featureType.getCoordinateReferenceSystem())); // write feature id output.writeUTF(feature.getID()); // write the attributes for (final AttributeDescriptor ad : featureType.getAttributeDescriptors()) { final Object value = feature.getAttribute(ad.getLocalName()); writeAttribute( output, ad, value); } } static void writeAttribute( final DataOutput output, final AttributeDescriptor ad, final Object value ) throws IOException { if (value == null) { // null marker output.writeBoolean(true); } else { // not null, write the contents. This one requires some explanation. // We are not writing any type metadata in the stream for the types // we can optimize (primitives, numbers, strings and the like). This // means we have to be 100% sure the class we're writing is actually // the one we can optimize for, and not some subclass. Thus, we are // authorized to use identity comparison instead of isAssignableFrom // or equality, when we read back it must be as if we did not // serialize stuff at all output.writeBoolean(false); final Class<?> binding = ad.getType().getBinding(); if (binding == Boolean.class) { output.writeBoolean((Boolean) value); } else if ((binding == Byte.class) || (binding == byte.class)) { output.writeByte((Byte) value); } else if ((binding == Short.class) || (binding == short.class)) { output.writeShort((Short) value); } else if ((binding == Integer.class) || (binding == int.class)) { output.writeInt((Integer) value); } else if ((binding == Long.class) || (binding == long.class)) { output.writeLong((Long) value); } else if ((binding == Float.class) || (binding == float.class)) { output.writeFloat((Float) value); } else if ((binding == Double.class) || (binding == double.class)) { output.writeDouble((Double) value); } else if (binding == String.class) { output.writeUTF((String) value); } else if ((binding == java.sql.Date.class) || (binding == java.sql.Time.class) || (binding == java.sql.Timestamp.class) || (binding == java.util.Date.class)) { output.writeLong(((Date) value).getTime()); } else if (Geometry.class.isAssignableFrom(binding)) { final WKBWriter writer = new WKBWriter(); final byte[] buffer = writer.write((Geometry) value); final int length = buffer.length; output.writeInt(length); output.write(buffer); } else { // can't optimize, in this case we use an ObjectOutputStream to // write out full metadata final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final ObjectOutputStream oos = new ObjectOutputStream( bos); oos.writeObject(value); oos.flush(); final byte[] bytes = bos.toByteArray(); output.writeInt(bytes.length); output.write(bytes); } } } /** * Reads the attributes. * * @param ad * @return * @throws IOException */ Object readAttribute( final AttributeDescriptor ad, final DataInput input ) throws IOException { final boolean isNull = input.readBoolean(); if (isNull) { return null; } else { final Class<?> binding = ad.getType().getBinding(); if (binding == Boolean.class) { return input.readBoolean(); } else if ((binding == Byte.class) || (binding == byte.class)) { return input.readByte(); } else if ((binding == Short.class) || (binding == short.class)) { return input.readShort(); } else if ((binding == Integer.class) || (binding == int.class)) { return input.readInt(); } else if ((binding == Long.class) || (binding == long.class)) { return input.readLong(); } else if ((binding == Float.class) || (binding == float.class)) { return input.readFloat(); } else if ((binding == Double.class) || (binding == double.class)) { return input.readDouble(); } else if (binding == String.class) { return input.readUTF(); } else if (binding == java.sql.Date.class) { return new java.sql.Date( input.readLong()); } else if (binding == java.sql.Time.class) { return new java.sql.Time( input.readLong()); } else if (binding == java.sql.Timestamp.class) { return new java.sql.Timestamp( input.readLong()); } else if (binding == java.util.Date.class) { return new java.util.Date( input.readLong()); } else if (Geometry.class.isAssignableFrom(binding)) { final WKBReader reader = new WKBReader(); final int length = input.readInt(); final byte[] buffer = new byte[length]; input.readFully(buffer); try { return reader.read(buffer); } catch (final ParseException e) { throw new IOException( "Failed to parse the geometry WKB", e); } } else { final int length = input.readInt(); final byte[] buffer = new byte[length]; input.readFully(buffer); final ByteArrayInputStream bis = new ByteArrayInputStream( buffer); final ObjectInputStream ois = new ObjectInputStream( bis); try { return ois.readObject(); } catch (final ClassNotFoundException e) { throw new IOException( "Could not read back object", e); } } } } private void writeObject( java.io.ObjectOutputStream out ) throws IOException { this.write(out); } private void readObject( java.io.ObjectInputStream in ) throws IOException, ClassNotFoundException { this.readFields(in); } public static final void clearCache() { FeatureTypeCache.clear(); } public static final void cache( SimpleFeatureType featureType ) { final Pair<String, String> id = Pair.of( featureType.getName().getNamespaceURI() == null ? "" : featureType.getName().getNamespaceURI(), featureType.getTypeName()); FeatureTypeCache.put( id, featureType); } }