/** * Copyright (C) 2010-2017 Structr GmbH * * This file is part of Structr <http://structr.org>. * * Structr is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Structr 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Structr. If not, see <http://www.gnu.org/licenses/>. */ package org.structr.bolt; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.neo4j.driver.v1.Record; import org.neo4j.driver.v1.Session; import org.neo4j.driver.v1.StatementResult; import org.neo4j.driver.v1.Transaction; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.Values; import org.neo4j.driver.v1.exceptions.NoSuchRecordException; import org.neo4j.driver.v1.exceptions.TransientException; import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Relationship; import org.structr.api.NativeResult; import org.structr.api.NotFoundException; import org.structr.api.RetryException; import org.structr.api.util.Iterables; import org.structr.bolt.mapper.RecordLongMapper; import org.structr.bolt.mapper.RecordNodeMapper; import org.structr.bolt.mapper.RecordRelationshipMapper; import org.structr.bolt.wrapper.EntityWrapper; import org.structr.bolt.wrapper.StatementResultWrapper; /** * */ public class SessionTransaction implements org.structr.api.Transaction { private final Set<EntityWrapper> modifiedEntities = new HashSet<>(); private BoltDatabaseService db = null; private Session session = null; private Transaction tx = null; private boolean closed = false; private boolean success = false; public SessionTransaction(final BoltDatabaseService db, final Session session) { this.session = session; this.tx = session.beginTransaction(); this.db = db; } @Override public void failure() { tx.failure(); } @Override public void success() { tx.success(); // transaction must be marked successfull explicitely success = true; } @Override public void close() { if (!success) { // We need to invalidate all existing references because we cannot // be sure that they contain the correct values after a rollback. for (final EntityWrapper entity : modifiedEntities) { entity.stale(); } } else { if (!modifiedEntities.isEmpty()) { // data was written, invalidate query cache db.invalidateQueryCache(); } // Invalidate all nodes that are modified in this transaction // so that the relationship caches are rebuilt. for (final EntityWrapper entity : modifiedEntities) { entity.invalidate(); } } // mark this transaction as closed BEFORE trying to actually close it // so that it is closed in case of a failure closed = true; try { tx.close(); session.close(); } catch (TransientException tex) { // transient exceptions can be retried throw new RetryException(tex); } finally { // make sure that the resources are freed if (session.isOpen()) { session.close(); } } } public boolean isClosed() { return closed; } public void setClosed(final boolean closed) { this.closed = closed; } public long getLong(final String statement) { try { if (db.logQueries()) { System.out.println(statement); } return getLong(statement, Collections.EMPTY_MAP); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } } public long getLong(final String statement, final Map<String, Object> map) { try { logQuery(statement, map); return tx.run(statement, map).next().get(0).asLong(); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } } public Object getObject(final String statement, final Map<String, Object> map) { try { logQuery(statement, map); final StatementResult result = tx.run(statement, map); if (result.hasNext()) { return result.next().get(0).asObject(); } } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } return null; } public Entity getEntity(final String statement, final Map<String, Object> map) { try { logQuery(statement, map); return tx.run(statement, map).next().get(0).asEntity(); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } } public Node getNode(final String statement, final Map<String, Object> map) { try { logQuery(statement, map); return tx.run(statement, map).next().get(0).asNode(); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } } public Relationship getRelationship(final String statement, final Map<String, Object> map) { try { logQuery(statement, map); return tx.run(statement, map).next().get(0).asRelationship(); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } } public Iterable<Node> getNodes(final String statement, final Map<String, Object> map) { try { logQuery(statement, map); return Iterables.map(new RecordNodeMapper(), new StatementIterable(tx.run(statement, map))); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } } public Iterable<Relationship> getRelationships(final String statement, final Map<String, Object> map) { try { logQuery(statement, map); return Iterables.map(new RecordRelationshipMapper(), new StatementIterable(tx.run(statement, map))); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } } public Iterable<Long> getIds(final String statement, final Map<String, Object> map) { try { logQuery(statement, map); return Iterables.map(new RecordLongMapper(), new StatementIterable(tx.run(statement, map))); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } } public Iterable<String> getStrings(final String statement, final Map<String, Object> map) { try { logQuery(statement, map); final StatementResult result = tx.run(statement, map); final Record record = result.next(); final Value value = record.get(0); return value.asList(Values.ofString()); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } } public NativeResult run(final String statement, final Map<String, Object> map) { try { logQuery(statement, map); return new StatementResultWrapper(db, tx.run(statement, map)); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } } public void set(final String statement, final Map<String, Object> map) { try { logQuery(statement, map); tx.run(statement, map).consume(); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } catch (NoSuchRecordException nex) { throw new NotFoundException(nex); } } public void logQuery(final String statement) { logQuery(statement, null); } public void logQuery(final String statement, final Map<String, Object> map) { if (db.logQueries()) { if (map != null && map.size() > 0) { System.out.println(statement + "\t\t Parameters: " + map.toString()); } else { System.out.println(statement); } } } public void modified(final EntityWrapper wrapper) { // data was written, invalidate query cache db.invalidateQueryCache(); modifiedEntities.add(wrapper); } private class StatementIterable implements Iterable<Record> { private StatementResult result = null; public StatementIterable(final StatementResult result) { this.result = result; } @Override public Iterator<Record> iterator() { return new Iterator<Record>() { @Override public boolean hasNext() { return result.hasNext(); } @Override public Record next() { try { return result.next(); } catch (TransientException tex) { closed = true; throw new RetryException(tex); } } @Override public void remove() { throw new UnsupportedOperationException("Removal not supported."); } }; } } }