/*******************************************************************************
* Copyright (c) 2006-2010 Vienna University of Technology,
* Department of Software Technology and Interactive Systems
*
* All rights reserved. This program and the accompanying
* materials are made available under the terms of the
* Apache License, Version 2.0 which accompanies
* this distribution, and is available at
* http://www.apache.org/licenses/LICENSE-2.0
*******************************************************************************/
package eu.planets_project.pp.plato.action.project;
import java.io.Serializable;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.List;
import javax.annotation.PreDestroy;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.hibernate.Hibernate;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.RaiseEvent;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.annotations.datamodel.DataModel;
import org.jboss.seam.annotations.datamodel.DataModelSelection;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.faces.FacesMessages;
import eu.planets_project.pp.plato.action.TestDataLoader;
import eu.planets_project.pp.plato.action.interfaces.IAnalyseResults;
import eu.planets_project.pp.plato.action.interfaces.ICreateExecutablePlan;
import eu.planets_project.pp.plato.action.interfaces.IDefineAlternatives;
import eu.planets_project.pp.plato.action.interfaces.IDefineBasis;
import eu.planets_project.pp.plato.action.interfaces.IDefinePlan;
import eu.planets_project.pp.plato.action.interfaces.IDefineSampleRecords;
import eu.planets_project.pp.plato.action.interfaces.IDevelopExperiments;
import eu.planets_project.pp.plato.action.interfaces.IEvaluateExperiments;
import eu.planets_project.pp.plato.action.interfaces.IFastTrackAnalyseResults;
import eu.planets_project.pp.plato.action.interfaces.IFastTrackDefineRequirements;
import eu.planets_project.pp.plato.action.interfaces.IFastTrackEvaluateAlternatives;
import eu.planets_project.pp.plato.action.interfaces.IGoNoGo;
import eu.planets_project.pp.plato.action.interfaces.IIdentifyRequirements;
import eu.planets_project.pp.plato.action.interfaces.IRunExperiments;
import eu.planets_project.pp.plato.action.interfaces.ISetImportanceFactorsAction;
import eu.planets_project.pp.plato.action.interfaces.ITransformMeasuredValues;
import eu.planets_project.pp.plato.action.interfaces.IUtilAction;
import eu.planets_project.pp.plato.action.interfaces.IValidatePlan;
import eu.planets_project.pp.plato.model.Plan;
import eu.planets_project.pp.plato.model.PlanProperties;
import eu.planets_project.pp.plato.model.PlanState;
import eu.planets_project.pp.plato.model.User;
import eu.planets_project.pp.plato.model.Values;
import eu.planets_project.pp.plato.model.transform.OrdinalTransformer;
import eu.planets_project.pp.plato.model.transform.Transformer;
import eu.planets_project.pp.plato.model.tree.Leaf;
import eu.planets_project.pp.plato.model.tree.Node;
import eu.planets_project.pp.plato.model.tree.TreeNode;
import eu.planets_project.pp.plato.util.IDownloadManagerHelperBean;
import eu.planets_project.pp.plato.util.PlatoLogger;
/**
* Loads PPs
*
* Implements operations specified in {@link eu.planets_project.pp.plato.action.interfaces.LoadPlanAction}
*
* @author Hannes Kulovits
*/
@Name("loadPlan")
@Scope(ScopeType.SESSION)
public class LoadPlanAction implements Serializable {
private static final long serialVersionUID = -5699231548828633148L;
private enum WhichProjects {
ALLPROJECTS,
PUBLICPROJECTS,
MYPROJECTS,
FTEPROJECTS,
PUBLICFTEPROJECTS;
}
private static final Log log = PlatoLogger.getLogger(LoadPlanAction.class);
@In(required = false, create = true)
@Out(scope = ScopeType.APPLICATION)
private IUtilAction utilAction;
@In
private FacesContext facesContext;
/**
* This download manager <b>helper</b> bean is used in the DownloadServlet because
* injecting or looking up the EntityManager in a http servlet is just pain.
*
* @see eu.planets_project.pp.plato.util.DownloadServlet
*/
@In(create=true)
@Out(required=true)
private IDownloadManagerHelperBean downloadManagerHelperBean;
/**
* we also have to inject the selected project because of our newProject
* observer. When a new project is created, selectedPlan must be injected
* into LoadPlanAction. If we would not inject it, selectedPlan would
* be outjected as null.
*/
@In(required = false)
@Out(required = false, scope = ScopeType.SESSION)
private Plan selectedPlan;
/**
* Contains projects from database.
*/
@DataModel
private List<PlanProperties> projectList;
/**
* Entity manager for persistence.
*/
@In
EntityManager em;
public void setEm(EntityManager em) {
this.em = em;
}
/**
* Plan selected by user from list {@link #projectList}.
*/
@DataModelSelection(value = "projectList")
private PlanProperties selection;
/**
* if the session ends, open projects must be closed but it is not sure that
* the selectedPlan is still in the session context therefore keep the id
* of the planProperties separately
*/
private int planPropertiesId = 0;
@In(create = true)
private TestDataLoader testDataLoader;
private WhichProjects lastLoadMode = WhichProjects.MYPROJECTS;
public WhichProjects getLastLoadMode() {
return lastLoadMode;
}
public void setLastLoadMode(WhichProjects lastLoadMode) {
this.lastLoadMode = lastLoadMode;
}
@Observer("projectListChanged")
public String relist() {
list(lastLoadMode);
log.debug("reloading in "+lastLoadMode+": number of projects loaded: " + projectList.size());
return "success";
}
@Transactional
@PreDestroy
public void preDestroy() {
// maybe there is an open project left - close it
unlockProject();
}
@Destroy
public void destroy() {
}
/**
* Initializes the user and loads the project from database when session
* component is created.
* This is called when this bean is created.
*/
@Create
public void onCreate() {
listMyProjects();
}
public String listFTEProjects() {
list(WhichProjects.FTEPROJECTS);
return "success";
}
public String listAllProjects() {
list(WhichProjects.ALLPROJECTS);
return "success";
}
public String listMyProjects() {
list(WhichProjects.MYPROJECTS);
return "success";
}
public String listPublicProjects() {
list(WhichProjects.PUBLICPROJECTS);
return "success";
}
public String listPublicFTEResults() {
list(WhichProjects.PUBLICFTEPROJECTS);
return "success";
}
@Out
private String planlist = "my plans";
/**
* Load projects from data:
* <ul>
* <li>When administrator is logged in, all projects are loaded from database.</li>
* <li>For any other user, only projects are loaded that are <b>not</b> set to private by another user.</li>
* </ul>
*
* Furthermore, checks if project is locked by the current user, who may thus be allowed to unlock the project.
* In this case the {@link PlanProperties#isAllowReload()} is set. In the user interface this flag means
* that an 'Unlock' button is displayed.
*/
public void list(WhichProjects whichProjects) {
Contexts.getSessionContext().remove("projectList");
// changed flag needs to be initialized here because list is the first method that is called
// when Plato is accessed.
// Injecting it as a member variable with create=true didn't work for some reason.
String changed = (String) Contexts.getSessionContext().get("changed");
if (changed == null)
Contexts.getSessionContext().set("changed", "");
String projectListQuery;
if (whichProjects == WhichProjects.MYPROJECTS) {
// load user's projects
//projectListQuery = "select p from PlanProperties p where (p.owner = '"+ user.getUsername() + "' and not (p.projectBasis.identificationCode LIKE 'FAST-TRACK-%'))" + " order by p.id" ;
projectListQuery = "select p.planProperties from Plan p where" +
" (p.planProperties.owner = '"+ user.getUsername() + "')" +
" and (p.projectBasis.identificationCode = null or p.projectBasis.identificationCode NOT LIKE 'FAST-TRACK-%')" +
" order by p.planProperties.id";
setPlanlist("my preservation plans");
} else if (whichProjects == WhichProjects.ALLPROJECTS && (user.isAdmin())) {
// load all projects, public and private,
// but ONLY if the user is an admin
projectListQuery = "select p from PlanProperties p order by p.id";
setPlanlist("all preservation plans");
} else if (whichProjects == WhichProjects.FTEPROJECTS) {
projectListQuery = "select p.planProperties from Plan p where" +
" (p.planProperties.owner = '"+ user.getUsername() + "')" +
" and (p.projectBasis.identificationCode LIKE 'FAST-TRACK-%')" +
" order by p.planProperties.id";
setPlanlist("fast track plans");
} else if (whichProjects == WhichProjects.PUBLICFTEPROJECTS) {
projectListQuery = "select p.planProperties from Plan p where" +
" (p.planProperties.privateProject = false )" +
" and (p.projectBasis.identificationCode LIKE 'FAST-TRACK-%')" +
" order by p.planProperties.id";
setPlanlist("public fast track plans");
} else {
// load all public projects, which includes those with published reports
projectListQuery = "select p.planProperties from Plan p where ((p.planProperties.privateProject = false)" +
" or (p.planProperties.privateProject = true and p.planProperties.reportPublic = true)) and p.projectBasis.identificationCode NOT LIKE 'FAST-TRACK-%' " +
" order by p.planProperties.id" ;
setPlanlist("public preservation plans");
}
projectList = em.createQuery(projectListQuery).getResultList();
try {
// if there are no PUBLIC projects, we want to load them:
if (projectList.size() == 0 && (whichProjects != WhichProjects.MYPROJECTS && whichProjects != WhichProjects.FTEPROJECTS)) {
testDataLoader.importAutoloadPlans();
projectList = em.createQuery(projectListQuery).getResultList();
}
} catch (Exception e) {
FacesMessages.instance().add(FacesMessage.SEVERITY_ERROR, "Failed to insert testdata.");
log.fatal("Testdataloader failed.", e);
}
//
// readOnly in PlanProperties is *transient*, it is used in loadPlan.xhtml
// to determine if a user is allowed to load a project
//
for (PlanProperties pp : projectList) {
//
// a project may not be loaded when
// ... it is set to private
// ... AND the user currently logged in is not the administrator
// ... AND the user currently logged in is not the owner of that project
boolean readOnly = pp.isPrivateProject()
&& !user.isAdmin()
&& !user.getUsername().equals(pp.getOwner());
boolean allowReload = pp.getOpenedByUser().equals(user.getUsername())
&& selectedPlan == null;
pp.setReadOnly(readOnly);
pp.setAllowReload(allowReload);
}
setLastLoadMode(whichProjects);
}
public void setPlanlist(String planlist) {
this.planlist = planlist;
}
@In(create = true)
ProjectSettings projectSettings;
@In(create = true)
IFastTrackDefineRequirements FTrequirements;
@In(create = true)
IFastTrackEvaluateAlternatives FTevaluate;
@In(create = true)
IFastTrackAnalyseResults FTanalyse;
@In(create = true)
IDefineBasis defineBasis;
@In(create = true)
IDefineSampleRecords defineSampleRecords;
@In(create = true)
IIdentifyRequirements identifyRequirements;
@In(create = true)
IDefineAlternatives defineAlternatives;
@In(create = true)
IGoNoGo gonogo;
@In(create = true)
IDevelopExperiments devexperiments;
@In(create = true)
IRunExperiments runexperiments;
@In(create = true)
IEvaluateExperiments evalexperiments;
@In(create = true)
ITransformMeasuredValues transform;
@In(create = true)
ISetImportanceFactorsAction importanceFactors;
@In(create = true)
IAnalyseResults analyseResults;
@In(create = true)
ICreateExecutablePlan createExecutablePlan;
@In(create = true)
IDefinePlan definePlan;
@In(create = true)
IValidatePlan validatePlan;
@In
private User user;
public void setPlanPropertiesID(int id){
planPropertiesId = id;
}
/**
* Loads a project from database that was selected by the user.
*
* Previously loaded project is unlocked.
*
* Forwards the user to respective workflowstep by calling the workflow step's
* <code>enter</code> method.
*/
@RaiseEvent("reload")
public String load() {
String id = "";
try {
id = ((HttpServletRequest) facesContext.getExternalContext()
.getRequest()).getSession().getId();
} catch (RuntimeException e) {
log.debug("Couldn't get SessionID");
}
log.info("Session="+id+" of user "+user.getUsername()
+ " is loading project "+selection.getId()
+ " - "+selection.getName());
// try to lock the project
Query q = em
.createQuery("update PlanProperties pp set pp.openHandle = 1, pp.openedByUser = '" + user.getUsername() + "' where (pp.openHandle is null or pp.openHandle = 0) and pp.id = "
+ selection.getId());
int num = q.executeUpdate();
if (num < 1) {
FacesMessages
.instance()
.add(
FacesMessage.SEVERITY_INFO,
"In the meantime the plan has been loaded by an other user. Please choose another plan.");
relist();
log.debug("Locking plan failed");
return null;
}
List<Plan> list = em.createQuery(
"select p from Plan p where p.planProperties.id = "
+ selection.getId()).getResultList();
// we locked the project before be we now cannot load it. not good.
if (list.size() != 1) {
FacesMessages.instance().add(FacesMessage.SEVERITY_ERROR,
"An unexpected error has occured while loading the plan.");
return null;
}
// ok - the selected project is free - unlock the old project
unlockProject();
// load the selected project (and keep the id!)
setPlanPropertiesID(selection.getId());
selectedPlan = em.find(Plan.class, list.get(0).getId());
// Strangely enough the outjection doesnt work here, so to be sure we set the member explicitly
Contexts.getSessionContext().set("selectedPlan", selectedPlan);
this.initializeProject(selectedPlan);
log.info("Plan " + selectedPlan.getPlanProperties().getName()
+ " loaded!");
String msg = "The plan you loaded has reached the state "
+ selectedPlan.getState().getStateName()
+ ". Therefore you have been directed to the subsequent workflow step.";
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO, msg);
if (selectedPlan.isFastTrackEvaluationPlan()) {
switch (selectedPlan.getState().getValue()) {
case PlanState.FTE_INITIALISED:
return FTrequirements.enter();
case PlanState.FTE_REQUIREMENTS_DEFINED:
return FTevaluate.enter();
case PlanState.FTE_ALTERNATIVES_EVALUATED:
return FTanalyse.enter();
case PlanState.FTE_RESULTS_ANALYSED:
return FTanalyse.enter();
}
}
// redirect to step corresponding to the project's state:
switch (selectedPlan.getState().getValue()) {
case PlanState.BASIS_DEFINED:
return defineSampleRecords.enter();
case PlanState.RECORDS_CHOSEN:
return identifyRequirements.enter();
case PlanState.TREE_DEFINED:
return defineAlternatives.enter();
case PlanState.ALTERNATIVES_DEFINED:
return gonogo.enter();
case PlanState.GO_CHOSEN:
return devexperiments.enter();
case PlanState.EXPERIMENT_DEFINED:
return runexperiments.enter();
case PlanState.EXPERIMENT_PERFORMED:
return evalexperiments.enter();
case PlanState.RESULTS_CAPTURED:
return transform.enter();
case PlanState.TRANSFORMATION_DEFINED:
return importanceFactors.enter();
case PlanState.WEIGHTS_SET:
return analyseResults.enter();
case PlanState.ANALYSED:
return createExecutablePlan.enter();
case PlanState.EXECUTEABLE_PLAN_CREATED:
return definePlan.enter();
case PlanState.PLAN_DEFINED:
return validatePlan.enter();
case PlanState.PLAN_VALIDATED:
return validatePlan.enter();
default:
return defineBasis.enter();
}
}
public String startFastTrackEvaluation() {
unlockProject();
selectedPlan = new Plan();
selectedPlan.getPlanProperties().setAuthor(user.getFullName());
selectedPlan.getPlanProperties().setPrivateProject(true);
selectedPlan.getPlanProperties().setOwner(user.getUsername());
// set Fast Track properties
selectedPlan.getState().setValue(PlanState.FTE_INITIALISED);
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-kkmmss");
String timestamp = format.format(new Date(System.currentTimeMillis()));
String identificationCode = Plan.fastTrackEvaluationPrefix + timestamp;
selectedPlan.getProjectBasis().setIdentificationCode(identificationCode);
// We have to prevent the user from navigating to the step 'Load plan'
// because the user wouldn't be able to leave this step: Going to 'Define
// Basis' is not possible as the project hasn't been saved so far.
//
// We 'activate' the changed flag so that the user is asked to either
// save the project or discard changes.
TreeNode root = new Node();
root.setName("Root");
selectedPlan.getTree().setRoot(root);
// ok - the selected project is free - unlock the old project
unlockProject();
// load the selected project (and keep the id!)
setPlanPropertiesID(selectedPlan.getPlanProperties().getId());
// Strangely enough the outjection doesnt work here, so to be sure we set the member explicitly
Contexts.getSessionContext().set("selectedPlan", selectedPlan);
this.initializeProject(selectedPlan);
FTrequirements.enter();
return "success";
}
/**
* Hibernate initializes project and its parts.
*/
public void initializeProject(Plan p) {
Hibernate.initialize(p);
Hibernate.initialize(p.getAlternativesDefinition());
Hibernate.initialize(p.getSampleRecordsDefinition());
Hibernate.initialize(p.getTree());
initializeNodeRec(p.getTree().getRoot());
log.debug("plan initialised");
}
/**
* Traverses down the nodes in the tree and calls <code>Hibernate.initialize</code>
* for each leaf. This is necessary to provide the application with a convenient
* way of working with lazily initialized collections or proxies.
*
* @param node node from where initialization shall start
*/
private void initializeNodeRec(TreeNode node) {
Hibernate.initialize(node);
if (node.isLeaf()) {
Leaf leaf = (Leaf) node;
Transformer t = leaf.getTransformer();
Hibernate.initialize(t);
if (t instanceof OrdinalTransformer) {
OrdinalTransformer nt = (OrdinalTransformer) t;
Hibernate.initialize(nt.getMapping());
}
//log.debug("hibernate initialising Transformer: " + leaf.getTransformer());
for (Values value : leaf.getValueMap().values()) {
Hibernate.initialize(value);
}
} else if (node instanceof Node) {
Node recnode = (Node) node;
Hibernate.initialize(node.getChildren());
for (TreeNode newNode : recnode.getChildren()) {
initializeNodeRec(newNode);
}
}
}
/**
* Unlocks all projects in database.
*/
public void unlockAll() {
this.unlockQuery(false);
}
/**
* Unlocks certain projects in database (dependent on parameter)
*
* @param useId If this is true, only project with id {@link #planPropertiesId} will be unlocked;
* otherwise, all projects in database will be unlocked
*/
private void unlockQuery(boolean useId) {
String where = "";
if (useId) {
where = "where pp.id = " + planPropertiesId;
}
Query q = em
.createQuery("update PlanProperties pp set pp.openHandle = 0, pp.openedByUser = ''"
+ where);
try {
if (q.executeUpdate() < 1) {
log.debug("Unlocking plan failed.");
} else {
log.debug("Unlocked plan");
}
} catch (Throwable e) {
log.error("Unlocking plan failed:", e);
}
planPropertiesId = 0;
}
/**
* Unlocks project with id {@link #planPropertiesId}.
*/
public void unlockProject() {
if (planPropertiesId != 0) {
log.info("unlocking plan "+planPropertiesId);
utilAction.unlockPlan(planPropertiesId);
}
}
/**
* Unlocks project depending on data model selection {@link #selection}
*/
public String unlockselectedPlan() {
if (selection != null) {
planPropertiesId = selection.getId();
unlockQuery(true);
}
return relist();
}
/**
* Closes currently selected project {@link #selectedPlan}
*/
public String closeProject() {
if (selectedPlan != null) {
unlockProject();
selectedPlan = null;
} else {
log.debug("not unlocking any plan because none is open.");
}
return "success";
}
}