/****************************************************************************** * * Copyright 2014 Paphus Solutions Inc. * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ package org.botlibre.knowledge.database; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import org.botlibre.Bot; import org.botlibre.api.knowledge.Data; import org.botlibre.api.knowledge.Network; import org.botlibre.api.knowledge.Relationship; import org.botlibre.api.knowledge.Vertex; import org.botlibre.knowledge.AbstractNetwork; import org.botlibre.knowledge.BasicVertex; import org.botlibre.knowledge.Primitive; import org.botlibre.knowledge.Property; import org.botlibre.thought.consciousness.Consciousness; import org.eclipse.persistence.jpa.JpaEntityManager; import org.eclipse.persistence.sessions.UnitOfWork; /** * Network using JPA to access a relational database. */ public class DatabaseNetwork extends AbstractNetwork { private Boolean trackAccessCount; private EntityManager entityManager; /** Cache the size query result. */ private int size = -1; public DatabaseNetwork(EntityManager entityManager, boolean isShortTerm) { super(isShortTerm); this.entityManager = entityManager; entityManager.unwrap(UnitOfWork.class).setProperty("network", this); } public EntityManager getEntityManager() { return entityManager; } public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } protected void addRelationship(Relationship relationship) { this.entityManager.persist(relationship); } protected boolean trackAccessCount() { if (trackAccessCount == null) { trackAccessCount = this.bot.mind().getThought(Consciousness.class).isEnabled(); } return trackAccessCount; } /** * Resume the network after a save. * Keep the MAX_SIZE number of most conscious vertices in memory. */ public void resume() { this.bot.log(this, "Resuming", Bot.FINE, this); if (!this.bot.mind().getThought(Consciousness.class).isEnabled()) { clear(); return; } Set<Vertex> oldVerticies = new HashSet<Vertex>(allActive()); // Shrink to fixed size. int level = 1; while ((oldVerticies.size() > MAX_SIZE) && (level < 256)) { Iterator<Vertex> iterator = oldVerticies.iterator(); while ((oldVerticies.size() > MAX_SIZE) && iterator.hasNext()) { Vertex vertex = iterator.next(); if ((!vertex.isPrimitive()) && vertex.getConsciousnessLevel() <= level) { iterator.remove(); } } level = level * 2; } clear(); for (Vertex oldVertex : oldVerticies) { Vertex newVertex = findById(oldVertex.getId()); if (newVertex != null) { // Can be null if save failed. newVertex.setConsciousnessLevel(oldVertex.getConsciousnessLevel()); if (newVertex.hasData()) { this.verticiesByData.put(newVertex.getData(), newVertex); } } } } /** * Execute and commit the native query. */ public int executeNativeQuery(String sql) { this.bot.log(this, "SQL", Bot.FINE, sql); int rowCount = 0; synchronized (this.bot.memory()) { try { this.entityManager.getTransaction().begin(); rowCount = this.entityManager.createNativeQuery(sql).executeUpdate(); this.entityManager.getTransaction().commit(); resetSize(); } catch (RuntimeException failed) { this.bot.log(this, failed); if (this.entityManager.getTransaction().isActive()) { this.entityManager.getTransaction().rollback(); } // If commit fails, clear short-term memory to avoid // repeated failures. clear(); throw failed; } } ((DatabaseNetwork)this.bot.memory().getLongTermMemory()).resetSize(); return rowCount; } /** * Execute and commit the update query. */ public int executeQuery(String jpql) { this.bot.log(this, "JPQL", Level.FINE, jpql); int rowCount = 0; synchronized (this.bot.memory()) { try { this.entityManager.getTransaction().begin(); rowCount = this.entityManager.createQuery(jpql).executeUpdate(); this.entityManager.getTransaction().commit(); resetSize(); } catch (RuntimeException failed) { this.bot.log(this, failed); if (this.entityManager.getTransaction().isActive()) { this.entityManager.getTransaction().rollback(); } // If commit fails, clear short-term memory to avoid // repeated failures. clear(); throw failed; } } ((DatabaseNetwork)this.bot.memory().getLongTermMemory()).resetSize(); return rowCount; } /** * Commit memory to the database. */ public void save() { this.bot.log(this, "Saving", Level.FINE); //, newObjects); synchronized (this.bot.memory()) { try { this.entityManager.getTransaction().begin(); this.entityManager.getTransaction().commit(); resetSize(); } catch (RuntimeException failed) { this.bot.log(this, failed); if (this.entityManager.getTransaction().isActive()) { this.entityManager.getTransaction().rollback(); } // If commit fails, clear short-term memory to avoid // repeated failures. clear(); throw failed; } } ((DatabaseNetwork)this.bot.memory().getLongTermMemory()).resetSize(); //newObjects = new HashSet<Vertex>(); } public void resetSize() { this.size = -1; } public synchronized void clear() { this.entityManager.clear(); this.verticiesByData.clear(); resetSize(); this.entityManager.unwrap(UnitOfWork.class).setProperty("network", this); } /** * Add the existing vertex to the network. * Used to load an existing vertex, createVertex must be used to create a new one. */ public synchronized void addVertex(Vertex vertex) { vertex.setNetwork(this); if (vertex.hasData() && (vertex.getData() instanceof Data)) { this.entityManager.persist(vertex.getData()); } this.entityManager.persist(vertex); if (vertex.hasData()) { this.verticiesByData.put(vertex.getData(), vertex); } //newObjects.add(vertex); } /** * Save the property setting to the current transaction. */ public void saveProperty(String propertyName, String value, boolean startup) { Property property = this.entityManager.find(Property.class, propertyName); if (property != null) { property.setValue(value); property.setStartup(startup); } else { property = new Property(propertyName, value, startup); this.entityManager.persist(property); } super.saveProperty(propertyName, value, startup); } /** * Remove the property setting to the current transaction. */ public void removeProperty(String propertyName) { Property property = this.entityManager.find(Property.class, propertyName); if (property != null) { this.entityManager.remove(property); } super.removeProperty(propertyName); } public Network getParent() { return null; } public void setParent(Network parent) { } public int size() { if (!this.entityManager.isOpen()) { return 0; } if (isShortTerm()) { return allActive().size(); } if (this.size == -1) { this.size = ((Number)this.entityManager.createQuery("Select count(v) from Vertex v").getSingleResult()).intValue(); } return this.size; } /** * Remove the vertex from the network. * Note that the vertex must be no longer referenced by any other vertex in the network. */ public void removeVertex(Vertex vertex) { Vertex managed = findById(vertex.getId()); if (managed == null) { return; } this.entityManager.remove(managed); if (vertex.hasData()) { this.verticiesByData.remove(vertex.getData()); if (vertex.getData() instanceof Data) { this.entityManager.remove(findData((Data)vertex.getData())); } } } /** * Remove the vertex and all references to it from the network. */ public void removeVertexAndReferences(Vertex vertex) { Vertex managed = findById(vertex.getId()); if (managed == null) { return; } Iterator<Relationship> iterator = findAllRelationshipsTo(vertex).iterator(); // Remove all references. while (iterator.hasNext()) { Relationship relationship = iterator.next(); //if (!((UnitOfWorkImpl)this.entityManager.unwrap(UnitOfWork.class)).isObjectDeleted(relationship.getSource())) { relationship.getSource().internalRemoveRelationship(relationship); //} } removeVertex(vertex); } /** * Remove the relationship from the network. * Note that the relationship must be no longer referenced by any other vertex in the network. */ public void removeRelationship(Relationship relationship) { if (relationship.getId() == null) { return; } Relationship managed = this.entityManager.find(relationship.getClass(), relationship.getId()); if (managed != null) { this.entityManager.remove(managed); } } /** * Return all active vertices. */ @SuppressWarnings("unchecked") public synchronized List<Vertex> allActive() { UnitOfWork unitOfWork = this.entityManager.unwrap(JpaEntityManager.class).getUnitOfWork(); try { return unitOfWork.getIdentityMapAccessor().getAllFromIdentityMap(null, BasicVertex.class, null, null); } catch (Exception exception) { return new ArrayList<Vertex>(); } } public void setHints(Query query) { } /** * Return all vertices. */ public synchronized List<Vertex> findAll() { return findAll(1000, 0); } /** * Return all vertices. */ @SuppressWarnings("unchecked") public synchronized List<Vertex> findAll(int pageSize, int page) { if (isShortTerm()) { return allActive(); } Query query = this.entityManager.createQuery("Select v from Vertex v"); setHints(query); query.setFirstResult(page * pageSize); query.setMaxResults(pageSize); return query.getResultList(); } /** * Return all vertices matching the filter. */ public synchronized List<Vertex> findAllLike(String filter) { return findAllLike(filter, 1000, 0); } /** * Return all vertices matching the filter. */ @SuppressWarnings("unchecked") public synchronized List<Vertex> findAllLike(String filter, int pageSize, int page) { Query query = null; if (filter.indexOf('*') == -1) { query = this.entityManager.createQuery("Select v from Vertex v where v.dataValue = :filter"); query.setParameter("filter", filter); } else { query = this.entityManager.createQuery("Select v from Vertex v where v.dataValue like :filter"); query.setParameter("filter", filter.replace('*', '%')); } setHints(query); query.setFirstResult(page * pageSize); query.setMaxResults(pageSize); return query.getResultList(); } /** * Return all vertices matching the JPQL query. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public synchronized List<Vertex> findAllQuery(String jpql, Map parameters, int pageSize, int page) { try { Query query = this.entityManager.createQuery(jpql); setHints(query); query.setFirstResult(pageSize * page); query.setMaxResults(pageSize); for (Map.Entry parameter : (Set<Map.Entry>)parameters.entrySet()) { query.setParameter((String)parameter.getKey(), parameter.getValue()); } return query.getResultList(); } catch (Exception badQuery) { this.bot.log(this, badQuery); return new ArrayList<Vertex>(); } } /** * Return all vertices matching the JPQL query. */ public synchronized List<Vertex> findAllQuery(String jpql) { return findAllQuery(jpql, 1000); } /** * Return all vertices. */ public synchronized int countAll() { Query query = this.entityManager.createQuery("Select count(v) from Vertex v"); setHints(query); return ((Number)query.getSingleResult()).intValue(); } /** * Return all vertices matching the filter. */ public synchronized int countAllLike(String filter) { Query query = null; if (filter.indexOf('*') == -1) { query = this.entityManager.createQuery("Select count(v) from Vertex v where v.dataValue = :filter"); query.setParameter("filter", filter); } else { query = this.entityManager.createQuery("Select count(v) from Vertex v where v.dataValue like :filter"); query.setParameter("filter", filter.replace('*', '%')); } setHints(query); return ((Number)query.getSingleResult()).intValue(); } /** * Return all vertices matching the JPQL query. */ @SuppressWarnings("unchecked") public synchronized List<Vertex> findAllQuery(String jpql, int max) { try { Query query = this.entityManager.createQuery(jpql); setHints(query); query.setMaxResults(max); return query.getResultList(); } catch (Exception badQuery) { this.bot.log(this, badQuery); return new ArrayList<Vertex>(); } } /** * Execute the native SQL query. */ @SuppressWarnings("rawtypes") public synchronized List findByNativeQuery(String sql, Class type, int max) { try { Query query = null; if (type == null) { query = this.entityManager.createNativeQuery(sql); } else { query = this.entityManager.createNativeQuery(sql, type); } setHints(query); query.setMaxResults(max); return query.getResultList(); } catch (Exception badQuery) { this.bot.log(this, badQuery); return new ArrayList(); } } /** * Find all relationships related to the vertex or of the vertex relationship type. */ @SuppressWarnings("unchecked") public synchronized List<Relationship> findAllRelationshipsTo(Vertex vertex) { Query query = this.entityManager.createQuery("Select r from Relationship r where r.target = :vertex or r.type = :vertex"); setHints(query); query.setParameter("vertex", vertex); return query.getResultList(); } /** * Find all relationships related to the vertex by the vertex type. */ @SuppressWarnings("unchecked") public synchronized List<Relationship> findAllRelationshipsTo(Vertex vertex, Vertex type) { Query query = this.entityManager.createQuery("Select r from Relationship r where r.target = :vertex and r.type = :type"); setHints(query); query.setParameter("vertex", vertex); query.setParameter("type", type); return query.getResultList(); } /** * Return a query builder. */ public CriteriaBuilder getCriteriaBuilder() { return this.entityManager.getCriteriaBuilder(); } /** * Find all relationships related to the vertex or of the vertex type. */ @SuppressWarnings("unchecked") public synchronized List<Vertex> findAllInstances(Vertex type, Vertex relationship, Calendar start) { Vertex instantiation = findByData(Primitive.INSTANTIATION); Query query = null; if (relationship != null) { if (start == null) { query = this.entityManager.createQuery( "Select distinct v from Vertex v join v.allRelationships r join v.allRelationships r2 where r.target = :type and r.type = :instantiation and r.correctness > 0" + " and r2.type = :relationship and r2.correctness > 0 order by r.creationDate desc"); query.setParameter("relationship", relationship.detach()); } else { query = this.entityManager.createQuery( "Select distinct v from Vertex v join v.allRelationships r join v.allRelationships r2 where r.target = :type and r.type = :instantiation and r.correctness > 0" + " and r2.type = :relationship and r2.correctness > 0 and r2.creationDate >= :start order by r.creationDate desc"); query.setParameter("relationship", relationship.detach()); query.setParameter("start", new Date(start.getTimeInMillis())); } } else { if (start == null) { query = this.entityManager.createQuery("Select distinct v from Vertex v join v.allRelationships r where r.target = :type and r.type = :instantiation and r.correctness > 0 order by r.creationDate desc"); } else { query = this.entityManager.createQuery("Select distinct v from Vertex v join v.allRelationships r where r.target = :type and r.type = :instantiation and r.correctness > 0 and r.creationDate >= :start order by r.creationDate desc"); query.setParameter("start", new Date(start.getTimeInMillis())); } } setHints(query); query.setParameter("instantiation", instantiation); query.setParameter("type", type); query.setMaxResults(1000); return query.getResultList(); } /** * Execute the criteria query. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public synchronized List search(CriteriaQuery criteria, int page, int max) { Query query = this.entityManager.createQuery(criteria); setHints(query); query.setFirstResult(page * max); query.setMaxResults(max); return query.getResultList(); } /** * Return the vertex with the given data. */ public synchronized Vertex findByData(Object data) { if (data == null) { return null; } Vertex vertex = this.verticiesByData.get(data); if (vertex != null) { return vertex; } Query query = this.entityManager.createNamedQuery("findVertexByData"); setHints(query); query.setParameter("data", BasicVertex.convertDataValue(data)); query.setParameter("type", BasicVertex.convertDataType(data)); try { vertex = (Vertex)query.getSingleResult(); if (trackAccessCount()) { vertex.incrementAccessCount(); } this.verticiesByData.put(vertex.getData(), vertex); return vertex; } catch (NoResultException notFound) { return null; } } /** * Return the vertex with the given data. */ @SuppressWarnings("unchecked") public synchronized Vertex findByName(String name) { if (name == null) { return null; } Query query = this.entityManager.createNamedQuery("findVertexByName"); setHints(query); query.setParameter("name", name); List<Vertex> result = query.getResultList(); if (result.isEmpty()) { return null; } else { return result.get(0); } } /** * Return the vertex with the given name. */ public synchronized Vertex findById(Number id) { if (id == null) { return null; } return this.entityManager.find(BasicVertex.class, id); } /** * Return the lob data. */ public synchronized Data findData(Data data) { if (data == null) { return null; } return this.entityManager.find(data.getClass(), data.getId()); } /** * Merge the vertices and relations of the network into this network. */ public synchronized void merge(Network network) { super.merge(network); this.size = -1; } }