package se.cambio.openehr.controller.session.data; import org.apache.log4j.Logger; import se.cambio.cm.model.util.CMElement; import se.cambio.openehr.controller.session.OpenEHRSessionManager; import se.cambio.openehr.util.CMElementComparator; import se.cambio.openehr.util.exceptions.InstanceNotFoundException; import se.cambio.openehr.util.exceptions.InternalErrorException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; public abstract class AbstractCMManager<E extends CMElement> { private static long MAX_CHECK_WAITING_TIME_IN_MILLIS = 5000; //5 seg private Map<String, E> cmElementMap; private boolean useCache = true; private Date lastCheckForUpdates; private Date mostRecentLocalUpdate; private Logger logger = Logger.getLogger(AbstractCMManager.class); public AbstractCMManager(){ Date currentTime = Calendar.getInstance().getTime(); mostRecentLocalUpdate = currentTime; lastCheckForUpdates = currentTime; } public void initialize() { getCmElementMap().clear(); } public Collection<E> getAllInCache() { return getCmElementMap().values(); } public Collection<String> getAllIds() throws InternalErrorException { return OpenEHRSessionManager.getAdministrationFacadeDelegate().getAllCMElementIds(getCMElementClass()); } public Collection<String> getAllIdsInCache() throws InternalErrorException { if (!useCache){ throw new InternalErrorException(new UnsupportedOperationException("Cannot cache, it is not active.")); } return getCmElementMap().keySet(); } public void loadAll() throws InternalErrorException { if (!useCache){ throw new InternalErrorException(new UnsupportedOperationException("Cannot load into cache, it is not active.")); } Collection<E> cmElements = OpenEHRSessionManager.getAdministrationFacadeDelegate().getAllCMElements(getCMElementClass()); registerCMElementsInCache(cmElements); } public E getCMElement(final String id) throws InstanceNotFoundException, InternalErrorException { return getCMElementByIds(Collections.singleton(id)).iterator().next(); } public Collection<E> getCMElementsInCache(final Collection<String> ids) throws InstanceNotFoundException, InternalErrorException { Collection<E> cmElements = new ArrayList<E>(); for(String id: ids){ cmElements.add(getCMElementInCache(id)); } return cmElements; } public E getCMElementInCache(final String id) throws InstanceNotFoundException, InternalErrorException { if (!useCache){ throw new InternalErrorException(new UnsupportedOperationException("Cannot load cache, it is not active.")); } E cmElement = getCmElementMap().get(id); if (cmElement==null){ throw new InstanceNotFoundException(id, "CMElement"); } return cmElement; } public Collection<E> getCMElementByIds(final Collection<String> ids) throws InstanceNotFoundException, InternalErrorException { if (ids==null){ throw new IllegalArgumentException("ids cannot have null value"); } final Collection<E> foundCMElements = new ArrayList<E>(); final Collection<String> idsNotInCache = new ArrayList<String>(); if (useCache) { findCMElementsInCache(ids, idsNotInCache, foundCMElements); } else { idsNotInCache.addAll(ids); } findCMElementsNotInCache(idsNotInCache, foundCMElements); return foundCMElements; } private void findCMElementsInCache(Collection<String> ids, Collection<String> idsNotInCache, Collection<E> foundCMElements) throws InternalErrorException { cacheUpdate(); for (String id: ids){ E cmElement = getCmElementMap().get(id); if (cmElement!=null){ foundCMElements.add(cmElement); }else{ idsNotInCache.add(id); } } } public boolean cacheUpdate() throws InternalErrorException { boolean changesDetected = false; if (isWaitingTimeForNextCheckReached()) { changesDetected = isDataChanged(changesDetected); } if (changesDetected) { logger.info("Detected changes on " + getCMElementClass().getSimpleName() + "). Initializing cache..."); initialize(); } return changesDetected; } private boolean isDataChanged(boolean changesDetected) throws InternalErrorException { Date lastUpdateDateOnServer = OpenEHRSessionManager.getAdministrationFacadeDelegate().getLastUpdate(getCMElementClass()); if (lastUpdateDateOnServer != null) { if (lastUpdateDateOnServer.getTime() > mostRecentLocalUpdate.getTime()) { changesDetected = true; mostRecentLocalUpdate = lastUpdateDateOnServer; } } return changesDetected; } private boolean isWaitingTimeForNextCheckReached() { boolean waitingTimeForNextCheckReached = false; long timeSinceLastCheck = System.currentTimeMillis() - lastCheckForUpdates.getTime(); if (timeSinceLastCheck>=MAX_CHECK_WAITING_TIME_IN_MILLIS){ waitingTimeForNextCheckReached = true; lastCheckForUpdates = Calendar.getInstance().getTime(); } return waitingTimeForNextCheckReached; } private void findCMElementsNotInCache(final Collection<String> idsNotInCache, final Collection<E> foundCMElements) throws InternalErrorException, InstanceNotFoundException { if (!idsNotInCache.isEmpty()) { Collection<E> cmElementsNotFoundInCache = OpenEHRSessionManager.getAdministrationFacadeDelegate().searchCMElementsByIds(getCMElementClass(), idsNotInCache); foundCMElements.addAll(cmElementsNotFoundInCache); registerCMElementsInCache(cmElementsNotFoundInCache); } } public void registerCMElementsInCache(Collection<E> cmElements){ if (useCache) { for (E cmElement : cmElements) { getCmElementMap().put(cmElement.getId(), cmElement); renewMostRecentLocalUpdate(cmElement.getLastUpdate()); } } } private void renewMostRecentLocalUpdate(Date lastUpdate){ if (lastUpdate!=null && lastUpdate.after(mostRecentLocalUpdate)){ mostRecentLocalUpdate = lastUpdate; } } public void remove(String id) throws InstanceNotFoundException, InternalErrorException { OpenEHRSessionManager.getAdministrationFacadeDelegate().removeCMElement(getCMElementClass(), id); } public void upsert(E cmElement) throws InternalErrorException { OpenEHRSessionManager.getAdministrationFacadeDelegate().upsertCMElement(cmElement); registerCMElementsInCache(Collections.singleton(cmElement)); } public static <E extends CMElement> String generateChecksum(Collection<E> cmElements) throws InternalErrorException { try { MessageDigest md = MessageDigest.getInstance("MD5"); List<E> cmElementList = getSortedCMElementList(cmElements); return getMD5Checksum(md, cmElementList); } catch (NoSuchAlgorithmException e) { throw new InternalErrorException(e); } } private static <E extends CMElement> String getMD5Checksum(MessageDigest md, List<E> cmElementList) { for(E cmElement : cmElementList){ md.update(cmElement.getSource().getBytes()); } byte[] md5Bytes = md.digest(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < md5Bytes.length; i++) { sb.append(Integer.toString((md5Bytes[i] & 0xff) + 0x100, 16).substring(1)); } return sb.toString(); } private static <E extends CMElement> List<E> getSortedCMElementList(Collection<E> cmElements) { List<E> cmElementList = new ArrayList<E>(); cmElementList.addAll(cmElements); Collections.sort(cmElementList, new CMElementComparator()); return cmElementList; } public <E extends CMElement>String getServerChecksum() throws InternalErrorException { return OpenEHRSessionManager.getAdministrationFacadeDelegate().getChecksumForCMElements(getCMElementClass()); } public String getCachedChecksum() throws InternalErrorException { if (!useCache){ throw new InternalErrorException(new UnsupportedOperationException("Cannot return checksum from cache if it is not used.")); } return generateChecksum(getCmElementMap().values()); } private synchronized Map<String, E> getCmElementMap() { if (cmElementMap == null) { cmElementMap = new HashMap<String, E>(); } return cmElementMap; } public void setUseCache(boolean useCache){ this.useCache = useCache; } public abstract Class<E> getCMElementClass(); }