/* * Copyright 2012, CMM, University of Queensland. * * This file is part of Paul. * * Paul 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. * * Paul 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 Paul. If not, see <http://www.gnu.org/licenses/>. */ package au.edu.uq.cmm.paul.status; import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import au.edu.uq.cmm.aclslib.proxy.AclsAuthenticationException; import au.edu.uq.cmm.aclslib.proxy.AclsHelper; import au.edu.uq.cmm.aclslib.proxy.AclsInUseException; import au.edu.uq.cmm.eccles.FacilitySession; import au.edu.uq.cmm.eccles.UserDetailsManager; import au.edu.uq.cmm.eccles.UserDetails; import au.edu.uq.cmm.eccles.UserDetailsException; import au.edu.uq.cmm.paul.Paul; import au.edu.uq.cmm.paul.PaulException; import au.edu.uq.cmm.paul.grabber.SessionDetails; /** * This class represents the session state of the facilities as * captured by the ACLS proxy. * * @author scrawley */ public class FacilityStatusManager { public enum Status { ON, DISABLED, OFF } private final EntityManagerFactory emf; private final AclsHelper aclsHelper; private final UserDetailsManager userDetailsManager; private final Map<Long, FacilityStatus> facilityStatuses = new HashMap<Long, FacilityStatus>(); private final Map<String, FacilitySessionCache> facilitySessionCaches = new HashMap<String, FacilitySessionCache>(); private final boolean makeDummySessions; private static final Logger LOG = LoggerFactory.getLogger(FacilityStatusManager.class); public FacilityStatusManager(Paul services) { this.emf = Objects.requireNonNull(services.getEntityManagerFactory()); this.userDetailsManager = Objects.requireNonNull( services.getUserDetailsManager()); this.makeDummySessions = !Objects.requireNonNull( services.getConfiguration()).isHoldDatasetsWithNoUser(); /* No non-null check for this one because of unit test pain ... */ this.aclsHelper = services.getAclsHelper(); } private Facility getFacility(EntityManager em, String facilityName) { TypedQuery<Facility> query = em.createQuery( "from Facility f where f.facilityName = :facilityName", Facility.class); query.setParameter("facilityName", facilityName); List<Facility> res = query.getResultList(); if (res.size() == 0) { return null; } else if (res.size() == 1) { return res.get(0); } else { throw new PaulException("Duplicate facility entries"); } } public FacilitySession getSession(String sessionUUID) { EntityManager em = emf.createEntityManager(); try { TypedQuery<FacilitySession> query = em.createQuery( "from FacilitySession s where s.sessionUuid = :uuid", FacilitySession.class); query.setParameter("uuid", sessionUUID); List<FacilitySession> list = query.getResultList(); if (list.isEmpty()) { return null; } else if (list.size() == 1) { return list.get(0); } else { throw new PaulException("Duplicate facility session entries"); } } finally { em.close(); } } public List<FacilitySession> sessionsForFacility(String facilityName) { EntityManager em = emf.createEntityManager(); try { TypedQuery<FacilitySession> query = em.createQuery( "from FacilitySession s where s.facilityName = :facilityName " + "order by s.loginTime desc", FacilitySession.class); query.setParameter("facilityName", facilityName); return query.getResultList(); } finally { em.close(); } } public FacilityStatus getStatus(Facility facility) { FacilityStatus status = facilityStatuses.get(facility.getId()); if (status == null) { EntityManager em = emf.createEntityManager(); try { em.getTransaction().begin(); TypedQuery<FacilityStatus> query = em.createQuery( "from FacilityStatus s where s.facilityId = :id", FacilityStatus.class); query.setParameter("id", facility.getId()); if (query.getResultList().isEmpty()) { status = new FacilityStatus(facility.getId()); em.persist(status); em.getTransaction().commit(); } else { status = query.getResultList().get(0); em.getTransaction().rollback(); } facilityStatuses.put(facility.getId(), status); } finally { em.close(); } } return status; } public void attachStatus(Facility facility) { FacilityStatus status = getStatus(facility); if (status.getStatus() == Status.OFF && facility.isDisabled()) { status.setStatus(Status.DISABLED); } else if (status.getStatus() == Status.DISABLED && !facility.isDisabled()) { status.setStatus(Status.OFF); } facility.setStatus(status); } /** * Set a Facility's intertidal timestamps on the in-memory FacilityStatus object * and in the database. * * @param facility The facility * @param lwm The LWM timestamp * @param hwm The HWM timestamp which should be not before the LWM */ public void updateIntertidalTimestamp(Facility facility, Date lwm, Date hwm) { FacilityStatus status = getStatus(facility); doUpdateIntertidalTimestamps(lwm, hwm, status); } /** * Advance the Facility's HWM timestamp. If the LWM or HWM are currently unset, * this sets them both to the HWM. * * @param facility The facility * @param hwm The HWM timestamp which should be not before the current HWM (if set) */ public void advanceHWMTimestamp(Facility facility, Date hwm) { FacilityStatus status = getStatus(facility); synchronized (status) { if (status.getGrabberLWMTimestamp() == null || status.getGrabberHWMTimestamp() == null) { doUpdateIntertidalTimestamps(hwm, hwm, status); } else if (hwm.before(status.getGrabberHWMTimestamp())) { throw new PaulException("inconsistent timestamps: " + hwm + " < " + status.getGrabberHWMTimestamp()); } else { doUpdateIntertidalTimestamps(status.getGrabberLWMTimestamp(), hwm, status); } } } private void doUpdateIntertidalTimestamps(Date lwm, Date hwm, FacilityStatus status) { if (lwm.after(hwm)) { throw new PaulException("inconsistent timestamps: " + lwm + " > " + hwm); } synchronized (status) { status.setGrabberLWMTimestamp(lwm); status.setGrabberHWMTimestamp(hwm); EntityManager em = emf.createEntityManager(); try { em.getTransaction().begin(); FacilityStatus pstatus = em.getReference( FacilityStatus.class, status.getFacilityId()); pstatus.setGrabberLWMTimestamp(lwm); pstatus.setGrabberHWMTimestamp(hwm); em.getTransaction().commit(); } finally { em.close(); } } } public List<FacilitySession> getLatestSessions() { EntityManager em = emf.createEntityManager(); try { em.getTransaction().begin(); TypedQuery<String> query0 = em.createQuery( "select f.facilityName from Facility f order by f.facilityName", String.class); List<String> facilityNames = query0.getResultList(); List<FacilitySession> sessions = new ArrayList<FacilitySession>(facilityNames.size()); for (String facilityName : facilityNames) { FacilitySession session = latestSession(em, facilityName); if (session == null) { session = new FacilitySession(facilityName); } sessions.add(session); } em.getTransaction().rollback(); return sessions; } finally { em.close(); } } public SessionDetails getSessionDetails(Facility facility, long timestamp, File datasetBasename) { FacilitySession session = getSession(facility, timestamp); if (session == null && makeDummySessions) { session = FacilitySession.makeDummySession(facility.getFacilityName(), timestamp); } if (facility.isUserOperated()) { return new SessionDetails(session); } else { return new SessionDetails(session, intuitUser(datasetBasename)); } } private UserDetails intuitUser(File pathname) { File parent = pathname.getParentFile(); if (parent != null) { UserDetails user = intuitUser(parent); if (user != null) { return user; } } String name = pathname.getName(); if (name == null || name.isEmpty()) { return null; } try { LOG.debug("userDetailsManager is " + userDetailsManager); return userDetailsManager.lookupUser(name.toLowerCase(), true); } catch (UserDetailsException ex) { return null; } } public FacilitySession getSession(Facility facility, long timestamp) { String facilityName = facility.getFacilityName(); FacilitySessionCache cache = facilitySessionCaches.get(facilityName); if (cache == null) { cache = new FacilitySessionCache(); facilitySessionCaches.put(facilityName, cache); } FacilitySession session = cache.lookup(timestamp); if (session == null) { session = computeSession(facilityName, timestamp); if (session != null && session.getInferredLogoutTime() != null) { cache.add(session); } } else { LOG.debug("Cached session for timestamp " + timestamp + " is " + session); } return session; } private FacilitySession computeSession(String facilityName, long timestamp) { EntityManager em = emf.createEntityManager(); try { // First, we select the sessions that potentially contain this timestamp. // These start at or before the timestamp and either end after it, or don't end. // We want the last of these ... so sort descending on start time. TypedQuery<FacilitySession> query = em.createQuery( "from FacilitySession s where s.facilityName = :facilityName " + "and s.loginTime <= :timestamp " + "and (s.logoutTime is null or s.logoutTime >= :timestamp) " + "order by s.loginTime desc", FacilitySession.class); query.setParameter("facilityName", facilityName); query.setParameter("timestamp", new Date(timestamp)); query.setMaxResults(1); LOG.debug("computeSession: facilityName = " + facilityName + ", timestamp = " + timestamp); List<FacilitySession> list = query.getResultList(); if (list.size() == 0) { LOG.debug("No session on '" + facilityName + "' matches timestamp " + timestamp); return null; } FacilitySession session = list.get(0); if (session.getLogoutTime() == null) { // If we have a session with no definite end, we need to infer an // end point from the start of the next session, if any. LOG.debug("Inferring session end ..."); TypedQuery<FacilitySession> query2 = em.createQuery( "from FacilitySession s where s.facilityName = :facilityName " + "and s.loginTime > :timestamp " + "order by s.loginTime asc", FacilitySession.class); query2.setParameter("facilityName", facilityName); query2.setParameter("timestamp", session.getLoginTime()); query2.setMaxResults(1); list = query2.getResultList(); if (list.size() > 0) { FacilitySession session2 = list.get(0); if (session2.getLoginTime().getTime() < timestamp) { LOG.debug("No session on '" + facilityName + "' matches timestamp " + timestamp + " (2)"); return null; } else { session.setInferredLogoutTime( new Date(session2.getLoginTime().getTime() - 1L)); } } } else { session.setInferredLogoutTime(session.getLogoutTime()); } LOG.debug("Computed session for timestamp " + timestamp + " is " + session); return session; } finally { em.close(); } } public void logoutSession(String sessionUuid) throws AclsAuthenticationException { EntityManager em = emf.createEntityManager(); try { em.getTransaction().begin(); TypedQuery<FacilitySession> query = em.createQuery( "from FacilitySession s where s.sessionUuid = :uuid", FacilitySession.class); query.setParameter("uuid", sessionUuid); FacilitySession session = query.getSingleResult(); if (session.getLogoutTime() == null) { session.setLogoutTime(new Date()); } aclsHelper.logout(getFacility(em, session.getFacilityName()), session.getUserName(), session.getAccount()); em.getTransaction().commit(); } catch (NoResultException ex) { LOG.debug("session doesn't exist", ex); } finally { em.close(); } } public void logoutFacility(String facilityName) throws AclsAuthenticationException { EntityManager em = emf.createEntityManager(); try { em.getTransaction().begin(); FacilitySession session = latestSession(em, facilityName); if (session != null) { aclsHelper.logout(getFacility(em, facilityName), session.getUserName(), session.getAccount()); em.getTransaction().commit(); } else { em.getTransaction().rollback(); } } finally { em.close(); } } private FacilitySession latestSession(EntityManager em, String facilityName) { TypedQuery<FacilitySession> query = em.createQuery( "from FacilitySession s where s.facilityName = :facilityName " + "order by s.loginTime desc", FacilitySession.class); query.setParameter("facilityName", facilityName); query.setMaxResults(1); List<FacilitySession> results = query.getResultList(); return (results.isEmpty()) ? null : results.get(0); } public List<String> login(String facilityName, String userName, String password) throws AclsAuthenticationException, AclsInUseException { Facility facility = lookupIdleFacility(facilityName); return aclsHelper.login(facility, userName, password); } public void selectAccount (String facilityName, String userName, String account) throws AclsAuthenticationException, AclsInUseException { Facility facility = lookupIdleFacility(facilityName); aclsHelper.selectAccount(facility, userName, account); } private Facility lookupIdleFacility(String facilityName) throws AclsAuthenticationException, AclsInUseException { Facility facility; EntityManager em = emf.createEntityManager(); try { FacilitySession session = latestSession(em, facilityName); if (session != null && session.getLogoutTime() == null) { throw new AclsInUseException(facilityName, session.getUserName()); } facility = getFacility(em, facilityName); if (facility == null) { throw new AclsAuthenticationException("Unknown facility " + facilityName); } return facility; } finally { em.close(); } } }