/**
  * Notifies the caller the requested operation is not supported, using a plain {@link
  * UnsupportedOperationException} in case we have to conceal the fact the data is actually
  * writable, using an Spring security exception otherwise to force an authentication from the user
  */
 protected RuntimeException unsupportedOperation() {
   String typeName = getSchema().getName().getLocalPart();
   if (policy.response == Response.CHALLENGE) {
     return SecureCatalogImpl.unauthorizedAccess(typeName);
   } else {
     return new UnsupportedOperationException(typeName + " is read only");
   }
 }
 @Override
 public DataAccess<? extends FeatureType, ? extends Feature> getDataStore(
     ProgressListener listener) throws IOException {
   final DataAccess<? extends FeatureType, ? extends Feature> ds = super.getDataStore(listener);
   if (ds == null) return null;
   else if (policy.level == AccessLevel.METADATA)
     throw SecureCatalogImpl.unauthorizedAccess(this.getName());
   else
     return (DataAccess<? extends FeatureType, ? extends Feature>)
         SecuredObjects.secure(ds, policy);
 }
  public List<FeatureId> addFeatures(FeatureCollection<T, F> collection) throws IOException {
    Query writeQuery = getWriteQuery(policy);

    if (writeQuery.getFilter() == Filter.EXCLUDE
        || writeQuery.getPropertyNames() == Query.NO_NAMES) {
      throw unsupportedOperation();
    } else if (writeQuery == Query.ALL) {
      // make sure it behaves like native when no write limits are imposed (even if this
      // case makes no sense, we should not wrap at all)
      return storeDelegate.addFeatures(collection);
    } else {
      // check if any of the inserted features does not pass the write filters
      if (writeQuery.getFilter() != null && writeQuery.getFilter() != Filter.INCLUDE) {
        final FilteringFeatureCollection<T, F> filtered =
            new FilteringFeatureCollection<T, F>(collection, writeQuery.getFilter());
        if (filtered.size() < collection.size()) {
          String typeName = getSchema().getName().getLocalPart();
          if (policy.response == Response.CHALLENGE) {
            throw SecureCatalogImpl.unauthorizedAccess(typeName);
          } else {
            throw new UnsupportedOperationException(
                "At least one of the features inserted does not satisfy your write restrictions");
          }
        }
      }

      // deal with writable properties
      if (writeQuery.getPropertyNames() == Query.ALL_NAMES) {
        return storeDelegate.addFeatures(collection);
      } else {
        if (collection.getSchema() instanceof SimpleFeatureType
            && storeDelegate instanceof SimpleFeatureStore) {
          // see if the user specified the value of any attribute she cannot write
          final SimpleFeatureCollection simpleCollection = (SimpleFeatureCollection) collection;

          // wrap it with a collection that will check if any non writable attribute has
          // been given a value
          List<String> writableAttributes = Arrays.asList(writeQuery.getPropertyNames());
          CheckAttributesFeatureCollection checker =
              new CheckAttributesFeatureCollection(
                  simpleCollection, writableAttributes, policy.getResponse());
          return ((SimpleFeatureStore) storeDelegate).addFeatures(checker);
        } else {
          // TODO: add retyping to shave off attributes we cannot write
          LOGGER.log(
              Level.SEVERE,
              "Unfinished implementation, we need to shave off "
                  + "the attributes one cannot write off complex features. "
                  + "However at this time there is no writable complex feature!");
          return storeDelegate.addFeatures(collection);
        }
      }
    }
  }
  public void modifyFeatures(Name[] names, Object[] values, Filter filter) throws IOException {
    // are we limiting anything?
    Query writeQuery = getWriteQuery(policy);
    if (writeQuery == Query.ALL) {
      storeDelegate.modifyFeatures(names, values, filter);
    } else if (writeQuery.getFilter() == Filter.EXCLUDE
        || writeQuery.getPropertyNames() == Query.NO_NAMES) {
      throw unsupportedOperation();
    }

    // get the mixed filter
    final Query local = new Query(null, filter);
    Query mixed = mixQueries(local, writeQuery);

    if (writeQuery.getPropertyNames() == Query.ALL_NAMES) {
      // it was just a matter of filtering.
      storeDelegate.modifyFeatures(names, values, mixed.getFilter());
    } else {
      // get the writable attribute set
      Set<String> queryNames = new HashSet<String>(Arrays.asList(writeQuery.getPropertyNames()));

      // check the update fields
      for (int i = 0; i < names.length; i++) {
        final String localName = names[i].getLocalPart();
        if (queryNames.contains(localName)) {
          String typeName = getSchema().getName().getLocalPart();
          if (policy.getResponse() == Response.CHALLENGE) {
            throw SecureCatalogImpl.unauthorizedAccess(typeName);
          } else {
            throw new UnsupportedOperationException(
                "Trying to write on the write protected attribute " + names[i]);
          }
        }
      }

      storeDelegate.modifyFeatures(names, values, mixed.getFilter());
    }
  }