/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2013, Geomatys * * 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.geotoolkit.db.session; import java.sql.Connection; import java.sql.SQLException; import java.util.*; import java.util.logging.Level; import org.apache.sis.feature.FeatureExt; import org.geotoolkit.data.FeatureIterator; import org.geotoolkit.data.FeatureStoreContentEvent; import org.geotoolkit.data.query.QueryBuilder; import org.geotoolkit.data.session.AddDelta; import org.geotoolkit.data.session.DefaultSession; import org.geotoolkit.data.session.Delta; import org.geotoolkit.data.session.ModifyDelta; import org.geotoolkit.data.session.RemoveDelta; import org.geotoolkit.data.session.Session; import org.geotoolkit.db.DefaultJDBCFeatureStore; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.version.Version; import org.opengis.feature.Feature; import org.opengis.util.GenericName; import org.opengis.filter.Filter; import org.opengis.filter.Id; import org.opengis.filter.identity.Identifier; /** * Provide JDBC transaction support for asynchrone sessions. * * @author Johann Sorel (Geomatys) * @author Quentin Boileau (Geomatys) */ public class JDBCSession extends DefaultSession { /** * Arbitrary max number of ID in a Filter used to remove Features. * This is to avoid "stack depth limit exceeded" exception when * removing a lot of Features in a single SQL query. */ private static final int MAX_ID_IN_REQUEST = 5000; private Connection transaction = null; public JDBCSession(DefaultJDBCFeatureStore store, boolean async, Version version) { super(store, async, version); } @Override protected AddDelta createAddDelta(Session session, String typeName, Collection<? extends Feature> features) { if(isAsynchrone()){ return new JDBCAddDelta(session, typeName, features); }else{ return super.createAddDelta(session, typeName, features); } } @Override protected ModifyDelta createModifyDelta(Session session, String typeName, Id filter, Map<String, ? extends Object> values) { if(isAsynchrone()){ return new JDBCModifyDelta(session, typeName, filter, values); }else{ return super.createModifyDelta(session, typeName, filter, values); } } @Override protected RemoveDelta createRemoveDelta(Session session, String typeName, Id filter) { if(isAsynchrone()){ return new JDBCRemoveDelta(session, typeName, filter); }else{ return super.createRemoveDelta(session, typeName, filter); } } @Override public DefaultJDBCFeatureStore getFeatureStore() { return (DefaultJDBCFeatureStore) super.getFeatureStore(); } @Override public synchronized void commit() throws DataStoreException { final List<Delta> deltas = getDiff().getDeltas(); final Set<GenericName> deltaChanges = new HashSet<>(); for (Delta delta : deltas) { deltaChanges.add(store.getFeatureType(delta.getType()).getName()); } getDiff().commit(store); //everything is ok, close transaction and diff if(transaction!=null){ try { transaction.commit(); } catch (SQLException ex) { throw new DataStoreException(ex.getMessage(),ex); } } closeTransaction(); fireSessionChanged(); for (GenericName deltaChange : deltaChanges) { ((DefaultJDBCFeatureStore)store).forwardContentEvent(FeatureStoreContentEvent.createUpdateEvent(store, deltaChange, null)); } } public synchronized Connection getTransaction() throws DataStoreException{ if(transaction==null){ try{ transaction = getFeatureStore().getDataSource().getConnection(); transaction.setAutoCommit(false); }catch(SQLException ex){ throw new DataStoreException(ex.getMessage(), ex); } } return transaction; } @Override public synchronized void rollback() { super.rollback(); closeTransaction(); } private void closeTransaction(){ if(transaction!=null){ try { transaction.rollback(); } catch (SQLException ex) { getFeatureStore().getLogger().log(Level.WARNING, ex.getMessage(), ex); } finally{ try { transaction.close(); } catch (SQLException ex) { getFeatureStore().getLogger().log(Level.WARNING, ex.getMessage(), ex); } } transaction = null; } } /** * {@inheritDoc } * Override here split remove query in order to avoid * "stack depth limit exceeded" exception. */ @Override public void removeFeatures(final String groupName, final Filter filter) throws DataStoreException { checkVersion(); //will raise an error if the name doesn't exist store.getFeatureType(groupName); if(isAsynchrone()){ List<Id> removeIdFilters = new ArrayList<Id>(); //split Id filter if(filter instanceof Id) { final Id removed = (Id)filter; if (removed.getIDs().size() > MAX_ID_IN_REQUEST) { Set<Identifier> identifiers = new HashSet<Identifier>(); for (Identifier id : removed.getIdentifiers()) { identifiers.add(id); //flush in list of filters if (identifiers.size() == MAX_ID_IN_REQUEST) { removeIdFilters.add(FF.id(identifiers)); identifiers.clear(); } } if(!identifiers.isEmpty()) { removeIdFilters.add(FF.id(identifiers)); } } else { removeIdFilters.add(removed); } } else { Set<Identifier> identifiers = new HashSet<Identifier>(); final QueryBuilder qb = new QueryBuilder(groupName); qb.setFilter(filter); final FeatureIterator ite = getFeatureIterator(qb.buildQuery()); try{ while(ite.hasNext()){ identifiers.add(FeatureExt.getId(ite.next())); //flush in list of filters if (identifiers.size() == MAX_ID_IN_REQUEST) { removeIdFilters.add(FF.id(identifiers)); identifiers.clear(); } } if(!identifiers.isEmpty()) { removeIdFilters.add(FF.id(identifiers)); } }finally{ ite.close(); } if(removeIdFilters.isEmpty()){ //no feature match this filter, no need to create to remove delta return; } } for (final Id removeIdFilter : removeIdFilters) { getDiff().add(createRemoveDelta(this, groupName, removeIdFilter)); } fireSessionChanged(); }else{ store.removeFeatures(groupName, filter); } } }