public void write() throws IOException {
   if (live == null) {
     throw new IOException("No current feature to write");
   }
   if (live.equals(origional)) {
     writeImplementation(origional);
   } else {
     writeImplementation(live);
     if (origional != null) {
       ReferencedEnvelope bounds = new ReferencedEnvelope();
       bounds.include(live.getBounds());
       bounds.include(origional.getBounds());
       store.listenerManager.fireFeaturesChanged(
           live.getFeatureType().getTypeName(), Transaction.AUTO_COMMIT, bounds, false);
     } else {
       store.listenerManager.fireFeaturesAdded(
           live.getFeatureType().getTypeName(),
           Transaction.AUTO_COMMIT,
           ReferencedEnvelope.reference(live.getBounds()),
           false);
     }
   }
   origional = null;
   live = null;
 }
  /**
   * Creates the resultant features.
   *
   * @param splitGeometries List with the new geometries.
   * @param feature The old feature.
   * @throws OperationNotFoundException
   * @throws TransformException
   */
  private List<SimpleFeature> createSplitFeatures(
      final List<Geometry> splitGeometries, final SimpleFeature feature)
      throws OperationNotFoundException, TransformException {

    final SimpleFeatureType featureType = feature.getFeatureType();
    final CoordinateReferenceSystem featureCrs = featureType.getCoordinateReferenceSystem();

    Class<? extends Geometry> geometryType =
        (Class<? extends Geometry>) featureType.getGeometryDescriptor().getType().getBinding();

    List<SimpleFeature> splitFeatureList = new LinkedList<SimpleFeature>();
    for (Geometry splittedPart : splitGeometries) {

      splittedPart = GeoToolsUtils.reproject(splittedPart, desiredCRS, featureCrs);

      splittedPart = GeometryUtil.adapt(splittedPart, geometryType);
      SimpleFeature newFeature = DataUtilities.template(featureType);
      GeoToolsUtils.copyAttributes(feature, newFeature);
      newFeature.setDefaultGeometry(splittedPart);

      splitFeatureList.add(newFeature);
    }

    return splitFeatureList;
  }
  /** @see org.geotools.data.FeatureStore#setFeatures(org.geotools.data.FeatureReader) */
  public void setFeatures(FeatureReader<SimpleFeatureType, SimpleFeature> reader)
      throws IOException {
    WFSTransactionState ts = null;

    if (trans == Transaction.AUTO_COMMIT) {
      ts = new WFSTransactionState(ds);
    } else {
      ts = (WFSTransactionState) trans.getState(ds);
    }

    ts.addAction(
        getSchema().getTypeName(), new DeleteAction(getSchema().getTypeName(), Filter.INCLUDE));

    ReferencedEnvelope bounds = null;
    while (reader.hasNext()) {

      try {
        SimpleFeature f = reader.next();
        List<AttributeDescriptor> atrs = f.getFeatureType().getAttributeDescriptors();
        for (int i = 0; i < atrs.size(); i++) {
          if (atrs.get(i) instanceof GeometryDescriptor) {
            Geometry g = (Geometry) f.getAttribute(i);
            CoordinateReferenceSystem cs =
                ((GeometryDescriptor) atrs.get(i)).getCoordinateReferenceSystem();
            if (cs != null && !cs.getIdentifiers().isEmpty())
              g.setUserData(cs.getIdentifiers().iterator().next().toString());
            if (g == null) continue;
            if (bounds == null) {
              bounds = new ReferencedEnvelope(g.getEnvelopeInternal(), cs);
            } else {
              bounds.expandToInclude(g.getEnvelopeInternal());
            }
          }
        }
        ts.addAction(getSchema().getTypeName(), new InsertAction(f));
      } catch (NoSuchElementException e) {
        WFS_1_0_0_DataStore.LOGGER.warning(e.toString());
      } catch (IllegalAttributeException e) {
        WFS_1_0_0_DataStore.LOGGER.warning(e.toString());
      }
    }

    // Fire a notification.
    // JE
    if (bounds == null) {
      // if bounds are null then send an envelope to say that features were added but
      // at an unknown location.
      bounds = new ReferencedEnvelope(getSchema().getCoordinateReferenceSystem());
      ((WFS_1_0_0_DataStore) getDataStore())
          .listenerManager.fireFeaturesRemoved(
              getSchema().getTypeName(), getTransaction(), bounds, false);
    } else {
      ((WFS_1_0_0_DataStore) getDataStore())
          .listenerManager.fireFeaturesRemoved(
              getSchema().getTypeName(), getTransaction(), bounds, false);
    }
    if (trans == Transaction.AUTO_COMMIT) {
      ts.commit();
    }
  }
  @Test
  public void testGetFeaturesFeatureSource() throws Exception {
    // check the schemas in feature source and feature collection
    SimpleFeatureSource fs = rts.getFeatureSource(RENAMED);
    assertEquals(primitive, fs.getSchema());
    SimpleFeatureCollection fc = fs.getFeatures();
    assertEquals(primitive, fc.getSchema());
    assertTrue(fc.size() > 0);

    // make sure the feature schema is good as well
    FeatureIterator<SimpleFeature> it = fc.features();
    SimpleFeature sf = it.next();
    it.close();

    assertEquals(primitive, sf.getFeatureType());

    // check the feature ids have been renamed as well
    assertTrue(
        "Feature id has not been renamed, it's still " + sf.getID(),
        sf.getID().startsWith(RENAMED));

    // check mappings occurred
    assertEquals("description-f001", sf.getAttribute("description"));
    assertTrue(
        new WKTReader()
            .read("MULTIPOINT(39.73245 2.00342)")
            .equalsExact((Geometry) sf.getAttribute("pointProperty")));
    assertEquals(new Long(155), sf.getAttribute("intProperty"));
    assertNull(sf.getAttribute("newProperty"));
  }
  /**
   * Analyze the feature list, and for those features that can suffer split operation, they'll be
   * split.
   *
   * @return The builder instance.
   * @throws SplitFeatureBuilderFailException if the operation fail
   * @throws CannotSplitException if the split line cannot divide the feature's geometry
   */
  public SplitFeatureBuilder buildSplit()
      throws SplitFeatureBuilderFailException, CannotSplitException {

    try {
      this.splitResultList = new LinkedList<SimpleFeature>();
      boolean existSplit = false;
      for (SimpleFeature feature : this.featureList) {

        Geometry geomToSplit = (Geometry) feature.getDefaultGeometry();
        assert geomToSplit.isValid() : "No Valid Geometry: " + geomToSplit.toText(); // $NON-NLS-1$
        CoordinateReferenceSystem featureCrs =
            feature.getFeatureType().getCoordinateReferenceSystem();
        geomToSplit = GeoToolsUtils.reproject(geomToSplit, featureCrs, this.desiredCRS);

        if (canSplit(geomToSplit)) {
          existSplit = true;
          this.featuresThatSufferedSplit.add(feature);

          List<Geometry> splitGeometriesResult = split(geomToSplit);
          this.splitResultList.addAll(createSplitFeatures(splitGeometriesResult, feature));
        }
      }
      if (!existSplit) {
        throw new CannotSplitException("The split line cannot split any features"); // $NON-NLS-1$
      }
    } catch (OperationNotFoundException e) {
      throw makeFailException(e);
    } catch (TransformException e) {
      throw makeFailException(e);
    }
    return this;
  }
  /**
   * Analyzes the feature list, take the features that are neighbour and if they meet the
   * requirements, add a vertex to them.
   *
   * @return The builder instance.
   * @throws SplitFeatureBuilderFailException
   */
  public SplitFeatureBuilder buildNeighbours() throws SplitFeatureBuilderFailException {

    this.neighbourResultList = new ArrayList<SimpleFeature>();
    try {
      for (SimpleFeature feature : featureList) {

        Geometry geom = (Geometry) feature.getDefaultGeometry();
        CoordinateReferenceSystem featureCRS =
            feature.getFeatureType().getCoordinateReferenceSystem();
        geom = GeoToolsUtils.reproject(geom, featureCRS, desiredCRS);

        if (!canSplit(geom) && (requireVertex(geom))) {

          Geometry geomWithAddedVertex = addVertexToNeighbour(geom);
          geomWithAddedVertex =
              GeoToolsUtils.reproject(geomWithAddedVertex, desiredCRS, featureCRS);
          feature.setDefaultGeometry(geomWithAddedVertex);
          this.neighbourResultList.add(feature);
        }
      }
    } catch (Exception e) {
      throw makeFailException(e);
    }
    return this;
  }
  @SuppressWarnings("unchecked")
  public void XtestGetSelection() throws Exception {

    SimpleFeatureType featureType = feature1.getFeatureType();
    List<SimpleFeature> newFeatures = new ArrayList<SimpleFeature>();
    for (int i = 5; i < 50; i++) {
      SimpleFeature f =
          SimpleFeatureBuilder.build(
              featureType,
              new Object[] {"feature" + i, i},
              "feature" + i); // $NON-NLS-1$ //$NON-NLS-2$
      newFeatures.add(f);
    }

    features.addAll(newFeatures);
    while (Display.getCurrent().readAndDispatch()) ;

    assertSelected(35, false);
    assertSelected(0, false);
    assertSelected(48, false);

    Table table2 = table.getViewer().getTable();
    table2.setTopIndex(0);

    // now test without reveal
    selectFeature(40, false);
    assertEquals(0, table2.getTopIndex());

    // test with reveal
    selectFeature(30, true);

    assertEquals(30, table2.getTopIndex());
  }
Пример #8
0
 @Override
 public SimpleFeature next() {
   SimpleFeature next = super.next();
   String fid = ((SimpleFeature) next).getID();
   if (fid.startsWith(fidPrefix)) {
     fid = fid.substring(fidPrefix.length());
   }
   return new FidAndFtOverrideFeature(next, fid, next.getFeatureType());
 }
  static SimpleFeature retype(SimpleFeature source, SimpleFeatureBuilder builder)
      throws IllegalAttributeException {
    SimpleFeatureType target = builder.getFeatureType();
    for (int i = 0; i < target.getAttributeCount(); i++) {
      AttributeDescriptor attributeType = target.getDescriptor(i);
      Object value = null;

      if (source.getFeatureType().getDescriptor(attributeType.getName()) != null) {
        value = source.getAttribute(attributeType.getName());
      }

      builder.add(value);
    }

    FeatureId id = reTypeId(source.getIdentifier(), source.getFeatureType(), target);
    SimpleFeature retyped = builder.buildFeature(id.getID());
    retyped.getUserData().putAll(source.getUserData());
    return retyped;
  }
  /**
   * Handles the feature creation/update/deletion strategy. You may want to override this in a
   * subclass to handle workflow.
   *
   * @param feature the feature that is about to be split
   * @param geoms new geometries that are the result of splitting
   * @param filter filter to get at feature
   * @param localStore the store we're working against
   * @param localStrategy the strategy in use
   * @return A list of FeatureIds is returned, one for each feature in the order created. However,
   *     these might not be assigned until after a commit has been performed.
   * @throws Exception if an error occurs modifying the data source, converting the geometry or an
   *     illegal argument was given
   */
  protected List<FeatureId> handleStrategy(
      SimpleFeature feature,
      List<? extends Geometry> geoms,
      Filter filter,
      SimpleFeatureStore localStore,
      String localStrategy)
      throws Exception {

    List<SimpleFeature> newFeats = new ArrayList();
    GeometryTypeConverterFactory cf = new GeometryTypeConverterFactory();
    Converter c =
        cf.createConverter(
            Geometry.class,
            localStore.getSchema().getGeometryDescriptor().getType().getBinding(),
            null);
    GeometryType type = localStore.getSchema().getGeometryDescriptor().getType();
    String geomAttribute = localStore.getSchema().getGeometryDescriptor().getLocalName();
    boolean firstFeature = true;
    for (Geometry newGeom : geoms) {
      if (firstFeature) {
        if (localStrategy.equalsIgnoreCase("replace")) {
          // use first/largest geom to update existing feature geom
          feature.setAttribute(geomAttribute, c.convert(newGeom, type.getBinding()));
          feature = this.handleExtraData(feature);
          Object[] attributevalues =
              feature.getAttributes().toArray(new Object[feature.getAttributeCount()]);
          AttributeDescriptor[] attributes =
              feature
                  .getFeatureType()
                  .getAttributeDescriptors()
                  .toArray(new AttributeDescriptor[feature.getAttributeCount()]);
          localStore.modifyFeatures(attributes, attributevalues, filter);
          firstFeature = false;
          continue;
        } else if (localStrategy.equalsIgnoreCase("add")) {
          // delete the source feature, new ones will be created
          localStore.removeFeatures(filter);
          firstFeature = false;
        } else {
          throw new IllegalArgumentException(
              "Unknown strategy '" + localStrategy + "', cannot split");
        }
      }
      // create + add new features
      SimpleFeature newFeat =
          DataUtilities.createFeature(
              feature.getType(), DataUtilities.encodeFeature(feature, false));
      newFeat.setAttribute(geomAttribute, c.convert(newGeom, type.getBinding()));
      newFeats.add(newFeat);
    }
    newFeats = this.handleExtraData(newFeats);
    return localStore.addFeatures(DataUtilities.collection(newFeats));
  }
Пример #11
0
  /**
   * Returns 0 if the features are the same. 1 if the attributes are the same but have different
   * featureIDs and -1 if attributes are different or are of different featureTypes.
   *
   * @param feature1
   * @param feature2
   * @return
   */
  public static int same(SimpleFeature feature1, SimpleFeature feature2) {
    if (DataUtilities.compare(feature1.getFeatureType(), feature2.getFeatureType()) != 0) {
      return -1;
    }
    for (int i = 0; i < feature1.getAttributeCount(); i++) {
      if (feature1.getAttribute(i) == null) {
        if (feature2.getAttribute(i) != null) return -1;
        else continue;
      }
      if (feature1.getAttribute(i) instanceof Geometry) {
        Geometry geom1 = (Geometry) feature1.getAttribute(i);
        if (feature2.getAttribute(i) instanceof Geometry) {
          Geometry geom2 = (Geometry) feature2.getAttribute(i);
          if (geom1.equalsExact(geom2)) continue;
          else return -1;
        } else return -1;
      }
      if (!feature1.getAttribute(i).equals(feature2.getAttribute(i))) return -1;
    }

    return feature1.getID().equals(feature2.getID()) ? 0 : 1;
  }
  @Test
  public void testModify() throws Exception {
    final Query queryAll = new Query(RENAMED);

    SimpleFeatureStore store;
    store = (SimpleFeatureStore) rts.getFeatureSource(RENAMED);
    SimpleFeature original = store.getFeatures(fidFilter).features().next();

    // test a non mapped attribute
    String newDescription = ((String) original.getAttribute("description")) + " xxx";
    store.modifyFeatures(
        original.getFeatureType().getDescriptor("description"), newDescription, fidFilter);
    SimpleFeature modified = store.getFeatures(fidFilter).features().next();
    assertEquals(newDescription, modified.getAttribute("description"));

    // test a mapped attribute
    MultiPoint mpo = (MultiPoint) original.getAttribute("pointProperty");
    MultiPoint mpm = mpo.getFactory().createMultiPoint(new Coordinate[] {new Coordinate(10, 12)});
    store.modifyFeatures(original.getFeatureType().getDescriptor("pointProperty"), mpm, fidFilter);
    modified = store.getFeatures(fidFilter).features().next();
    assertTrue(mpm.equalsExact((Geometry) modified.getAttribute("pointProperty")));
  }
  @Test
  public void testGetFeaturesReader() throws Exception {
    FeatureReader<SimpleFeatureType, SimpleFeature> fr;
    fr = rts.getFeatureReader(new Query(RENAMED), Transaction.AUTO_COMMIT);
    SimpleFeature sf = fr.next();
    fr.close();

    assertEquals(primitive, sf.getFeatureType());

    // check the feature ids have been renamed as well
    assertTrue(
        "Feature id has not been renamed, it's still " + sf.getID(),
        sf.getID().startsWith(RENAMED));
  }
 public void remove() throws IOException {
   if (live == null) {
     throw new IOException("No current feature to remove");
   }
   if (origional != null) {
     store.listenerManager.fireFeaturesRemoved(
         live.getFeatureType().getTypeName(),
         Transaction.AUTO_COMMIT,
         ReferencedEnvelope.reference(origional.getBounds()),
         false);
   }
   origional = null;
   live = null; // prevent live and remove from being written out
 }
  public List<FeatureId> addFeatures(FeatureCollection<SimpleFeatureType, SimpleFeature> collection)
      throws IOException {
    List<FeatureId> addedFids = new LinkedList<FeatureId>();
    String typeName = getSchema().getTypeName();
    SimpleFeature feature = null;
    SimpleFeature newFeature;
    FeatureWriter<SimpleFeatureType, SimpleFeature> writer =
        getDataStore().getFeatureWriterAppend(typeName, getTransaction());

    Iterator iterator = collection.iterator();
    try {
      while (iterator.hasNext()) {
        try {
          feature = (SimpleFeature) iterator.next();
        } catch (Exception e) {
          throw new DataSourceException("Could not add Features, problem with provided reader", e);
        }

        newFeature = (SimpleFeature) writer.next();

        try {
          // JD: we may have a case that the source feature type does not
          // match exactly the target feature type, so build attributes
          // based oin target
          // newFeature.setAttributes(feature.getAttributes(null));
          Object[] attributes = new Object[newFeature.getAttributeCount()];
          for (int i = 0; i < attributes.length; i++) {
            AttributeDescriptor type = newFeature.getFeatureType().getDescriptor(i);
            attributes[i] = feature.getAttribute(type.getLocalName());
          }
          newFeature.setAttributes(attributes);

        } catch (IllegalAttributeException writeProblem) {
          throw new DataSourceException(
              "Could not create " + typeName + " out of provided feature: " + feature.getID(),
              writeProblem);
        }

        writer.write();
        addedFids.add(newFeature.getIdentifier());
      }
    } finally {
      collection.close(iterator);
      writer.close();
    }

    return addedFids;
  }
    private boolean intersects(SimpleFeature feature) {
      GeometryDescriptor geomDescriptor = getGeometryAttDescriptor(feature.getFeatureType());

      Geometry bboxGeom = new GeometryFactory().toGeometry(bbox);

      Geometry geom = (Geometry) feature.getAttribute(geomDescriptor.getName());

      try {
        return geom.intersects(bboxGeom);
      } catch (Exception e) {
        // ok so exception happened during intersection.  This usually means geometry is a little
        // crazy
        // what to do?...
        EditPlugin.log("Can't do intersection so I'm assuming they intersect", e); // $NON-NLS-1$
        return false;
      }
    }
Пример #17
0
 public static WritableGeometry convertToGeometry(SimpleFeature feature) {
   // Translate the SimpleFeature geometry to JTS geometry
   com.vividsolutions.jts.geom.Geometry jtsGeometry =
       (com.vividsolutions.jts.geom.Geometry) feature.getDefaultGeometry();
   // Then generate a MrGeo WritableGeometry from the JTS geometry
   WritableGeometry geom = GeometryFactory.fromJTS(jtsGeometry);
   // Now copy over the attributes to the MrGeo WritableGeometry
   List<AttributeDescriptor> descriptors = feature.getFeatureType().getAttributeDescriptors();
   for (AttributeDescriptor desc : descriptors) {
     String localName = desc.getLocalName();
     Object attrValue = feature.getAttribute(localName);
     if (attrValue != null) {
       String strValue = attrValue.toString();
       geom.setAttribute(localName, strValue);
     }
   }
   return geom;
 }
  /**
   * A new feature type using the attribute names of all joined feature type.
   *
   * @return a new feature type
   * @throws IllegalAttributeException
   * @throws SchemaException
   */
  public SimpleFeature getFeature() throws IllegalAttributeException, SchemaException {

    SimpleFeatureType unionFeatureType = getFeatureType();
    // adds the attributes values
    Object[] attrList = new Object[unionFeatureType.getAttributeCount()];
    for (SimpleFeature feature : this.unionFeatures) {

      SimpleFeatureType featureType = feature.getFeatureType();
      for (int j = 0; j < featureType.getAttributeCount(); j++) {

        // gets the attribute value
        AttributeDescriptor attrType = featureType.getDescriptor(j);
        if (!(attrType instanceof GeometryDescriptor)) {

          Object attrValue = feature.getAttribute(j);

          // gets the position in the union
          String unionAttrName =
              findAttributeName(featureType.getTypeName(), attrType.getLocalName());

          int unionAttrPosition = unionFeatureType.indexOf(unionAttrName);

          // set the value in union
          attrList[unionAttrPosition] = attrValue;
        }
      }
    }
    // creates the new feature
    SimpleFeatureBuilder builder = new SimpleFeatureBuilder(unionFeatureType);
    builder.addAll(attrList);
    // SimpleFeature product = unionFeatureType.create(attrList);
    SimpleFeature product = builder.buildFeature(null);
    product.setDefaultGeometry(this.geometry);

    return product;
  }
Пример #19
0
  /**
   * Executes the export operation using the parameters that have been specified.
   *
   * @return a FeatureCollection with the specified features
   */
  @Override
  protected SimpleFeatureStore _call() {
    final ObjectDatabase database = objectDatabase();
    if (filterFeatureTypeId != null) {
      RevObject filterType = database.getIfPresent(filterFeatureTypeId);
      checkArgument(
          filterType instanceof RevFeatureType, "Provided filter feature type is does not exist");
    }

    final SimpleFeatureStore targetStore = getTargetStore();

    final String refspec = resolveRefSpec();
    final String treePath = refspec.substring(refspec.indexOf(':') + 1);
    final RevTree rootTree = resolveRootTree(refspec);
    final NodeRef typeTreeRef = resolTypeTreeRef(refspec, treePath, rootTree);

    final ObjectId defaultMetadataId = typeTreeRef.getMetadataId();

    final RevTree typeTree = database.getTree(typeTreeRef.getObjectId());

    final ProgressListener progressListener = getProgressListener();

    progressListener.started();
    progressListener.setDescription(
        "Exporting from " + path + " to " + targetStore.getName().getLocalPart() + "... ");

    final Iterator<SimpleFeature> filtered;
    {
      final Iterator<SimpleFeature> plainFeatures =
          getFeatures(typeTree, database, defaultMetadataId, progressListener);

      Iterator<SimpleFeature> adaptedFeatures = adaptToArguments(plainFeatures, defaultMetadataId);

      Iterator<Optional<Feature>> transformed =
          Iterators.transform(adaptedFeatures, ExportOp.this.function);

      Iterator<SimpleFeature> result =
          Iterators.filter(
              Iterators.transform(
                  transformed,
                  new Function<Optional<Feature>, SimpleFeature>() {
                    @Override
                    public SimpleFeature apply(Optional<Feature> input) {
                      return (SimpleFeature) input.orNull();
                    }
                  }),
              Predicates.notNull());

      // check the resulting schema has something to contribute
      PeekingIterator<SimpleFeature> peekingIt = Iterators.peekingIterator(result);
      if (peekingIt.hasNext()) {
        Function<AttributeDescriptor, String> toString =
            new Function<AttributeDescriptor, String>() {
              @Override
              public String apply(AttributeDescriptor input) {
                return input.getLocalName();
              }
            };
        SimpleFeature peek = peekingIt.peek();
        Set<String> sourceAtts =
            new HashSet<String>(
                Lists.transform(peek.getFeatureType().getAttributeDescriptors(), toString));
        Set<String> targetAtts =
            new HashSet<String>(
                Lists.transform(targetStore.getSchema().getAttributeDescriptors(), toString));
        if (Sets.intersection(sourceAtts, targetAtts).isEmpty()) {
          throw new GeoToolsOpException(
              StatusCode.UNABLE_TO_ADD,
              "No common attributes between source and target feature types");
        }
      }

      filtered = peekingIt;
    }
    FeatureCollection<SimpleFeatureType, SimpleFeature> asFeatureCollection =
        new BaseFeatureCollection<SimpleFeatureType, SimpleFeature>() {

          @Override
          public FeatureIterator<SimpleFeature> features() {

            return new DelegateFeatureIterator<SimpleFeature>(filtered);
          }
        };

    // add the feature collection to the feature store
    final Transaction transaction;
    if (transactional) {
      transaction = new DefaultTransaction("create");
    } else {
      transaction = Transaction.AUTO_COMMIT;
    }
    try {
      targetStore.setTransaction(transaction);
      try {
        targetStore.addFeatures(asFeatureCollection);
        transaction.commit();
      } catch (final Exception e) {
        if (transactional) {
          transaction.rollback();
        }
        Throwables.propagateIfInstanceOf(e, GeoToolsOpException.class);
        throw new GeoToolsOpException(e, StatusCode.UNABLE_TO_ADD);
      } finally {
        transaction.close();
      }
    } catch (IOException e) {
      throw new GeoToolsOpException(e, StatusCode.UNABLE_TO_ADD);
    }

    progressListener.complete();

    return targetStore;
  }
  public void rollback(String toVersion, Filter filter, String[] userIds) throws IOException {
    // TODO: build an optimized version of this that can do the same work with a couple
    // of queries assuming the filter is fully encodable

    Transaction t = getTransaction();
    boolean autoCommit = false;
    if (Transaction.AUTO_COMMIT.equals(t)) {
      t = new DefaultTransaction();
      autoCommit = true;
    }

    // Gather feature modified after toVersion
    ModifiedFeatureIds mfids =
        store.getModifiedFeatureFIDs(schema.getTypeName(), toVersion, null, filter, userIds, t);
    FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);

    // grab the state, we need to mark as dirty all the features we are going to modify/re-insert
    VersionedJdbcTransactionState state = store.wrapped.getVersionedJdbcTransactionState(t);

    // remove all features that have been created and not deleted
    Set fidsToRemove = new HashSet(mfids.getCreated());
    fidsToRemove.removeAll(mfids.getDeleted());
    if (!fidsToRemove.isEmpty()) {
      removeFeatures(store.buildFidFilter(fidsToRemove));
      state.setTypeNameDirty(getSchema().getTypeName());
    }

    // reinstate all features that were there before toVersion and that
    // have been deleted after it. Notice this is an insertion, so to preserve
    // the fids I have to use low level writers where I can set all attributes manually
    // (we work on the assumption the wrapped data store maps all attributes of the primary
    // key in the feature itself)
    Set fidsToRecreate = new HashSet(mfids.getDeleted());
    fidsToRecreate.removeAll(mfids.getCreated());
    if (!fidsToRecreate.isEmpty()) {
      state.setTypeNameDirty(getSchema().getTypeName());
      state.setFidsDirty(getSchema().getTypeName(), fidsToRecreate);

      long revision = store.wrapped.getVersionedJdbcTransactionState(t).getRevision();
      Filter recreateFilter =
          store.buildVersionedFilter(
              schema.getTypeName(), store.buildFidFilter(fidsToRecreate), mfids.fromRevision);
      FeatureReader<SimpleFeatureType, SimpleFeature> fr = null;
      FeatureWriter<SimpleFeatureType, SimpleFeature> fw = null;
      try {
        DefaultQuery q = new DefaultQuery(schema.getTypeName(), recreateFilter);
        fr = store.wrapped.getFeatureReader(q, t);
        fw = store.wrapped.getFeatureWriterAppend(schema.getTypeName(), t);
        while (fr.hasNext()) {
          SimpleFeature original = fr.next();
          SimpleFeature restored = fw.next();
          for (int i = 0; i < original.getFeatureType().getAttributeCount(); i++) {
            restored.setAttribute(i, original.getAttribute(i));
          }
          restored.setAttribute("revision", new Long(revision));
          restored.setAttribute("expired", new Long(Long.MAX_VALUE));
          fw.write();
        }
      } catch (IllegalAttributeException iae) {
        throw new DataSourceException(
            "Unexpected error occurred while " + "restoring deleted featues", iae);
      } finally {
        if (fr != null) fr.close();
        if (fw != null) fw.close();
      }
    }

    // Now onto the modified features, that were there, and still are there.
    // Since we cannot get a sorted writer we have to do a kind of inner loop scan
    // (note, a parellel scan of similarly sorted reader and writer would be more
    // efficient, but writer sorting is not there...)
    // Here it's possible to work against the external API, thought it would be more
    // efficient (but more complex) to work against the wrapped one.
    if (!mfids.getModified().isEmpty()) {
      state.setTypeNameDirty(getSchema().getTypeName());
      state.setFidsDirty(getSchema().getTypeName(), mfids.getModified());

      Filter modifiedIdFilter = store.buildFidFilter(mfids.getModified());
      Filter mifCurrent =
          store.buildVersionedFilter(schema.getTypeName(), modifiedIdFilter, new RevisionInfo());
      FeatureReader<SimpleFeatureType, SimpleFeature> fr = null;
      FeatureWriter<SimpleFeatureType, SimpleFeature> fw = null;
      try {
        fw = store.getFeatureWriter(schema.getTypeName(), mifCurrent, t);
        while (fw.hasNext()) {
          SimpleFeature current = fw.next();
          Filter currIdFilter = ff.id(Collections.singleton(ff.featureId(current.getID())));
          Filter cidToVersion =
              store.buildVersionedFilter(schema.getTypeName(), currIdFilter, mfids.fromRevision);
          DefaultQuery q = new DefaultQuery(schema.getTypeName(), cidToVersion);
          q.setVersion(mfids.fromRevision.toString());
          fr = store.getFeatureReader(q, t);
          SimpleFeature original = fr.next();
          for (int i = 0; i < original.getFeatureType().getAttributeCount(); i++) {
            current.setAttribute(i, original.getAttribute(i));
          }
          fr.close();
          fw.write();
        }
      } catch (IllegalAttributeException iae) {
        throw new DataSourceException(
            "Unexpected error occurred while " + "restoring deleted featues", iae);
      } finally {
        if (fr != null) fr.close();
        if (fw != null) fw.close();
      }
    }

    // if it's auto commit, don't forget to actually commit
    if (autoCommit) {
      t.commit();
      t.close();
    }
  }