/*
###############################################################################
# #
# 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);
}
}
}