/*
* (c) 2008- RANDI2 Core Development Team
*
* This file is part of RANDI2.
*
* RANDI2 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.
*
* RANDI2 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
* RANDI2. If not, see <http://www.gnu.org/licenses/>.
*/
package de.randi2.services;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.validation.ValidationException;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import de.randi2.dao.TrialDao;
import de.randi2.dao.TrialSiteDao;
import de.randi2.model.AbstractDomainObject;
import de.randi2.model.Login;
import de.randi2.model.TreatmentArm;
import de.randi2.model.Trial;
import de.randi2.model.TrialSite;
import de.randi2.model.TrialSubject;
import de.randi2.model.criteria.AbstractCriterion;
import de.randi2.model.enumerations.TrialStatus;
import de.randi2.model.exceptions.TrialStateException;
import de.randi2.model.randomization.ResponseAdaptiveRConfig;
import de.randi2.randomization.ResponseAdaptiveRandomization;
import de.randi2.utility.logging.LogService;
import de.randi2.utility.mail.MailServiceInterface;
import de.randi2.utility.mail.exceptions.MailErrorException;
import de.randi2.utility.logging.LogEntry.ActionType;
@Service("trialService")
public class TrialServiceImpl implements TrialService {
private Logger logger = Logger.getLogger(TrialServiceImpl.class);
@Autowired
private TrialDao trialDao;
@Autowired
private LogService logService;
@Autowired
private TrialSiteDao trialSiteDao;
protected EntityManager entityManager;
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Autowired
private MailServiceInterface mailService;
@Override
@Secured({ "ACL_TRIAL_CREATE" })
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void create(Trial newTrial) {
logger.info("user: "
+ SecurityContextHolder.getContext().getAuthentication()
.getName() + " create a new trial site with name "
+ newTrial.getName());
// added relationship between trial and treatment arm
for (TreatmentArm tA : newTrial.getTreatmentArms()) {
tA.setTrial(newTrial);
}
trialDao.create(newTrial);
}
@Override
// secured with own SecurityAspect
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Trial randomize(Trial trial, TrialSubject subject) throws IllegalArgumentException {
trial = entityManager.find(Trial.class, trial.getId());
if (subject.getIdentification() != null && !isTrialSubjectIdentficationUnique(trial, subject)) {
throw new IllegalArgumentException("Trial subject identifier is not unique");
}
logger.debug("user: "
+ SecurityContextHolder.getContext().getAuthentication()
.getName() + " randomized in trial " + trial.getName());
subject.setTrialSite(trialSiteDao.get(((Login) SecurityContextHolder
.getContext().getAuthentication().getPrincipal()).getPerson())); // TODO
// if
// trialSiteDao.get
// ==
// null
subject.setInvestigator(((Login) SecurityContextHolder.getContext()
.getAuthentication().getPrincipal()));
TreatmentArm assignedArm = trial.getRandomizationConfiguration()
.getAlgorithm().randomize(subject);
subject.setArm(assignedArm);
subject.setRandNumber(subject.getTrialSite().getName() + "_"
+ trial.getAbbreviation() + "_" + assignedArm.getName() + "_"
+ (assignedArm.getSubjects().size() + 1));
subject.setCounter((trial.getSubjects().size() + 1));
if (subject.getIdentification() == null)
subject.setIdentification(subject.getRandNumber());
entityManager.persist(subject);
assignedArm.addSubject(subject);
Trial t = trialDao.update(trial);
entityManager.flush();
try {
sendRandomisationMail(trial, ((Login) SecurityContextHolder
.getContext().getAuthentication().getPrincipal()), subject);
} catch (MailErrorException e) {
logger.error("mail error", e);
}
return t;
}
private boolean isTrialSubjectIdentficationUnique(Trial trial,
TrialSubject newSubject) {
for (TrialSubject subject : trial.getSubjects()) {
if (subject.getIdentification().equalsIgnoreCase(
newSubject.getIdentification()))
return false;
}
return true;
}
@Override
@Secured({ "ACL_TRIAL_WRITE" })
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Trial update(Trial trial) throws TrialStateException,
IllegalArgumentException {
if (trial == null) {
throw new IllegalArgumentException("Trial can't be NULL");
} else if (trial.getId() == AbstractDomainObject.NOT_YET_SAVED_ID) {
throw new IllegalArgumentException(
"Trial must be a persistent object");
}
/*
* Get the old version of the object
*/
Trial oldObject = trialDao.get(trial.getId());
switch (oldObject.getStatus()) {
case ACTIVE:
/*
* If object has been change, examine the changes
*/
if (!oldObject.equals(trial)) {
/*
* Clone the object revert the allowed changes and check if the
* object's are the same
*/
Trial temp = clone(trial);
temp.setStatus(oldObject.getStatus());
temp.setProtocol(oldObject.getProtocol());
temp.setEndDate(oldObject.getEndDate());
temp.setDescription(oldObject.getDescription());
temp.setVersion(oldObject.getVersion());
/*
* If not throw an exception
*/
if (!temp.equals(oldObject))
throw new TrialStateException(
"Object changes not permitted!");
/*
* If only the allowed attribute has been changed check the
* state
*/
else {
if (trial.getStatus() == TrialStatus.IN_PREPARATION)
throw new TrialStateException(
"Status change not permitted");
}
}
break;
case FINISHED:
throw new TrialStateException("Object changes not permitted!");
case IN_PREPARATION:
/*
* If object has been change, examine the changes
*/
if (!oldObject.equals(trial)) {
/*
* Check if the state (if changed) is ACTIVE
*/
if (trial.getStatus() != TrialStatus.IN_PREPARATION
&& trial.getStatus() != TrialStatus.ACTIVE)
throw new TrialStateException("Status change not permitted");
}
break;
case PAUSED:
/*
* If object has been change, examine the changes
*/
if (!oldObject.equals(trial)) {
/*
* Clone the object revert the allowed changes and check if the
* object's are the same
*/
Trial temp = clone(trial);
temp.setStatus(oldObject.getStatus());
temp.setProtocol(oldObject.getProtocol());
temp.setEndDate(oldObject.getEndDate());
temp.setDescription(oldObject.getDescription());
temp.setVersion(oldObject.getVersion());
/*
* If not throw an exception
*/
if (!temp.equals(oldObject))
throw new TrialStateException(
"Object changes not permitted!");
/*
* If only the allowed attribute has been changed check the
* state
*/
else {
if (trial.getStatus() == TrialStatus.IN_PREPARATION)
throw new TrialStateException(
"Status change not permitted");
}
}
break;
}
logger.info("user: "
+ SecurityContextHolder.getContext().getAuthentication()
.getName() + " update trial site with name "
+ trial.getName() + "(id: " + trial.getId() + ")");
logService.logTrialChange(ActionType.UPDATE, SecurityContextHolder
.getContext().getAuthentication().getName(), oldObject, trial);
return trialDao.update(trial);
}
@Override
@Secured({ "AFTER_ACL_COLLECTION_READ" })
@Transactional(propagation = Propagation.REQUIRED)
public List<Trial> getAll() {
logger.info("user: "
+ SecurityContextHolder.getContext().getAuthentication()
.getName() + " get all trials");
return trialDao.getAll();
}
@Override
@Secured({ "ROLE_USER", "AFTER_ACL_READ" })
@Transactional(propagation = Propagation.REQUIRED)
public Trial getObject(long objectID) {
logger.info("user: "
+ SecurityContextHolder.getContext().getAuthentication()
.getName() + " get trial site with id=" + objectID);
Trial trial = trialDao.get(objectID);
// lazy loading for participating sites and subject criteria
if (trial.getParticipatingSites().size() > 0)
trial.getParticipatingSites().iterator().next();
if (trial.getCriteria().size() > 0) {
trial.getCriteria().get(0);
for (AbstractCriterion<?, ?> crit : trial.getCriteria()) {
if (crit.getStrata().size() > 0)
crit.getStrata().get(0);
}
}
for (TreatmentArm arm : trial.getTreatmentArms()) {
if (arm.getSubjects().size() > 0)
arm.getSubjects().get(0);
}
return trial;
}
private void sendRandomisationMail(Trial trial, Login user,
TrialSubject subject) {
Map<String, Object> newUserMessageFields = new HashMap<String, Object>();
newUserMessageFields.put("user", user);
newUserMessageFields.put("trial", trial);
newUserMessageFields.put("trialSubject", subject);
// Map of variables for the subject
Map<String, Object> newUserSubjectFields = new HashMap<String, Object>();
newUserSubjectFields.put("trialName", trial.getName());
Locale language = user.getPrefLocale();
mailService.sendMail(user.getPerson().getEmail(), "Randomize",
language, newUserMessageFields, newUserSubjectFields);
newUserMessageFields = new HashMap<String, Object>();
newUserMessageFields.put("user", trial.getSponsorInvestigator()
.getLogin());
newUserMessageFields.put("trial", trial);
newUserMessageFields.put("trialSubject", subject);
newUserMessageFields.put("url", "http://randi2.com/CHANGEME");
// Map of variables for the subject
try {
language = trial.getSponsorInvestigator().getLogin()
.getPrefLocale();
} catch (Exception e) {
language = Locale.getDefault();
}
mailService.sendMail(trial.getSponsorInvestigator().getEmail(),
"Randomize", language, newUserMessageFields,
newUserSubjectFields);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public List<TrialSubject> getSubjects(Trial trial, Login investigator) {
if (trial.getId() > 0) {
trial = trialDao.refresh(trial);
return trialDao.getSubjects(trial, investigator);
} else
return new ArrayList<TrialSubject>();
}
/**
* Util method for cloning objects
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T> T clone(T o) {
T clone = null;
try {
clone = (T) o.getClass().newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// Walk up the superclass hierarchy
for (Class obj = o.getClass(); !obj.equals(Object.class); obj = obj
.getSuperclass()) {
Field[] fields = obj.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
try {
// for each class/suerclass, copy all fields
// from this object to the clone
fields[i].set(clone, fields[i].get(o));
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
}
}
return clone;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addResponse(Trial trial, TrialSubject subject) {
if (subject == null) {
throw new IllegalArgumentException("Trial subject can't be NULL");
} else if (subject.getId() == AbstractDomainObject.NOT_YET_SAVED_ID) {
throw new IllegalArgumentException(
"Trial subject must be a persistent object");
}
TrialSubject tmp = (TrialSubject) entityManager.createQuery("from TrialSubject t where t.id = ?").setParameter(1, subject.getId()).getSingleResult();
if (tmp.getResponseProperty() != null) {
throw new ValidationException(
"Response for trial subject with id: " + subject.getId()
+ " has been already saved");
}
trial = entityManager.find(Trial.class, trial.getId());
logger.debug("user: "
+ SecurityContextHolder.getContext().getAuthentication()
.getName() + " added response in trial " + trial.getName());
if (trial.getRandomizationConfiguration() instanceof ResponseAdaptiveRConfig) {
ResponseAdaptiveRandomization algorithm = (ResponseAdaptiveRandomization) trial
.getRandomizationConfiguration().getAlgorithm();
algorithm.addResponse(subject);
entityManager.persist(subject.getResponseProperty());
entityManager.merge(subject);
}
}
@Override
@Secured({ "ROLE_USER", "AFTER_ACL_COLLECTION_READ" })
public List<Trial> getAll(TrialSite site) {
if (site == null)
return null;
return trialDao.getAll(site);
}
}