/* ############################################################################### # # # Copyright (C) 2011-2016 OpenMEAP, Inc. # # Credits to Jonathan Schang & Rob Thacher # # # # Released under the LGPLv3 # # # # OpenMEAP is free software: you can redistribute it and/or modify # # it under the terms of the GNU Lesser General Public License as published # # by the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # OpenMEAP 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 Lesser General Public License for more details. # # # # You should have received a copy of the GNU Lesser General Public License # # along with OpenMEAP. If not, see <http://www.gnu.org/licenses/>. # # # ############################################################################### */ package com.openmeap.model; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import javax.persistence.PersistenceException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import com.openmeap.AuthorizationException; import com.openmeap.Authorizer; import com.openmeap.event.EventNotificationException; import com.openmeap.event.MessagesEvent; import com.openmeap.event.ProcessingEvent; import com.openmeap.file.FileOperationException; import com.openmeap.file.FileOperationManager; import com.openmeap.model.dto.ClusterNode; import com.openmeap.model.dto.GlobalSettings; import com.openmeap.model.event.ModelEntityEvent; import com.openmeap.model.event.notifier.ModelServiceEventNotifier; import com.openmeap.model.event.notifier.ModelServiceEventNotifier.CutPoint; /** * Handles all business logic related to the model Entity objects. * * @author schang */ public class ModelManagerImpl implements ModelManager, ApplicationContextAware { private Collection<ModelServiceEventNotifier> eventNotifiers = null; private ModelService modelService; private FileOperationManager fileManager; private Logger logger = LoggerFactory.getLogger(ModelManagerImpl.class); private ApplicationContext context = null; private Authorizer authorizer = new Authorizer() { @Override public Boolean may(Action action, Object object) { return Boolean.TRUE; } }; private Map<Thread,List<ModelEntityEvent>> eventQueue = new HashMap<Thread,List<ModelEntityEvent>>(); public ModelManagerImpl() {} public ModelManagerImpl(ModelService service) { setModelService(service); } @Override public ModelManager begin() { try { fileManager.begin(); } catch (FileOperationException e) { throw new PersistenceException("An exception was thrown creating a file-resource transaction: "+e.getMessage(),e); } modelService.begin(); return this; } @Override public ModelManager commit() throws PersistenceException { return commit(null); } @Override public ModelManager commit(List<ProcessingEvent> events) throws PersistenceException { processModelEntityEventQueue(CutPoint.IN_COMMIT_BEFORE_COMMIT, events); try { if(fileManager.isTransactionActive()) { fileManager.commit(); } } catch (FileOperationException e) { throw new PersistenceException("An exception was thrown commiting a file-resource transaction: "+e.getMessage(),e); } modelService.commit(); processModelEntityEventQueue(CutPoint.IN_COMMIT_AFTER_COMMIT, events); clearModelEntityEventQueue(); return this; } @Override public void rollback() throws PersistenceException { clearModelEntityEventQueue(); try { if(fileManager.isTransactionActive()) { fileManager.rollback(); } } catch (FileOperationException e) { throw new PersistenceException("An exception was thrown rolling back a file-resource transaction:"+e.getMessage(),e); } modelService.rollback(); } @Override public <T extends ModelEntity> ModelManager refresh(T obj2Refresh, List<ProcessingEvent> events) throws PersistenceException { ModelEntityEvent event = new ModelEntityEvent(ModelServiceOperation.REFRESH,obj2Refresh); stashModelEntityEventTillCommit(event); callEventNotifiers(CutPoint.BEFORE_OPERATION,event,events); modelService.refresh(obj2Refresh); callEventNotifiers(CutPoint.AFTER_OPERATION,event,events); return this; } @Override public <T extends ModelEntity> ModelManager delete(T entity, List<ProcessingEvent> events) { authorize(entity,Authorizer.Action.DELETE); ModelEntityEvent event = new ModelEntityEvent(ModelServiceOperation.DELETE,entity); stashModelEntityEventTillCommit(event); callEventNotifiers(CutPoint.BEFORE_OPERATION,event,events); _delete(entity,events); callEventNotifiers(CutPoint.AFTER_OPERATION,event,events); return this; } @Override public <T extends ModelEntity> T addModify(T entity, List<ProcessingEvent> events) throws InvalidPropertiesException, PersistenceException { T revised = entity; authorize(entity,determineCreateUpdateAction(entity)); ModelEntityEvent event = new ModelEntityEvent(ModelServiceOperation.SAVE_OR_UPDATE,entity); stashModelEntityEventTillCommit(event); callEventNotifiers(CutPoint.BEFORE_OPERATION,event,events); revised = (T)event.getPayload(); revised = (T) _addModify(revised,events); event.setPayload(revised); callEventNotifiers(CutPoint.AFTER_OPERATION,event,events); revised = (T)event.getPayload(); validate(revised); return revised; } @Override public GlobalSettings getGlobalSettings() { GlobalSettings settings = modelService.findByPrimaryKey(GlobalSettings.class,(Long)1L); boolean update = false; if( settings==null ) { settings = new GlobalSettings(); settings.setServiceManagementAuthSalt(UUID.randomUUID().toString()); update = true; } if( settings.getServiceManagementAuthSalt()==null || settings.getServiceManagementAuthSalt().trim().length()==0 ) { settings.setServiceManagementAuthSalt(UUID.randomUUID().toString()); update = true; } if(update) { try { modelService.begin(); settings = modelService.saveOrUpdate(settings); modelService.commit(); } catch(Exception e) { modelService.rollback(); throw new PersistenceException(e); } } return settings; } @Override public ClusterNode getClusterNode() { if( context!=null ) { try { Map<String,String> servicesWebProperties = (Map<String,String>)context.getBean("openmeapServicesWebPropertiesMap"); String serviceUrl = null; synchronized(servicesWebProperties) { serviceUrl = (String)servicesWebProperties.get("clusterNodeUrlPrefix"); } return this.getGlobalSettings().getClusterNode(serviceUrl); } catch(Exception e) { logger.warn("{}",e); } } return null; } /* * GETTERS/SETTERS */ public void setEventNotifiers(Collection<ModelServiceEventNotifier> handlers) { eventNotifiers = handlers; } public Collection<ModelServiceEventNotifier> getEventNotifiers() { return eventNotifiers; } public void setAuthorizer(Authorizer auth) { this.authorizer=auth; } public Authorizer getAuthorizer() { return authorizer; } public void setModelService(ModelService service) { modelService = service; } public ModelService getModelService() { return modelService; } public void setFileManager(FileOperationManager fileManager) { this.fileManager = fileManager; } public FileOperationManager getFileManager() { return fileManager; } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } /* * PRIVATE METHODS */ private <T extends ModelEntity> void _delete(T entity, List<ProcessingEvent> events) { if( ! getAuthorizer().may(Authorizer.Action.DELETE, entity) ) { throw new PersistenceException(new AuthorizationException("The user logged in does not have permissions to DELETE "+entity.getClass().getSimpleName()+" objects.")); } modelService.delete(entity); } private <T extends ModelEntity> T _addModify(T entity, List<ProcessingEvent> events) throws InvalidPropertiesException, PersistenceException { authorizeAndValidate(entity,determineCreateUpdateAction(entity)); T o = modelService.saveOrUpdate(entity); return o; } private void stashModelEntityEventTillCommit(ModelEntityEvent event) { List<ModelEntityEvent> modelEvents = eventQueue.get(Thread.currentThread()); if(modelEvents==null) { modelEvents = new ArrayList<ModelEntityEvent>(); eventQueue.put(Thread.currentThread(),modelEvents); } if(!modelEvents.contains(event)) { modelEvents.add(event); } } private void processModelEntityEventQueue(CutPoint cutPoint,List<ProcessingEvent> events) { List<ModelEntityEvent> modelEvents; if( (modelEvents = eventQueue.get(Thread.currentThread()))!=null ) { int size = modelEvents.size(); for(int i=0;i<size;i++) { ModelEntityEvent event = modelEvents.get(i); callEventNotifiers(cutPoint,event,events); } } } private void clearModelEntityEventQueue() { List<ModelEntityEvent> modelEvents; if( (modelEvents = eventQueue.get(Thread.currentThread()))!=null ) { eventQueue.remove(Thread.currentThread()); } } private void callEventNotifiers(CutPoint cutPoint, ModelEntityEvent event, List<ProcessingEvent> events) { if(eventNotifiers==null) { return; } for( ModelServiceEventNotifier handler : eventNotifiers ) { try { if( handler.notifiesFor(event.getOperation(),(ModelEntity)event.getPayload()) ) { switch(cutPoint) { case AFTER_OPERATION: handler.onAfterOperation(event, events); break; case BEFORE_OPERATION: handler.onBeforeOperation(event, events); break; case IN_COMMIT_AFTER_COMMIT: handler.onInCommitAfterCommit(event, events); break; case IN_COMMIT_BEFORE_COMMIT: handler.onInCommitBeforeCommit(event, events); break; } } } catch( EventNotificationException e ) { String msg = String.format("EventNotificationException occurred: %s",e.getMessage()); logger.error(msg); if(events!=null) { events.add(new MessagesEvent(msg)); } } } } private Authorizer.Action determineCreateUpdateAction(ModelEntity entity) { Authorizer.Action action = Authorizer.Action.MODIFY; if( entity.getPk()==null ) { action = Authorizer.Action.CREATE; } return action; } private void authorizeAndValidate(ModelEntity entity, Authorizer.Action action) throws InvalidPropertiesException { authorize(entity,action); validate(entity); } private void authorize(ModelEntity entity, Authorizer.Action action) { if( ! getAuthorizer().may(action, entity) ) { throw new PersistenceException(new AuthorizationException("The user logged in does not have permissions to " + action.toString() + " the " + entity.getClass().getSimpleName())); } } private void validate(ModelEntity entity) throws InvalidPropertiesException { Map<Method,String> errors = entity.validate(); if( errors!=null ) { throw new InvalidPropertiesException(entity,errors); } } }