/******************************************************************************* * Mission Control Technologies, Copyright (c) 2009-2012, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * The MCT platform is licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0. * * 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. * * MCT includes source code licensed under additional open source licenses. See * the MCT Open Source Licenses file included with this distribution or the About * MCT Licenses dialog available at runtime from the MCT Help menu for additional * information. *******************************************************************************/ package gov.nasa.arc.mct.dbpersistence.service; import gov.nasa.arc.mct.components.AbstractComponent; import gov.nasa.arc.mct.components.ExtendedProperties; import gov.nasa.arc.mct.components.ModelStatePersistence; import gov.nasa.arc.mct.dbpersistence.access.InternalDBPersistenceAccess; import gov.nasa.arc.mct.dbpersistence.dao.ComponentSpec; import gov.nasa.arc.mct.dbpersistence.dao.DatabaseIdentification; import gov.nasa.arc.mct.dbpersistence.dao.Disciplines; import gov.nasa.arc.mct.dbpersistence.dao.MctUsers; import gov.nasa.arc.mct.dbpersistence.dao.Tag; import gov.nasa.arc.mct.dbpersistence.dao.TagAssociation; import gov.nasa.arc.mct.dbpersistence.dao.TagAssociationPK; import gov.nasa.arc.mct.dbpersistence.dao.ViewState; import gov.nasa.arc.mct.dbpersistence.dao.ViewStatePK; import gov.nasa.arc.mct.dbpersistence.search.QueryResult; import gov.nasa.arc.mct.dbpersistence.service.StepBehindCache.Lookup; import gov.nasa.arc.mct.platform.spi.PersistenceProvider; import gov.nasa.arc.mct.platform.spi.Platform; import gov.nasa.arc.mct.services.internal.component.ComponentInitializer; import gov.nasa.arc.mct.services.internal.component.Updatable; import gov.nasa.arc.mct.services.internal.component.User; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import javax.persistence.Cache; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.NoResultException; import javax.persistence.OptimisticLockException; import javax.persistence.Persistence; import javax.persistence.Query; import javax.persistence.TemporalType; import javax.persistence.TypedQuery; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import org.hibernate.ejb.HibernateEntityManagerFactory; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PersistenceServiceImpl implements PersistenceProvider { private static final Logger LOGGER = LoggerFactory.getLogger(PersistenceServiceImpl.class); private static final String PERSISTENCE_PROPS = "properties/persistence.properties"; private static final String VERSION_PROPS = "properties/version.properties"; private static final JAXBContext propContext; private static final ComponentIdComparator COMPONENT_ID_COMPARATOR = new ComponentIdComparator(); private static final long MINIMUM_POLLING_INTERVAL = 10; // Don't poll more often than 10 ms private static final int DEFAULT_MAX_RESULTS = 100; // Default max search results private final ConcurrentHashMap<String, List<WeakReference<AbstractComponent>>> cache = new ConcurrentHashMap<String, List<WeakReference<AbstractComponent>>>(); private Platform platform = null; private StepBehindCache<Set<String>> allUsers = new StepBehindCache<Set<String>>(new Lookup<Set<String>>() { public Set<String> lookup() { return lookupAllUsers(); } }); private StepBehindCache<List<String>> bootstrapComponentIds = new StepBehindCache<List<String>>(new Lookup<List<String>>() { public List<String> lookup() { return lookupBootstrapComponents(); } }); static { try { propContext = JAXBContext.newInstance(ExtendedProperties.class); } catch (JAXBException e) { throw new RuntimeException(e); } } static class ComponentIdComparator implements Comparator<AbstractComponent>, Serializable{ private static final long serialVersionUID = 1834331905999908621L; public int compare(AbstractComponent o1, AbstractComponent o2) { return o1.getComponentId().compareTo(o2.getComponentId()); } } private static final ThreadLocal<Set<AbstractComponent>> components = new ThreadLocal<Set<AbstractComponent>>(){ @Override protected Set<AbstractComponent> initialValue() { return Collections.emptySet(); } }; private EntityManagerFactory entityManagerFactory; private PollTime lastPollTime; private Date lastModified; private long pollingInterval; private int maxResults = DEFAULT_MAX_RESULTS; public void bind(Platform platform) { this.platform = platform; } public void unbind(Platform platform) { if (this.platform == platform) { this.platform = null; } } public void setEntityManagerProperties(Properties p) { ClassLoader originalCL = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); try { entityManagerFactory = Persistence.createEntityManagerFactory("MissionControlTechnologies", p); } finally { Thread.currentThread().setContextClassLoader(originalCL); } } public void activate(ComponentContext context) throws IOException { Properties persistenceProperties = getPersistenceProperties(); setEntityManagerProperties(persistenceProperties); new InternalDBPersistenceAccess().setPersistenceService(this); checkDatabaseVersion(); // Trigger initial lookup of users // (this leaves something in cache for subsequent lookups) allUsers.get(); // Check for configuration of the polling interval (default is 3s) pollingInterval = 3000; try { String intervalString = persistenceProperties.getProperty("mct.database_pollInterval"); if (intervalString != null) { pollingInterval = Long.parseLong(intervalString); if (pollingInterval < MINIMUM_POLLING_INTERVAL) { LOGGER.warn("Configured database polling interval {} too short. Defaulting to {} ms.", pollingInterval); pollingInterval = MINIMUM_POLLING_INTERVAL; } } } catch (NumberFormatException nfe) { // Stick with the default } try { String maxResultsString = persistenceProperties.getProperty("mct.database_maxResults"); if (maxResultsString != null) { maxResults = Integer.parseInt(maxResultsString); } } catch (NumberFormatException nfe) { // Stick with the default maxResults = DEFAULT_MAX_RESULTS; } Timer databasePollingTimer = new Timer(); databasePollingTimer.schedule(new TimerTask() { @Override public void run() { InternalDBPersistenceAccess.getService().updateComponentsFromDatabase(); } }, pollingInterval, pollingInterval); } private Properties getPersistenceProperties() throws IOException { Properties properties = new Properties(); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(PERSISTENCE_PROPS); if (is == null) throw new IOException("Unable to get mct property file: " + PERSISTENCE_PROPS); try { properties.load(is); } finally { is.close(); } // adjust user name and connection string properties for JPA final String dbUser = "mct.database_userName"; final String dbPassword = "mct.database_password"; final String dbConnectionURL = "mct.database_connectionUrl"; final String dbName = "mct.database_name"; final String dbProperties = "mct.database_properties"; final String jdbcUrlProperty = "javax.persistence.jdbc.url"; if (System.getProperty(jdbcUrlProperty) != null) { properties.put(jdbcUrlProperty, System.getProperty(jdbcUrlProperty)); } final String userName = System.getProperty(dbUser, properties.getProperty(dbUser)); final String pw = System.getProperty(dbPassword,properties.getProperty(dbPassword)); if (userName != null) { properties.put("javax.persistence.jdbc.user", userName); } if (pw != null) { properties.put("javax.persistence.jdbc.password",pw); } String connectionURL = System.getProperty(dbConnectionURL, properties.getProperty(dbConnectionURL)) + System.getProperty(dbName, properties.getProperty(dbName)) + "?" + System.getProperty(dbProperties, properties.getProperty(dbProperties)); if (!properties.containsKey(jdbcUrlProperty)) { properties.put(jdbcUrlProperty,connectionURL); } return properties; } private void checkDatabaseVersion() { // Don't check schema versions if a special JVM parameter is set. if (System.getProperty("mct.db.check-schema-version", Boolean.TRUE.toString()).equals(Boolean.TRUE.toString())) { Properties versionProperties = new Properties(); try { InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(VERSION_PROPS); if (is == null) throw new IOException("Unable to get mct property file: " + VERSION_PROPS); try { versionProperties.load(is); } finally { is.close(); } } catch (IOException e) { throw new RuntimeException("Cannot load version properties (properties/version.properties)", e); } String schemaId; if ((schemaId = versionProperties.getProperty("mct.db.schema_id")) == null) { throw new RuntimeException("required property mct.db.schema_id is undefined"); } EntityManager em = entityManagerFactory.createEntityManager(); try { TypedQuery<DatabaseIdentification> q = em.createNamedQuery("DatabaseIdentification.findSchemaId", DatabaseIdentification.class); DatabaseIdentification di = q.getSingleResult(); if (!schemaId.equals(di.getValue())) { String errMsg = "Mismatched schemaID.\nDeployed schema ID is " + di.getValue() + "\nbut MCT requires schema ID " + schemaId; LOGGER.error(errMsg); throw new RuntimeException (errMsg); } } finally { em.close(); } } } @Override public AbstractComponent getComponentFromStore(String componentId) { Cache c = entityManagerFactory.getCache(); c.evict(ComponentSpec.class, componentId); return getComponent(componentId); } @Override public AbstractComponent getComponent(String componentId) { EntityManager em = entityManagerFactory.createEntityManager(); ComponentSpec cs = null; try { cs = em.find(ComponentSpec.class, componentId); } finally { em.close(); } if (cs != null) { return createAbstractComponent(cs); } return null; } @Override public Collection<AbstractComponent> getReferences(AbstractComponent component) { Collection<AbstractComponent> references = new ArrayList<AbstractComponent>(); EntityManager em = entityManagerFactory.createEntityManager(); try { TypedQuery<ComponentSpec> q = em.createNamedQuery("ComponentSpec.findReferencingComponents", ComponentSpec.class); q.setParameter("component", component.getComponentId()); List<ComponentSpec> referencingComponents = q.getResultList(); for (ComponentSpec cs:referencingComponents) { references.add(createAbstractComponent(cs)); } } finally { em.close(); } return references; } private boolean hasWorkUnitBeenStarted() { return getCurrentComponents() != Collections.<AbstractComponent>emptySet(); } @Override public void startRelatedOperations() { assert entityManagerFactory != null; if (hasWorkUnitBeenStarted()) { throw new IllegalStateException("startRelatedOperations cannot be invoked until the last operations has been closed"); } components.set(new TreeSet<AbstractComponent>(COMPONENT_ID_COMPARATOR)); } @Override public void completeRelatedOperations(boolean save) { try { if (save) { persist(components.get()); } } finally { components.remove(); } } @Override public void addComponentToWorkUnit(AbstractComponent component) { if (hasWorkUnitBeenStarted()) { getCurrentComponents().add(component); } } public Collection<AbstractComponent> getCurrentComponents() { return components.get(); } @Override public Set<String> getAllUsers() { return allUsers.get(); } private Set<String> lookupAllUsers() { EntityManager em = entityManagerFactory.createEntityManager(); Set<String> userNames = null; try { TypedQuery<MctUsers> q = em.createNamedQuery("MctUsers.findAll", MctUsers.class); List<MctUsers> users = q.getResultList(); userNames = new HashSet<String>(users.size()); for (MctUsers u:users) { userNames.add(u.getUserId()); } } finally { em.close(); } return userNames; } @Override public Collection<String> getUsersInGroup(String group) { EntityManager em = entityManagerFactory.createEntityManager(); List<String> userNames = null; try { TypedQuery<MctUsers> q = em.createNamedQuery("MctUsers.findByGroup", MctUsers.class); q.setParameter("group", group); List<MctUsers> users = q.getResultList(); userNames = new ArrayList<String>(users.size()); for (MctUsers u:users) { userNames.add(u.getUserId()); } } finally { em.close(); } return userNames; } private ExtendedProperties createExtendedProperties(String props) { Unmarshaller unmarshaller; try { unmarshaller = propContext.createUnmarshaller(); InputStream is = new ByteArrayInputStream(props.getBytes("ASCII")); return ExtendedProperties.class.cast(unmarshaller.unmarshal(is)); } catch (JAXBException je) { throw new RuntimeException(je); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private String generateStringFromView(ExtendedProperties viewState) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); Marshaller marshaller = propContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); marshaller.setProperty(Marshaller.JAXB_ENCODING, "ASCII"); marshaller.marshal(viewState, out); return out.toString("ASCII"); } catch (JAXBException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private ViewState createViewState(String viewType, String componentId, ExtendedProperties viewData, EntityManager em, ComponentSpec cs) { ViewStatePK viewStatePK = new ViewStatePK(); viewStatePK.setComponentId(componentId); viewStatePK.setViewType(viewType); ViewState vs = em.find(ViewState.class, viewStatePK); if (vs == null) { vs = new ViewState(); vs.setViewStatePK(viewStatePK); if (cs.getViewStateCollection() == null) { cs.setViewStateCollection(new ArrayList<ViewState>()); } cs.getViewStateCollection().add(vs); } vs.setViewInfo(generateStringFromView(viewData)); return vs; } private void updateComponentSpec(AbstractComponent ac,ComponentSpec cs,EntityManager em, boolean fullSave) { cs.setComponentId(ac.getComponentId()); cs.setComponentName(ac.getDisplayName()); cs.setOwner(ac.getOwner()); cs.setCreatorUserId(ac.getCreator()); cs.setComponentType(ac.getClass().getName()); cs.setExternalKey(ac.getExternalKey()); cs.setLastModified(lastModified); if (!fullSave) { return; } cs.setObjVersion(ac.getVersion()); ModelStatePersistence persistence = ac.getCapability(ModelStatePersistence.class); if (persistence != null) { cs.setModelInfo(persistence.getModelState()); } // save relationships // non optimal implementation as this will require a query to get the relationships cs.setReferencedComponents(new ArrayList<ComponentSpec>(ac.getComponents().size())); for (AbstractComponent c : ac.getComponents()) { ComponentSpec refCs = em.find(ComponentSpec.class, c.getComponentId()); if (refCs == null) { // this can be null if the component has been deleted continue; } cs.getReferencedComponents().add(refCs); } // save views ComponentInitializer ci = ac.getCapability(ComponentInitializer.class); if (ci.getMutatedViewRoleProperties() != null) { for (Entry<String,ExtendedProperties> viewEntry : ci.getAllViewRoleProperties().entrySet()) { createViewState(viewEntry.getKey(), cs.getComponentId(), viewEntry.getValue(),em,cs); } } } @Override public void persist(Collection<AbstractComponent> componentsToPersist) { EntityManager em = entityManagerFactory.createEntityManager(); lastModified = lastPollTime != null ? lastPollTime.getAdjustedNow() : // Predict database time getCurrentTimeFromDatabase(); // Or read it, if we haven't yet if (lastModified == null) { lastModified = new Date(); // Use system time as a fallback } try { em.getTransaction().begin(); // first persist all new components, without relationships, model, and view states for (AbstractComponent nc : componentsToPersist) { if (nc.getCreationDate() == null) { ComponentSpec cs = new ComponentSpec(); updateComponentSpec(nc, cs, em, false); em.persist(cs); } } // now persist the data for (AbstractComponent c : componentsToPersist) { updateComponentSpec(c, em.find(ComponentSpec.class, c.getComponentId()), em, true); } em.flush(); em.getTransaction().commit(); for (AbstractComponent c : componentsToPersist) { ComponentInitializer ci = c.getCapability(ComponentInitializer.class); ci.componentSaved(); if (c.getCreationDate() == null) { ci.setCreationDate(em.find(ComponentSpec.class, c.getComponentId()).getDateCreated()); } c.getCapability(Updatable.class).setVersion(em.find(ComponentSpec.class, c.getComponentId()).getObjVersion()); c.componentSaved(); List<WeakReference<AbstractComponent>> list = cache.get(c.getComponentId()); if (list == null) { list = Collections.synchronizedList(new LinkedList<WeakReference<AbstractComponent>>()); cache.put(c.getComponentId(), list); } list.add(new WeakReference<AbstractComponent>(c)); } } catch(OptimisticLockException ole) { throw new gov.nasa.arc.mct.api.persistence.OptimisticLockException(ole); } finally { if (em.getTransaction().isActive()) { em.getTransaction().rollback(); } em.close(); } } @Override public void delete(Collection<AbstractComponent> componentsToDelete) { EntityManager em = entityManagerFactory.createEntityManager(); try { em.getTransaction().begin(); for (AbstractComponent component:componentsToDelete) { ComponentSpec componentToDelete = em.find(ComponentSpec.class, component.getComponentId()); if (componentToDelete == null) { // component has already been deleted from database but not refreshed in the user interface continue; } TypedQuery<ComponentSpec> q = em.createNamedQuery("ComponentSpec.findReferencingComponents", ComponentSpec.class); q.setParameter("component", component.getComponentId()); List<ComponentSpec> referencingComponents = q.getResultList(); Date lastModified = lastPollTime != null ? lastPollTime.getAdjustedNow() : getCurrentTimeFromDatabase(); if (lastModified == null) { lastModified = new Date(); } for (ComponentSpec cs:referencingComponents) { cs.getReferencedComponents().remove(componentToDelete); cs.setLastModified(lastModified != null ? lastModified : new Date()); } em.remove(componentToDelete); } em.getTransaction().commit(); } finally { if (em.getTransaction().isActive()) { em.getTransaction().rollback(); } em.close(); } } @Override public boolean hasComponentsTaggedBy(String tagId) { EntityManager em = null; try { em = entityManagerFactory.createEntityManager(); TypedQuery<TagAssociation> q = em.createNamedQuery("TagAssociation.getComponentsByTag", TagAssociation.class); q.setParameter("tagId", tagId); q.setMaxResults(1); return !q.getResultList().isEmpty(); } finally { if (em != null) { em.close(); } } } @Override public List<AbstractComponent> getReferencedComponents( AbstractComponent component) { List<AbstractComponent> references = new ArrayList<AbstractComponent>(); EntityManager em = entityManagerFactory.createEntityManager(); if (component.getComponentId() != null) { try { ComponentSpec reference = em.find(ComponentSpec.class, component.getComponentId()); if (reference != null) { List<ComponentSpec> referencedComponents = reference.getReferencedComponents(); for (ComponentSpec cs:referencedComponents) { if (cs != null) { AbstractComponent ac = createAbstractComponent(cs); references.add(ac); } } } } finally { em.close(); } } return references; } @Override public User getUser(String userId) { EntityManager em = entityManagerFactory.createEntityManager(); User u = null; try { MctUsers users = em.find(MctUsers.class, userId); if (users != null) { final String discipline = users.getDisciplineId().getDisciplineId(); final String uId = users.getUserId(); u = new User() { @Override public String getDisciplineId() { return discipline; } @Override public String getUserId() { return uId; } @Override public User getValidUser(String userID) { return getUser(userID); } }; } } finally { em.close(); } return u; } /** * Finds all the components by base display name regex pattern. * @param pattern - regex. * @param props <code>Properties</code> set arguments for SQL query. * @return QueryResult - search results. */ @SuppressWarnings("unchecked") public QueryResult findComponentsByBaseDisplayedNamePattern(String pattern, Properties props) { String username = platform.getCurrentUser().getUserId(); pattern = pattern.isEmpty() ? "%" : pattern.replace('*', '%'); String countQuery = "select count(*) from component_spec c " + "where c.creator_user_id like :creator " + "and c.component_id not in (select component_id from component_spec where component_type = 'gov.nasa.arc.mct.core.components.TelemetryDataTaxonomyComponent' and component_name = 'All')" + "and (c.component_type != 'gov.nasa.arc.mct.core.components.MineTaxonomyComponent' or c.owner = :owner) " + "and c.component_name like :pattern "; String entitiesQuery = "select c.* from component_spec c " + "where c.creator_user_id like :creator " + "and c.component_id not in (select component_id from component_spec where component_type = 'gov.nasa.arc.mct.core.components.TelemetryDataTaxonomyComponent' and component_name = 'All')" + "and (c.component_type != 'gov.nasa.arc.mct.core.components.MineTaxonomyComponent' or c.owner = :owner) " + "and c.component_name like :pattern "; EntityManager em = entityManagerFactory.createEntityManager(); try { Query q = em.createNativeQuery(countQuery); q.setParameter("pattern", pattern); q.setParameter("owner", username); q.setParameter("creator", (props != null && props.get("creator") != null) ? props.get("creator") : "%" ); int count = ((Number) q.getSingleResult()).intValue(); q = em.createNativeQuery(entitiesQuery, ComponentSpec.class); q.setMaxResults(maxResults); q.setParameter("pattern", pattern); q.setParameter("owner", username); q.setParameter("creator", (props != null && props.get("creator") != null) ? props.get("creator") : "%" ); List<ComponentSpec> daoObjects = q.getResultList(); return new QueryResult(count, daoObjects); } catch (Exception t) { LOGGER.error("error executing query", t); return null; } finally { em.close(); } } AbstractComponent newAbstractComponent(ComponentSpec cs) { return platform.getComponentRegistry().newInstance(cs.getComponentType()); } private AbstractComponent createAbstractComponent(ComponentSpec cs) { AbstractComponent ac = newAbstractComponent(cs); ComponentInitializer initializer = ac.getCapability(ComponentInitializer.class); initializer.setCreationDate(cs.getDateCreated()); initializer.setCreator(cs.getCreatorUserId()); initializer.setOwner(cs.getOwner()); initializer.setId(cs.getComponentId()); ac.setExternalKey(cs.getExternalKey()); ac.setDisplayName(cs.getComponentName()); ac.getCapability(Updatable.class).setVersion(cs.getObjVersion()); ModelStatePersistence persister = ac.getCapability(ModelStatePersistence.class); if ((persister != null) && (cs.getModelInfo() != null)) { persister.setModelState(cs.getModelInfo()); } // Add ac to cache List<WeakReference<AbstractComponent>> list = cache.get(cs.getComponentId()); if (list == null) { list = Collections.synchronizedList(new LinkedList<WeakReference<AbstractComponent>>()); } list.add(new WeakReference<AbstractComponent>(ac)); cache.put(cs.getComponentId(), list); return ac; } @Override public <T extends AbstractComponent> T getComponent(String externalKey, Class<T> componentType) { EntityManager em = entityManagerFactory.createEntityManager(); T comp = null; try { TypedQuery<ComponentSpec> q = em.createQuery("SELECT c FROM ComponentSpec c WHERE c.externalKey = :externalKey and c.componentType = :componentType", ComponentSpec.class); q.setParameter("externalKey", externalKey); q.setParameter("componentType", componentType.getName()); List<ComponentSpec> cs = q.getResultList(); if (!cs.isEmpty()) { AbstractComponent ac = createAbstractComponent(cs.get(0)); comp = componentType.cast(ac); } } finally { em.close(); } return comp; } private Date getCurrentTimeFromDatabase() { if (platform==null) { return null; } User currentUser = platform.getCurrentUser(); if (currentUser == null) return null; String userId = currentUser.getUserId(); EntityManager em = entityManagerFactory.createEntityManager(); try { TypedQuery<Date> q = em.createQuery("SELECT CURRENT_TIMESTAMP FROM ComponentSpec c WHERE c.owner = :owner", Date.class); q.setParameter("owner", userId); return q.getSingleResult(); } catch (NoResultException e) { // No result from database, so no current time // (database may not be fully initialized) return null; } finally { em.close(); } } private void iterateOverChangedComponents(ChangedComponentVisitor v) { if (lastPollTime == null) { Date storeTime = getCurrentTimeFromDatabase(); if (storeTime == null) return; else lastPollTime = new PollTime(storeTime); } String query = "SELECT CURRENT_TIMESTAMP, c FROM ComponentSpec c WHERE c.lastModified BETWEEN ?1 AND CURRENT_TIMESTAMP"; EntityManager em = entityManagerFactory.createEntityManager(); try { Query q = em.createQuery(query); q.setParameter(1, new Date(lastPollTime.getStoreTime().getTime() - pollingInterval), TemporalType.TIMESTAMP); final int MAX_CACHE_SIZE = 500; int iteration = 0; boolean done = false; while(!done) { q.setFirstResult(MAX_CACHE_SIZE * iteration); q.setMaxResults(MAX_CACHE_SIZE); @SuppressWarnings("rawtypes") List resultList = q.getResultList(); for (int i=0; i<resultList.size(); i++) { for (Object obj : (Object[]) resultList.get(i)) { if (obj instanceof ComponentSpec) v.operateOnComponent((ComponentSpec) obj); if (obj instanceof Date) lastPollTime = new PollTime((Date) obj); } } done = resultList.size() < MAX_CACHE_SIZE; iteration++; em.clear(); } } catch (Exception t) { LOGGER.error("error executing query", t); } finally { em.close(); } } private void cleanCacheIfNecessary(String componentId, int latestVersion) { Cache c = entityManagerFactory.getCache(); if (c.contains(ComponentSpec.class, componentId)) { EntityManager em = entityManagerFactory.createEntityManager(); ComponentSpec cs = em.find(ComponentSpec.class, componentId); if (cs != null && cs.getObjVersion() < latestVersion) { c.evict(ComponentSpec.class, componentId); } em.close(); } } private void updateComponentIfNecessary(final ComponentSpec c, final Collection<AbstractComponent> cachedComponents) { Collection<AbstractComponent> delegateComponets = new ArrayList<AbstractComponent>(); for (final AbstractComponent ac : cachedComponents) { Updatable updatable = ac.getCapability(Updatable.class); updatable.setStaleByVersion(c.getComponentId(), c.getObjVersion()); cleanCacheIfNecessary(c.getComponentId(), c.getObjVersion()); if (ac.getWorkUnitDelegate() != null) { ac.getWorkUnitDelegate().getCapability(Updatable.class).setStaleByVersion(c.getComponentId(), c.getObjVersion()); delegateComponets.add(ac.getWorkUnitDelegate()); } } cachedComponents.addAll(delegateComponets); for (final AbstractComponent ac: cachedComponents) { if (ac.isStale()) { ac.resetComponentProperties(new AbstractComponent.ResetPropertiesTransaction() { @Override public void perform() { Updatable updatable = ac.getCapability(Updatable.class); updatable.notifyStale(); } }); LOGGER.debug("{} updated", c.getComponentName()); } } } private int pollCounter = 0; @Override public void updateComponentsFromDatabase() { pollCounter++; iterateOverChangedComponents( new ChangedComponentVisitor() { @Override public void operateOnComponent(ComponentSpec c) { // Evict referenced components from L2 cache // TODO: Find alternate solution or refactor in order // to remove this explicit reference to Hibernate ((HibernateEntityManagerFactory)entityManagerFactory) .getSessionFactory() .getCache().evictCollection( ComponentSpec.class.getName() + ".referencedComponents", c.getComponentId()); List<WeakReference<AbstractComponent>> list = cache.get(c.getComponentId()); if (list != null && !list.isEmpty()) { Collection<AbstractComponent> cachedComponents = new ArrayList<AbstractComponent>(list.size()); synchronized(list) { Iterator<WeakReference<AbstractComponent>> it = list.iterator(); while (it.hasNext()) { AbstractComponent ac = it.next().get(); if (ac != null) { cachedComponents.add(ac); } else { it.remove(); } } } if (!cachedComponents.isEmpty()) updateComponentIfNecessary(c, cachedComponents); } } } ); if (pollCounter % 1000 == 0) cleanCache(); } public interface ChangedComponentVisitor { /** * This method is invoked for each component that has changed. * @param component that has changed */ void operateOnComponent(ComponentSpec component); } @Override public List<AbstractComponent> getBootstrapComponents() { List<String> bootstrapCache = bootstrapComponentIds.get(); if (bootstrapCache != null) { // Look up specific components based on bootstrap ids List<AbstractComponent> result = new ArrayList<AbstractComponent>(bootstrapCache.size()); for (String id : bootstrapCache) { result.add(getComponent(id)); } return result; } return null; } private List<String> lookupBootstrapComponents() { List<ComponentSpec> cslist = null; EntityManager em = entityManagerFactory.createEntityManager(); try { String userId = platform == null ? null : platform.getCurrentUser() == null ? null : platform.getCurrentUser().getUserId(); TypedQuery<ComponentSpec> q = em.createQuery("SELECT t.componentSpec FROM TagAssociation t where t.tag.tagId = 'bootstrap:admin' or (t.tag.tagId = 'bootstrap:creator' and t.componentSpec.creatorUserId = :user)", ComponentSpec.class); q.setParameter("user", userId); cslist = q.getResultList(); } finally { em.close(); } if (cslist != null) { // Assemble list of component ids List<String> bootstrapCache = new ArrayList<String>(); for (ComponentSpec cs : cslist) { bootstrapCache.add(cs.getComponentId()); } return bootstrapCache; } else { return null; } } private void cleanCache() { Iterator<String> iterator = cache.keySet().iterator(); while(iterator.hasNext()) { String componentId = iterator.next(); List<WeakReference<AbstractComponent>> list = cache.get(componentId); boolean canBeDeleted = true; synchronized(list) { for (WeakReference<AbstractComponent> r : list) { if (r.get() != null) { canBeDeleted = false; } } } if (canBeDeleted) iterator.remove(); } } @Override public void addNewUser(String userId, String groupId, AbstractComponent mysandbox, AbstractComponent dropbox) { String userDropboxesId = platform.getUserDropboxes().getComponentId(); EntityManager em = entityManagerFactory.createEntityManager(); try { em.getTransaction().begin(); Disciplines group = em.find(Disciplines.class, groupId); if (group == null) { group = new Disciplines(); group.setDisciplineId(groupId); em.persist(group); } MctUsers user = new MctUsers(); user.setUserId(userId); user.setDisciplineId(group); em.persist(user); ComponentSpec mysandboxComponentSpec = new ComponentSpec(); updateComponentSpec(mysandbox, mysandboxComponentSpec, em, true); em.persist(mysandboxComponentSpec); TagAssociationPK tagAssociationPK = new TagAssociationPK(); tagAssociationPK.setComponentId(mysandbox.getComponentId()); tagAssociationPK.setTagId("bootstrap:creator"); TagAssociation tagAssociation = new TagAssociation(); tagAssociation.setTagAssociationPK(tagAssociationPK); em.persist(tagAssociation); ComponentSpec dropboxComponentSpec = new ComponentSpec(); updateComponentSpec(dropbox, dropboxComponentSpec, em, true); em.persist(dropboxComponentSpec); ComponentSpec userDropboxes = em.find(ComponentSpec.class, userDropboxesId); userDropboxes.getReferencedComponents().add(dropboxComponentSpec); mysandboxComponentSpec.getReferencedComponents().add(dropboxComponentSpec); em.persist(userDropboxes); em.persist(mysandboxComponentSpec); em.getTransaction().commit(); } finally { if(em.getTransaction().isActive()) em.getTransaction().rollback(); em.close(); } // My Sandbox was marked a bootstrap, so we need to refresh cache // Otherwise, My Sandbox may not appear for the newly-created user. bootstrapComponentIds.refresh(); } @Override public Map<String, ExtendedProperties> getAllProperties(String componentId) { EntityManager em = entityManagerFactory.createEntityManager(); Map<String, ExtendedProperties> properties = new HashMap<String, ExtendedProperties>(); try { ComponentSpec cs = em.find(ComponentSpec.class, componentId); if (cs != null) { for (ViewState vs: cs.getViewStateCollection()) { properties.put(vs.getViewStatePK().getViewType(), createExtendedProperties(vs.getViewInfo())); } } return properties; } finally { em.close(); } } @Override public AbstractComponent getComponent(String externalKey, String componentType) { EntityManager em = entityManagerFactory.createEntityManager(); AbstractComponent ac = null; try { TypedQuery<ComponentSpec> q = em .createQuery( "SELECT c FROM ComponentSpec c " + "WHERE c.externalKey = :externalKey and c.componentType = :componentType", ComponentSpec.class); q.setParameter("externalKey", externalKey); q.setParameter("componentType", componentType); List<ComponentSpec> cs = q.getResultList(); if (!cs.isEmpty()) { ac = createAbstractComponent(cs.get(0)); } } finally { em.close(); } return ac; } @Override public void tagComponents(String tag, Collection<AbstractComponent> components) { EntityManager em = entityManagerFactory.createEntityManager(); try { em.getTransaction().begin(); Tag t = em.find(Tag.class, tag); if (t == null) { t = new Tag(); t.setTagId(tag); em.persist(t); } for (AbstractComponent component:components) { ComponentSpec cs = em.find(ComponentSpec.class, component.getComponentId()); TagAssociation association = new TagAssociation(); TagAssociationPK tagPK = new TagAssociationPK(); tagPK.setComponentId(cs.getComponentId()); tagPK.setTagId(t.getTagId()); association.setTagAssociationPK(tagPK); cs.getTagAssociationCollection().add(association); } em.getTransaction().commit(); } finally { if(em.getTransaction().isActive()) em.getTransaction().rollback(); em.close(); } // Refresh the bootstrap cache if it appears to have changed. if (tag.startsWith("bootstrap:")) { bootstrapComponentIds.refresh(); } } private static class PollTime { private Date storeTime; private long localTime; public PollTime(Date storeTime) { this.storeTime = storeTime; localTime = System.currentTimeMillis(); } public Date getStoreTime() { return storeTime; } public Date getAdjustedNow() { long diff = System.currentTimeMillis() - localTime; return new Date(storeTime.getTime() + diff); } } }