/* =============================================================================== /* =============================================================================== * * Part of the InfoGlue Content Management Platform (www.infoglue.org) * * =============================================================================== * * Copyright (C) * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2, as published by the * Free Software Foundation. See the file LICENSE.html for more information. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY, including 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 * this program; if not, write to the Free Software Foundation, Inc. / 59 Temple * Place, Suite 330 / Boston, MA 02111-1307 / USA. * * =============================================================================== */ package org.infoglue.cms.controllers.kernel.impl.simple; import java.io.PrintWriter; import java.io.StringWriter; import java.net.InetAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import net.sf.hibernate.HibernateException; import net.sf.hibernate.Session; import net.sf.hibernate.SessionFactory; import net.sf.hibernate.cfg.Configuration; import org.apache.log4j.Logger; import org.exolab.castor.jdo.Database; import org.infoglue.cms.entities.kernel.BaseEntityVO; import org.infoglue.cms.entities.mydesktop.WorkflowActionVO; import org.infoglue.cms.entities.mydesktop.WorkflowVO; import org.infoglue.cms.exception.Bug; import org.infoglue.cms.exception.SystemException; import org.infoglue.cms.security.InfoGluePrincipal; import org.infoglue.cms.util.CmsPropertyHandler; import org.infoglue.cms.util.mail.MailServiceFactory; import org.infoglue.cms.util.workflow.WorkflowFacade; import org.infoglue.deliver.util.CacheController; import com.opensymphony.module.propertyset.PropertySet; import com.opensymphony.workflow.WorkflowException; /** * This controller acts as the api towards the OSWorkflow Workflow-engine. * @author Mattias Bogeblad * @author <a href="mailto:mattias.bogeblad@modul1.se">Mattias Bogeblad</a> */ public class WorkflowController extends BaseController { private final static Logger logger = Logger.getLogger(UserPropertiesController.class.getName()); private static final WorkflowController controller = new WorkflowController(); private static SessionFactory hibernateSessionFactory; static { try { hibernateSessionFactory = new Configuration().configure().buildSessionFactory(); } catch (HibernateException e) { logger.error("An exception occurred when we tried to initialize the hibernateSessionFactory", e); throw new ExceptionInInitializerError(e); } } /** * Returns the WorkflowController singleton * @return a reference to a WorkflowController */ public static WorkflowController getController() { return controller; } private WorkflowController() {} /** * TODO: move; used by tests + CreateWorkflowInstanceAction */ public static Map createWorkflowParameters(final HttpServletRequest request) { final Map parameters = new HashMap(); parameters.putAll(request.getParameterMap()); parameters.put("request", request); return parameters; } /** * @param principal the user principal representing the desired user * @param name the name of the workflow to create. * @param actionId the ID of the initial action * @param inputs the inputs to the workflow * @return a WorkflowVO representing the newly created workflow instance * @throws SystemException if an error occurs while initiaizing the workflow */ public WorkflowVO initializeWorkflow(InfoGluePrincipal principal, String name, int actionId, Map inputs) throws SystemException { WorkflowVO workflowVO = null; try { Session session = null; net.sf.hibernate.Transaction tx = null; try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); if(getIsAccessApproved(name, principal)) { WorkflowFacade wf = new WorkflowFacade(principal, name, actionId, inputs, hibernateSessionFactory, session); workflowVO = wf.createWorkflowVO(); session.flush(); tx.commit(); } else { throw new Bug("You are not allowed to create " + name + " workflows."); } } catch (Exception e) { logger.error("An error occurred when we tries to run initializeWorkflow():" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction:" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close:" + e.getMessage(), e); } } } catch (Exception e) { throw new SystemException(e); } return workflowVO; } public WorkflowVO getWorkflow(String workflowName, InfoGluePrincipal principal) throws SystemException, Exception { WorkflowVO workflow = null; List<WorkflowVO> workflows = getAvailableWorkflowVOList(principal); Iterator<WorkflowVO> workflowsIterator = workflows.iterator(); while(workflowsIterator.hasNext()) { WorkflowVO workflowVO = workflowsIterator.next(); String fromEncoding = CmsPropertyHandler.getAssetKeyFromEncoding(); if(fromEncoding == null) fromEncoding = "iso-8859-1"; String toEncoding = CmsPropertyHandler.getAssetKeyToEncoding(); if(toEncoding == null) toEncoding = "utf-8"; String encodedName = new String(workflowName.getBytes(fromEncoding), toEncoding); logger.info("" + workflowVO.getName() + "=" + workflowName + "/" + encodedName); if(workflowVO.getName().equals(workflowName) || workflowVO.getName().equals(encodedName)) { workflow = workflowVO; break; } } return workflow; } public WorkflowVO getCurrentWorkflow(Long workflowId, InfoGluePrincipal principal) throws SystemException { WorkflowVO workflow = null; List<WorkflowVO> workflows = getCurrentWorkflowVOList(principal, null); logger.info("workflows:" + workflows); if(workflows != null) { Iterator<WorkflowVO> workflowsIterator = workflows.iterator(); while(workflowsIterator.hasNext()) { WorkflowVO workflowVO = workflowsIterator.next(); if(workflowVO.getWorkflowId().longValue() == workflowId.longValue()) { workflow = workflowVO; break; } } } return workflow; } /** * Returns a list of all available workflows, i.e., workflows defined in workflows.xml * @param userPrincipal a user principal * @return a list WorkflowVOs representing available workflows */ public List<WorkflowVO> getAvailableWorkflowVOList(InfoGluePrincipal userPrincipal) throws SystemException { final List<WorkflowVO> accessibleWorkflows = new ArrayList<WorkflowVO>(); Session session = null; net.sf.hibernate.Transaction tx = null; try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); WorkflowFacade wf = new WorkflowFacade(userPrincipal, hibernateSessionFactory, session); final List<WorkflowVO> allWorkflows = wf.getDeclaredWorkflows(); for(final Iterator<WorkflowVO> i = allWorkflows.iterator(); i.hasNext(); ) { final WorkflowVO workflowVO = i.next(); if(getIsAccessApproved(workflowVO.getName(), userPrincipal)) { accessibleWorkflows.add(workflowVO); } } session.flush(); tx.commit(); } catch (Exception e) { logger.error("An error occurred when we tries to execute getAvailableWorkflowVOList():" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction():" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close session:" + e.getMessage(), e); } } return accessibleWorkflows; } /** * This method returns true if the user should have access to the contentTypeDefinition sent in. */ public boolean getIsAccessApproved(String workflowName, InfoGluePrincipal infoGluePrincipal) throws SystemException { final String protectWorkflows = CmsPropertyHandler.getProtectWorkflows(); if(protectWorkflows == null || !protectWorkflows.equalsIgnoreCase("true")) { return true; } logger.info("getIsAccessApproved for " + workflowName + " AND " + infoGluePrincipal); boolean hasAccess = false; Database db = CastorDatabaseService.getDatabase(); beginTransaction(db); try { hasAccess = AccessRightController.getController().getIsPrincipalAuthorized(db, infoGluePrincipal, "Workflow.Create", workflowName); commitTransaction(db); } catch(Exception e) { logger.error("An error occurred so we should not complete the transaction:" + e, e); rollbackTransaction(db); throw new SystemException(e.getMessage()); } return hasAccess; } /** * Returns current workflows, i.e., workflows that are active. * @param userPrincipal a user principal * @return a list of WorkflowVOs representing all active workflows * @throws SystemException if an error occurs while finding the current workflows */ public List<WorkflowVO> getCurrentWorkflowVOList(InfoGluePrincipal userPrincipal, Integer maxNumberOfWorkflows) throws SystemException { List<WorkflowVO> list = new ArrayList<WorkflowVO>(); Session session = null; net.sf.hibernate.Transaction tx = null; try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); WorkflowFacade wf = new WorkflowFacade(userPrincipal, hibernateSessionFactory, session); list = wf.getActiveWorkflows(maxNumberOfWorkflows); session.flush(); tx.commit(); } catch (Exception e) { logger.error("An error occurred when we tries to execute getCurrentWorkflowVOList():" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction:" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close session:" + e.getMessage(), e); } } return list; } /** * Returns the workflows owned by the specified principal. * * @param userPrincipal a user principal. * @return a list of WorkflowVOs owned by the principal. * @throws SystemException if an error occurs while finding the workflows */ public List getMyCurrentWorkflowVOList(InfoGluePrincipal userPrincipal, Integer maxNumberOfWorkflows) throws SystemException { List list = new ArrayList(); Session session = null; net.sf.hibernate.Transaction tx = null; try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); WorkflowFacade wf = new WorkflowFacade(userPrincipal, hibernateSessionFactory, session); list = wf.getMyActiveWorkflows(userPrincipal, maxNumberOfWorkflows); session.flush(); tx.commit(); } catch (Exception e) { logger.error("An error occurred when we tries to execute getMyCurrentWorkflowVOList():" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction:" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close session:" + e.getMessage(), e); } } return list; //return new WorkflowFacade(userPrincipal, true).getMyActiveWorkflows(userPrincipal); } /** * Invokes an action on a workflow for a given user and request * @param principal the user principal * @param workflowId the ID of the desired workflow * @param actionId the ID of the desired action * @param inputs the inputs to the workflow * @return a WorkflowVO representing the current state of the workflow identified by workflowId * @throws WorkflowException if a workflow error occurs */ public WorkflowVO invokeAction(InfoGluePrincipal principal, long workflowId, int actionId, Map inputs) throws WorkflowException { WorkflowVO workflowVO = null; Session session = null; net.sf.hibernate.Transaction tx = null; try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); WorkflowFacade wf = new WorkflowFacade(principal, workflowId, hibernateSessionFactory, session); wf.doAction(actionId, inputs); session.flush(); tx.commit(); } catch (Exception e) { logger.error("An error occurred when we tries to execute invokeAction():" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction:" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close session:" + e.getMessage(), e); } } try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); WorkflowFacade wf = new WorkflowFacade(principal, workflowId, hibernateSessionFactory, session); workflowVO = wf.createWorkflowVO(); session.flush(); tx.commit(); } catch (Exception e) { logger.error("An error occurred when we tries to execute invokeAction():" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction:" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close session:" + e.getMessage(), e); } } return workflowVO; } /** * Returns the workflow property set for a particular user and workflow * @return the workflow property set for the workflow with workflowId and the user represented by userPrincipal */ /* public PropertySet getPropertySet(InfoGluePrincipal userPrincipal, long workflowId) { PropertySet propertySet = null; try { WorkflowFacade wf = new WorkflowFacade(userPrincipal, workflowId, false); propertySet = wf.getPropertySet(); } catch (Exception e) { e.printStackTrace(); } finally { } return propertySet; //return new WorkflowFacade(userPrincipal, workflowId, false).getPropertySet(); } */ public PropertySet getPropertySet(InfoGluePrincipal userPrincipal, long workflowId) { PropertySet propertySet = null; Session session = null; net.sf.hibernate.Transaction tx = null; try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); WorkflowFacade wf = new WorkflowFacade(userPrincipal, workflowId, hibernateSessionFactory, session); propertySet = wf.getPropertySet(); session.flush(); tx.commit(); } catch (Exception e) { logger.error("An error occurred when we tries to run getHistorySteps():" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction:" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close session:" + e.getMessage(), e); } } return propertySet; //return new WorkflowFacade(userPrincipal, workflowId, false).getPropertySet(); } /** * Returns the workflow property set for a particular user and workflow * @return the workflow property set for the workflow with workflowId and the user represented by userPrincipal */ public PropertySet getPropertySet(InfoGluePrincipal userPrincipal, long workflowId, Session session) { PropertySet propertySet = null; try { WorkflowFacade wf = new WorkflowFacade(userPrincipal, workflowId, hibernateSessionFactory, session); propertySet = wf.getPropertySet(); } catch (Exception e) { e.printStackTrace(); } finally { } return propertySet; //return new WorkflowFacade(userPrincipal, workflowId, false).getPropertySet(); } /** * Returns the contents of the PropertySet for a particular workflow * @param userPrincipal a user principal * @param workflowId the ID of the desired workflow * @return a map containing the contents of the workflow property set */ /* public Map getProperties(InfoGluePrincipal userPrincipal, long workflowId) { if(logger.isDebugEnabled()) { logger.info("userPrincipal:" + userPrincipal); logger.info("workflowId:" + workflowId); } PropertySet propertySet = getPropertySet(userPrincipal, workflowId); Map parameters = new HashMap(); for (Iterator keys = getPropertySet(userPrincipal, workflowId).getKeys().iterator(); keys.hasNext();) { String key = (String)keys.next(); parameters.put(key, propertySet.getString(key)); } return parameters; } */ public Map getProperties(InfoGluePrincipal userPrincipal, long workflowId) { if(logger.isDebugEnabled()) { logger.info("userPrincipal:" + userPrincipal); logger.info("workflowId:" + workflowId); } Map parameters = new HashMap(); Session session = null; net.sf.hibernate.Transaction tx = null; try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); PropertySet propertySet = getPropertySet(userPrincipal, workflowId, session); for (Iterator keys = propertySet.getKeys().iterator(); keys.hasNext();) { String key = (String)keys.next(); parameters.put(key, propertySet.getString(key)); } session.flush(); tx.commit(); } catch (Exception e) { logger.error("An error occurred when we tries to run getHistorySteps():" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction:" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close session:" + e.getMessage(), e); } } return parameters; } /** * Returns all history steps for a workflow, i.e., all the steps that have already been performed. * @param userPrincipal a user principal * @param workflowId the ID of the desired workflow * @return a list of WorkflowStepVOs representing all history steps for the workflow with workflowId */ public List getHistorySteps(InfoGluePrincipal userPrincipal, long workflowId) { List historySteps = new ArrayList(); Session session = null; net.sf.hibernate.Transaction tx = null; try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); WorkflowFacade wf = new WorkflowFacade(userPrincipal, workflowId, hibernateSessionFactory, session); historySteps = wf.getHistorySteps(); session.flush(); tx.commit(); } catch (Exception e) { logger.error("An error occurred when we tries to run getHistorySteps():" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction:" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close session:" + e.getMessage(), e); } } return historySteps; //return new WorkflowFacade(userPrincipal, workflowId, true).getHistorySteps(); } /** * Returns all current steps for a workflow, i.e., steps that could be performed in the workflow's current state * @param userPrincipal a user principal * @param workflowId the Id of the desired workflow * @return a list of WorkflowStepVOs representing the current steps of the workflow with workflowId */ public List getCurrentSteps(InfoGluePrincipal userPrincipal, long workflowId) { List currentSteps = new ArrayList(); Session session = null; net.sf.hibernate.Transaction tx = null; try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); WorkflowFacade wf = new WorkflowFacade(userPrincipal, workflowId, hibernateSessionFactory, session); currentSteps = wf.getCurrentSteps(); session.flush(); tx.commit(); } catch (Exception e) { logger.error("An error occurred when we tries to run getCurrentSteps():" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction:" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close session:" + e.getMessage(), e); } } //WorkflowFacade wf = new WorkflowFacade(userPrincipal, workflowId, true); //List currentSteps = wf.getCurrentSteps(); return currentSteps; } /** * Returns all steps for a workflow definition. These are the steps declared in the workfow descriptor; there is * no knowledge of current or history steps at this point. * @param userPrincipal an InfoGluePrincipal representing a system user * @param workflowId a workflowId * @return a list of WorkflowStepVOs representing all steps in the workflow. */ public List getAllSteps(InfoGluePrincipal userPrincipal, long workflowId) { List declaredSteps = new ArrayList(); Session session = null; net.sf.hibernate.Transaction tx = null; try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); WorkflowFacade wf = new WorkflowFacade(userPrincipal, workflowId, hibernateSessionFactory, session); declaredSteps = wf.getDeclaredSteps(); session.flush(); tx.commit(); } catch (Exception e) { logger.error("An error occurred when we tries to run getAllSteps():" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction:" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close session:" + e.getMessage(), e); } } return declaredSteps; //return new WorkflowFacade(userPrincipal, workflowId, true).getDeclaredSteps(); } /** * Returns true if the workflow has terminated; false otherwise. * * @param workflowVO the workflow. * @return true if the workflow has terminated; false otherwise. */ public boolean hasTerminated(InfoGluePrincipal userPrincipal, long workflowId) throws WorkflowException { boolean isFinished = false; Session session = null; net.sf.hibernate.Transaction tx = null; try { session = hibernateSessionFactory.openSession(); tx = session.beginTransaction(); WorkflowFacade wf = new WorkflowFacade(userPrincipal, workflowId, hibernateSessionFactory, session); isFinished = wf.isFinished(); session.flush(); tx.commit(); } catch (Exception e) { logger.error("An error occurred when we tries to run hasTerminated:" + e.getMessage(), e); try { tx.rollback(); } catch (HibernateException he) { logger.error("An error occurred when we tries to rollback transaction:" + he.getMessage(), he); } restoreSessionFactory(e); } finally { try { session.close(); } catch (HibernateException e) { logger.error("An error occurred when we tries to close session:" + e.getMessage(), e); } } return isFinished; //return new WorkflowFacade(userPrincipal, workflowId, true).isFinished(); } public static void restoreSessionFactory(Throwable we) { try { logger.error("Restoring session factory..."); String serverName = "Unknown"; try { InetAddress localhost = InetAddress.getLocalHost(); serverName = localhost.getHostName(); } catch(Exception e) {} String errorMessage = ""; String stacktrace = ""; StringWriter sw = new StringWriter(); if(we != null) { errorMessage = we.getMessage(); we.printStackTrace(new PrintWriter(sw)); stacktrace = sw.toString().replaceAll("(\r\n|\r|\n|\n\r)", "<br/>"); } String subject = "CMS - Restoring session factory on " + serverName; String message = "OS Workflow had problems accessing the database or some other problem occurred. Check why the database went away or the error occurred."; message = message + "\n\n" + errorMessage + "\n\n" + stacktrace; String warningEmailReceiver = CmsPropertyHandler.getWarningEmailReceiver(); if(warningEmailReceiver != null && !warningEmailReceiver.equals("") && warningEmailReceiver.indexOf("@warningEmailReceiver@") == -1) { try { MailServiceFactory.getService().sendEmail("text/html", warningEmailReceiver, warningEmailReceiver, null, null, null, null, subject, message, "utf-8"); } catch (Exception e) { logger.error("Could not send mail:" + e.getMessage(), e); } } try { logger.info("Closing:" + hibernateSessionFactory); hibernateSessionFactory.close(); CacheController.clearCache("propertySetCache"); } catch (Exception e) { logger.error("An error occurred when we tried to close the hibernate session factory:" + e.getMessage()); } hibernateSessionFactory = new Configuration().configure().buildSessionFactory(); logger.info("Opened:" + hibernateSessionFactory); } catch (Exception e) { logger.error("An error occurred when we tried to restore the hibernate session factory:" + e.getMessage(), e); } } /** * Returns a new WorkflowActionVO. This method is apparently unused, but is required by BaseController. We don't * use it internally because it requires a cast; it is simpler to just use <code>new</code> to create an instance. * @return a new WorkflowActionVO. */ public BaseEntityVO getNewVO() { return new WorkflowActionVO(); } }