/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2008-2014, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.data.wfs; import static org.geotools.data.wfs.internal.Loggers.trace; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.namespace.QName; import org.geotools.data.Diff; import org.geotools.data.Transaction; import org.geotools.data.Transaction.State; import org.geotools.data.wfs.WFSDiff.BatchUpdate; import org.geotools.data.wfs.internal.TransactionRequest; import org.geotools.data.wfs.internal.TransactionRequest.Delete; import org.geotools.data.wfs.internal.TransactionRequest.Insert; import org.geotools.data.wfs.internal.TransactionRequest.Update; import org.geotools.data.wfs.internal.TransactionResponse; import org.geotools.data.wfs.internal.WFSClient; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.opengis.feature.Property; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.identity.FeatureId; import org.opengis.filter.identity.Identifier; class WFSRemoteTransactionState implements State { private final WFSDataStore dataStore; private Transaction transaction; private Map<Name, WFSContentState> localStates; public WFSRemoteTransactionState(WFSDataStore dataStore) { this.dataStore = dataStore; this.localStates = new HashMap<Name, WFSContentState>(); } public synchronized WFSDiff getDiff(final Name typeName) { WFSContentState localState = localStates.get(typeName); if (null == localState) { throw new IllegalStateException("Not watching " + typeName); } WFSDiff diff = localState.getLocalTransactionState().getDiff(); return diff; } @Override public void setTransaction(Transaction transaction) { this.transaction = transaction; } @Override public void rollback() throws IOException { clear(); // rollback differences } @Override public void addAuthorization(String AuthID) throws IOException { // not needed? } @Override public synchronized void commit() throws IOException { try { commitInternal(); } finally { // If the commit fails or succeeds, state is reset clear(); } } /** * This state takes ownership of each type's diff, so lets clear them all */ private void clear() { for (WFSContentState localState : localStates.values()) { WFSLocalTransactionState localTransactionState = localState.getLocalTransactionState(); WFSDiff diff = localTransactionState.getDiff(); diff.clear(); } } private void commitInternal() throws IOException { if (this.localStates.isEmpty()) { return; } WFSClient wfs = dataStore.getWfsClient(); TransactionRequest transactionRequest = wfs.createTransaction(); List<MutableFeatureId> requestedInsertFids = new ArrayList<MutableFeatureId>(); for (Name typeName : localStates.keySet()) { List<MutableFeatureId> addedFids = applyDiff(typeName, transactionRequest); requestedInsertFids.addAll(addedFids); } TransactionResponse transactionResponse = wfs.issueTransaction(transactionRequest); List<FeatureId> insertedFids = transactionResponse.getInsertedFids(); int deleteCount = transactionResponse.getDeleteCount(); int updatedCount = transactionResponse.getUpdatedCount(); trace(getClass().getSimpleName(), "::commit(): Updated: ", updatedCount, ", Deleted: ", deleteCount, ", Inserted: ", insertedFids); if (requestedInsertFids.size() != insertedFids.size()) { throw new IllegalStateException("Asked to add " + requestedInsertFids.size() + " Features but got " + insertedFids.size() + " insert results"); } for (int i = 0; i < requestedInsertFids.size(); i++) { MutableFeatureId local = requestedInsertFids.get(i); FeatureId inserted = insertedFids.get(i); local.setID(inserted.getID()); local.setFeatureVersion(inserted.getFeatureVersion()); } } private List<MutableFeatureId> applyDiff(final Name localTypeName, TransactionRequest transactionRequest) throws IOException { final WFSContentState localState = localStates.get(localTypeName); final WFSLocalTransactionState localTransactionState = localState .getLocalTransactionState(); final WFSDiff diff = localTransactionState.getDiff(); List<MutableFeatureId> addedFeatureIds = new LinkedList<MutableFeatureId>(); final QName remoteTypeName = dataStore.getRemoteTypeName(localTypeName); applyBatchUpdates(remoteTypeName, diff, transactionRequest); final Set<String> ignored = diff.getBatchModified(); final SimpleFeatureType remoteType = dataStore.getRemoteSimpleFeatureType(remoteTypeName); // Create a single insert element with all the inserts for this type final Map<String, SimpleFeature> added = diff.getAdded(); if (added.size() > 0) { Insert insert = transactionRequest.createInsert(remoteTypeName); SimpleFeatureBuilder builder = new SimpleFeatureBuilder(remoteType); for (String fid : diff.getAddedOrder()) { if (ignored.contains(fid)) { continue; } SimpleFeature localFeature = added.get(fid); MutableFeatureId addedFid = (MutableFeatureId) localFeature.getIdentifier(); addedFeatureIds.add(addedFid); SimpleFeature remoteFeature = SimpleFeatureBuilder.retype(localFeature, builder); insert.add(remoteFeature); } transactionRequest.add(insert); } final Map<String, SimpleFeature> modified = diff.getModified(); // Create a single delete element with all the deletes for this type Set<Identifier> ids = new LinkedHashSet<Identifier>(); for (Map.Entry<String, SimpleFeature> entry : modified.entrySet()) { if (!(Diff.NULL == entry.getValue())) { continue;// not a delete } String rid = entry.getKey(); if (ignored.contains(rid)) { continue; } Identifier featureId = featureId(rid); ids.add(featureId); } if (!ids.isEmpty()) { Filter deleteFilter = dataStore.getFilterFactory().id(ids); Delete delete = transactionRequest.createDelete(remoteTypeName, deleteFilter); transactionRequest.add(delete); } // Create one update element per modified feature. Batch modified ones should have been // added as a single update element at applyBatchUpdate before for (Map.Entry<String, SimpleFeature> entry : modified.entrySet()) { String fid = entry.getKey(); SimpleFeature feature = entry.getValue(); if (Diff.NULL == feature) { continue;// not an update } if (ignored.contains(fid)) { continue; } applySingleUpdate(remoteTypeName, feature, transactionRequest); } return addedFeatureIds; } private void applySingleUpdate(QName remoteTypeName, SimpleFeature feature, TransactionRequest transactionRequest) throws IOException { // so bad, this is going to update a lot of unnecessary properties. We'd need to make // DiffContentFeatureWriter's live and current attributes protected and extend write so that // it records the truly modified attributes instead Collection<Property> properties = feature.getProperties(); List<QName> propertyNames = new ArrayList<QName>(); List<Object> newValues = new ArrayList<Object>(); for (Property p : properties) { QName attName = new QName(remoteTypeName.getNamespaceURI(), p.getName().getLocalPart()); Object attValue = p.getValue(); propertyNames.add(attName); newValues.add(attValue); } Filter updateFilter = dataStore.getFilterFactory().id( Collections.singleton(feature.getIdentifier())); Update update = transactionRequest.createUpdate(remoteTypeName, propertyNames, newValues, updateFilter); transactionRequest.add(update); } private void applyBatchUpdates(QName remoteTypeName, WFSDiff diff, TransactionRequest transactionRequest) { List<BatchUpdate> batchUpdates = diff.getBatchUpdates(); for (BatchUpdate batch : batchUpdates) { List<QName> propertyNames = new ArrayList<QName>(batch.properties.length); for (Name attName : batch.properties) { propertyNames.add(new QName(remoteTypeName.getNamespaceURI(), attName .getLocalPart())); } List<Object> newValues = Arrays.asList(batch.values); Filter updateFilter = batch.filter; Update update = transactionRequest.createUpdate(remoteTypeName, propertyNames, newValues, updateFilter); transactionRequest.add(update); } } private Identifier featureId(final String rid) { final FilterFactory ff = dataStore.getFilterFactory(); String fid = rid; String featureVersion = null; int versionSeparatorIdx = rid.indexOf(FeatureId.VERSION_SEPARATOR); if (-1 != versionSeparatorIdx) { fid = rid.substring(0, versionSeparatorIdx); featureVersion = rid.substring(versionSeparatorIdx + 1); } FeatureId featureId = ff.featureId(fid, featureVersion); return featureId; } public void watch(WFSContentState localState) { Name typeName = localState.getEntry().getName(); localStates.put(typeName, localState); } }