- add moved class to core-workflow

git-svn-id: https://svn.apache.org/repos/asf/lenya/trunk@1034330 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/DocumentWorkflowable.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/DocumentWorkflowable.java
new file mode 100644
index 0000000..baafb02
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/DocumentWorkflowable.java
@@ -0,0 +1,275 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.workflow;
+
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.lenya.ac.Identity;
+import org.apache.lenya.ac.User;
+import org.apache.lenya.cms.metadata.MetaData;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.ResourceType;
+import org.apache.lenya.workflow.Version;
+import org.apache.lenya.workflow.Workflow;
+import org.apache.lenya.workflow.Workflowable;
+
+/**
+ * Workflowable around a CMS document.
+ * 
+ * @version $Id: DocumentWorkflowable.java 416648 2006-06-23 09:15:28Z andreas $
+ */
+class DocumentWorkflowable extends AbstractLogEnabled implements Workflowable {
+    
+    private static final Log logger = LogFactory.getLog(DocumentWorkflowable.class);
+
+    /**
+     * Ctor.
+     * @param manager The service manager.
+     * @param document The document.
+     * @param logger The logger.
+     */
+    public DocumentWorkflowable(Document document) {
+        if (document.getSession().getIdentity() == null) {
+            throw new IllegalArgumentException("The session must have an identity.");
+        }
+        this.document = document;
+    }
+
+    private Document document;
+
+    protected Document getDocument() {
+        return this.document;
+    }
+
+    /**
+     * @return The name of the workflow schema.
+     */
+    protected String getWorkflowSchema() {
+        String workflowName = null;
+        try {
+            ResourceType doctype = document.getResourceType();
+            if (doctype != null) {
+                workflowName = document.getPublication().getWorkflowSchema(doctype);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return workflowName;
+    }
+
+    private Version[] versions = null;
+
+    private int revision = 0;
+
+    protected static final String METADATA_NAMESPACE = "http://apache.org/lenya/metadata/workflow/1.0";
+    protected static final String METADATA_VERSION = "workflowVersion";
+    
+    protected int getLatestRevision(int[] revisions) {
+        return revisions[revisions.length - 1];
+    }
+
+    /**
+     * @see org.apache.lenya.workflow.Workflowable#getVersions()
+     */
+    public Version[] getVersions() {
+        try {
+            MetaData meta = this.document.getMetaData(METADATA_NAMESPACE);
+
+            int[] revisions = getDocument().getHistory().getRevisionNumbers();
+            boolean checkedIn = revisions.length > 0;
+            if (this.versions == null
+                    || (checkedIn && getLatestRevision(revisions) > this.revision)) {
+                String[] versionStrings = meta.getValues(METADATA_VERSION);
+                this.versions = new Version[versionStrings.length];
+
+                SortedMap number2version = new TreeMap();
+
+                for (int i = 0; i < versionStrings.length; i++) {
+                    String string = versionStrings[i];
+                    int spaceIndex = string.indexOf(" ");
+                    String numberString = string.substring(0, spaceIndex);
+                    int number = Integer.parseInt(numberString);
+                    String versionString = string.substring(spaceIndex + 1);
+                    Version version = decodeVersion(versionString);
+                    number2version.put(new Integer(number), version);
+                }
+
+                int number = 0;
+                for (Iterator i = number2version.keySet().iterator(); i.hasNext();) {
+                    Version version = (Version) number2version.get(i.next());
+                    this.versions[number] = version;
+                    number++;
+                }
+
+                if (checkedIn) {
+                    this.revision = getLatestRevision(revisions);
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return this.versions;
+    }
+
+    /**
+     * @see org.apache.lenya.workflow.Workflowable#getLatestVersion()
+     */
+    public Version getLatestVersion() {
+        Version version = null;
+        Version[] versions = getVersions();
+        if (versions.length > 0) {
+            version = versions[versions.length - 1];
+        }
+        return version;
+    }
+
+    /**
+     * @see org.apache.lenya.workflow.Workflowable#newVersion(org.apache.lenya.workflow.Workflow,
+     *      org.apache.lenya.workflow.Version)
+     */
+    public void newVersion(Workflow workflow, Version version) {
+        Version[] newVersions = new Version[getVersions().length + 1];
+        for (int i = 0; i < getVersions().length; i++) {
+            newVersions[i] = getVersions()[i];
+        }
+
+        int number = newVersions.length - 1;
+        newVersions[number] = version;
+
+        String string = number + " " + encodeVersion(workflow, version);
+        addToMetaData(string);
+
+        WorkflowEventDescriptor descriptor = new WorkflowEventDescriptor(version);
+        getDocument().getSession().enqueueEvent(getDocument(), descriptor);
+    }
+
+    protected void addToMetaData(String versionString) {
+        try {
+            String[] areas = getDocument().getPublication().getAreaNames();
+            for (int i = 0; i < areas.length; i++) {
+                if (getDocument().existsAreaVersion(areas[i])) {
+                    Document doc = getDocument().getAreaVersion(areas[i]);
+                    MetaData meta = doc.getMetaData(METADATA_NAMESPACE);
+                    meta.addValue(METADATA_VERSION, versionString);
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected String encodeVersion(Workflow workflow, Version version) {
+
+        StringBuffer stringBuf = new StringBuffer("event:").append(version.getEvent());
+        stringBuf.append(" state:").append(version.getState());
+
+        Identity identity = getDocument().getSession().getIdentity();
+        User user = identity.getUser();
+        if (user != null) {
+            stringBuf.append(" user:").append(identity.getUser().getId());
+        }
+        stringBuf.append(" machine:").append(identity.getMachine().getIp());
+
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss");
+        stringBuf.append(" date:").append(format.format(new Date()));
+
+        String names[] = workflow.getVariableNames();
+        for (int i = 0; i < names.length; i++) {
+            String value = Boolean.toString(version.getValue(names[i]));
+            stringBuf.append(" var:").append(names[i]);
+            stringBuf.append("=").append(value);
+        }
+        return stringBuf.toString();
+    }
+
+    protected Version decodeVersion(String string) {
+
+        String event = null;
+        String state = null;
+        String user = null;
+        String machine = null;
+        Date date = null;
+        Map variables = new HashMap();
+
+        String[] parts = string.split(" ");
+        for (int i = 0; i < parts.length; i++) {
+            String[] steps = parts[i].split(":", 2);
+            if (steps[0].equals("event")) {
+                event = steps[1];
+            } else if (steps[0].equals("state")) {
+                state = steps[1];
+            } else if (steps[0].equals("user")) {
+                user = steps[1];
+            } else if (steps[0].equals("date")) {
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss", Locale.US);
+                date = sdf.parse(steps[1], new ParsePosition(0));
+            } else if (steps[0].equals("machine")) {
+                machine = steps[1];
+            } else if (steps[0].equals("var")) {
+                String[] nameValue = steps[1].split("=");
+                variables.put(nameValue[0], nameValue[1]);
+            }
+        }
+
+        Version version = new LenyaVersion(event, state);
+        for (Iterator i = variables.keySet().iterator(); i.hasNext();) {
+            String name = (String) i.next();
+            String value = (String) variables.get(name);
+            version.setUserId(user);
+            version.setDate(date);
+            version.setIPAddress(machine);
+            version.setValue(name, Boolean.valueOf(value).booleanValue());
+        }
+        return version;
+    }
+
+    /**
+     * @see org.apache.lenya.workflow.Workflowable#getWorkflowSchemaURI()
+     */
+    public String getWorkflowSchemaURI() {
+        String uri = null;
+        String schema = getWorkflowSchema();
+        if (schema != null) {
+
+            if (schema.indexOf("://") != -1) {
+                return schema;
+            } else {
+                uri = this.document.getPublication().getSourceUri() + "/config/workflow/" + schema;
+                uri = uri.substring("lenya://".length());
+                uri = "context://" + uri;
+            }
+        }
+        return uri;
+    }
+
+    public String toString() {
+        return this.document.toString();
+    }
+
+}
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/LenyaVersion.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/LenyaVersion.java
new file mode 100644
index 0000000..b59b9bb
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/LenyaVersion.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id: VersionImpl.java 372568 2006-01-26 16:51:38Z andreas $  */
+
+package org.apache.lenya.cms.workflow;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lenya.workflow.Version;
+
+/**
+ * A version of the workflow history.
+ */
+public class LenyaVersion implements Version {
+
+    private Date date;
+    private String event;
+    private String ipAddress;
+    private String state;
+    private String userId;
+    private Map variableValues = new HashMap();
+    
+    /**
+     * @see org.apache.lenya.workflow.Version#getEvent()
+     */
+    public String getEvent() {
+        return this.event;
+    }
+
+    /**
+     * @see org.apache.lenya.workflow.Version#getState()
+     */
+    public String getState() {
+        return this.state;
+    }
+    
+    /**
+     * Returns the date.
+     * @return A string.
+     */
+    public Date getDate() {
+        return (Date)this.date.clone();
+    }
+
+    /**
+     * Sets the date.
+     * @param _date A date.
+     */
+    public void setDate(Date _date) {
+        this.date = (Date)_date.clone();
+    }
+
+    /**
+     * Returns the user ID.
+     * @return A string.
+     */
+    public String getUserId() {
+        return this.userId;
+    }
+
+    /**
+     * Sets the user ID.
+     * @param _userId A user ID.
+     */
+    public void setUserId(String _userId) {
+        this.userId = _userId;
+    }
+
+    /**
+     * Returns the ip address.
+     * @return A string.
+     */
+    public String getIPAddress() {
+    	return this.ipAddress;
+    }
+
+    /**
+     * Sets the ip address.
+     * @param _ipaddress A ip address.
+     */
+    public void setIPAddress(String _ipaddress){
+    	this.ipAddress = _ipaddress;
+    }
+    
+    /**
+     * Ctor.
+     * @param _event The event that caused the version change.
+     * @param _state The destination state.
+     */
+    public LenyaVersion(String _event, String _state) {
+        this.event = _event;
+        this.state = _state;
+    }
+
+    /**
+     * @see org.apache.lenya.workflow.Version#getValue(java.lang.String)
+     */
+    public boolean getValue(String variableName) {
+        Boolean value = (Boolean) this.variableValues.get(variableName);
+        if (value == null) {
+            throw new RuntimeException("No value set for variable [" + variableName + "]");
+        }
+        return value.booleanValue();
+    }
+
+    /**
+     * @see org.apache.lenya.workflow.Version#setValue(java.lang.String, boolean)
+     */
+    public void setValue(String variableName, boolean value) {
+        this.variableValues.put(variableName, Boolean.valueOf(value));
+    }
+
+}
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/RoleCondition.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/RoleCondition.java
new file mode 100644
index 0000000..f0460e6
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/RoleCondition.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.cms.workflow;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.lenya.ac.AccessController;
+import org.apache.lenya.ac.AccessControllerResolver;
+import org.apache.lenya.ac.AccreditableManager;
+import org.apache.lenya.ac.Identity;
+import org.apache.lenya.ac.Policy;
+import org.apache.lenya.ac.PolicyManager;
+import org.apache.lenya.ac.Role;
+import org.apache.lenya.ac.RoleManager;
+import org.apache.lenya.workflow.Condition;
+import org.apache.lenya.workflow.Workflow;
+import org.apache.lenya.workflow.WorkflowException;
+import org.apache.lenya.workflow.Workflowable;
+
+/**
+ * Role condition
+ */
+public class RoleCondition implements Condition {
+
+    private Set roleIds = new HashSet();
+
+    protected static final String SEPARATOR = ",";
+
+    /**
+     * @see org.apache.lenya.workflow.Condition#setExpression(java.lang.String)
+     */
+    public void setExpression(String expression) throws WorkflowException {
+        this.expression = expression;
+
+        String[] roles = expression.split(SEPARATOR);
+        for (int i = 0; i < roles.length; i++) {
+            this.roleIds.add(roles[i].trim());
+        }
+    }
+
+    protected AccessControllerResolver getAccessControllerResolver() {
+        return (AccessControllerResolver) WebAppContextUtils.getCurrentWebApplicationContext()
+                .getBean(AccessControllerResolver.ROLE);
+    }
+
+    /**
+     * Returns if the condition is complied in a certain situation. The condition is complied when
+     * the current user has the role that is required by the RoleCondition.
+     */
+    public boolean isComplied(Workflow workflow, Workflowable instance) {
+
+        DocumentWorkflowable workflowable = (DocumentWorkflowable) instance;
+        String url = workflowable.getDocument().getCanonicalWebappURL();
+
+        AccessControllerResolver acResolver = getAccessControllerResolver();
+        AccessController accessController = null;
+        try {
+            accessController = acResolver.resolveAccessController(url);
+
+            PolicyManager policyManager = accessController.getPolicyManager();
+            Identity identity = workflowable.getDocument().getSession().getIdentity();
+            if (identity == null) {
+                throw new IllegalArgumentException("The session of the workflowable "
+                        + workflowable + " has no identity.");
+            }
+            AccreditableManager accreditableMgr = accessController.getAccreditableManager();
+            Policy policy = policyManager.getPolicy(accreditableMgr, url);
+            RoleManager roleManager = accreditableMgr.getRoleManager();
+
+            boolean complied = false;
+
+            for (Iterator i = this.roleIds.iterator(); i.hasNext();) {
+                String roleId = (String) i.next();
+                Role role = roleManager.getRole(roleId);
+                if (policy.check(identity, role) == Policy.RESULT_GRANTED) {
+                    complied = true;
+                }
+            }
+
+            return complied;
+
+        } catch (final Exception e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    private String expression;
+
+    /**
+     * Returns the expression of this condition.
+     * 
+     * @return A string.
+     */
+    public String getExpression() {
+        return this.expression;
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return getExpression();
+    }
+
+}
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/WorkflowEventDescriptor.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/WorkflowEventDescriptor.java
new file mode 100644
index 0000000..6f978e6
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/WorkflowEventDescriptor.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.workflow;
+
+import org.apache.commons.lang.Validate;
+import org.apache.lenya.workflow.Version;
+
+/**
+ * Descriptor for workflow events.
+ */
+public class WorkflowEventDescriptor {
+
+    private Version version;
+
+    /**
+     * @param version The version.
+     */
+    public WorkflowEventDescriptor(Version version) {
+        Validate.notNull(version);
+        this.version = version;
+    }
+
+    /**
+     * @return The version.
+     */
+    public Version getVersion() {
+        return this.version;
+    }
+
+    public String toString() {
+        return "workflow:" + this.version.getEvent() + "->" + this.version.getState();
+    }
+
+}
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/WorkflowUtil.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/WorkflowUtil.java
new file mode 100644
index 0000000..84767aa
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/cms/workflow/WorkflowUtil.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.workflow;
+
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.cms.publication.util.DocumentSet;
+import org.apache.lenya.workflow.Workflow;
+import org.apache.lenya.workflow.WorkflowException;
+import org.apache.lenya.workflow.WorkflowManager;
+import org.apache.lenya.workflow.Workflowable;
+
+/**
+ * Utility class for workflow tasks.
+ * 
+ * @version $Id:$
+ */
+public class WorkflowUtil {
+
+    /**
+     * Invokes a workflow event on a document. This is the same as
+     * <code>invoke(Document, String, true)</code>.
+     * @param document The document.
+     * @param event The name of the event.
+     * @throws WorkflowException if the event could not be invoked in the current situation.
+     */
+    public static void invoke(Document document, String event) throws WorkflowException {
+        WorkflowManager wfManager = getWorkflowManager();
+        Workflowable workflowable = getWorkflowable(document);
+        wfManager.invoke(workflowable, event);
+    }
+
+    /**
+     * Invokes a workflow event on a document.
+     * @param document The document.
+     * @param event The name of the event.
+     * @param force If this is set to <code>true</code>, the execution is forced, which means an
+     *            exception is thrown if the workflowable in the set does not support the event. If
+     *            set to <code>false</code>, non-supporting documents are ignored.
+     * @throws WorkflowException if the event could not be invoked in the current situation.
+     */
+    public static void invoke(Document document, String event, boolean force)
+            throws WorkflowException {
+        WorkflowManager wfManager = getWorkflowManager();
+        Workflowable workflowable = getWorkflowable(document);
+        wfManager.invoke(workflowable, event, force);
+    }
+
+    /**
+     * Invokes a workflow event on a document set.
+     * @param documentSet The document.
+     * @param event The event.
+     * @param force If this is set to <code>true</code>, the execution is forced, which means an
+     *            exception is thrown if a document in the set does not support the event. If set to
+     *            <code>false</code>, non-supporting documents are ignored.
+     * @throws WorkflowException if <code>force</code> is set to <code>true</code> and a document
+     *             does not support the workflow event.
+     */
+    public static void invoke(DocumentSet documentSet, String event, boolean force)
+            throws WorkflowException {
+        WorkflowManager wfManager = getWorkflowManager();
+        Document[] documents = documentSet.getDocuments();
+        for (int i = 0; i < documents.length; i++) {
+            Workflowable workflowable = new DocumentWorkflowable(documents[i]);
+            wfManager.invoke(workflowable, event, force);
+        }
+    }
+
+    protected static WorkflowManager getWorkflowManager() {
+        return (WorkflowManager) WebAppContextUtils.getCurrentWebApplicationContext().getBean(
+                WorkflowManager.ROLE);
+    }
+
+    /**
+     * Checks if an event can be invoked on a document.
+     * @param document The document.
+     * @param event The event.
+     * @return A boolean value.
+     * @throws WorkflowException
+     */
+    public static boolean canInvoke(Document document, String event)
+            throws WorkflowException {
+        Workflowable workflowable = new DocumentWorkflowable(document);
+        return getWorkflowManager().canInvoke(workflowable, event);
+    }
+
+    /**
+     * Checks if an event can be invoked on all documents in a set.
+     * @param session The repository session.
+     * @param documents The documents.
+     * @param event The event.
+     * @return if an error occurs.
+     * @throws WorkflowException
+     */
+    public static boolean canInvoke(Session session, DocumentSet documents, String event)
+            throws WorkflowException {
+        WorkflowManager wfManager = getWorkflowManager();
+
+        boolean canInvoke = true;
+        Document[] documentArray = documents.getDocuments();
+        for (int i = 0; i < documentArray.length; i++) {
+            Workflowable workflowable = new DocumentWorkflowable(documentArray[i]);
+            canInvoke = canInvoke && wfManager.canInvoke(workflowable, event);
+        }
+        return canInvoke;
+    }
+
+    /**
+     * Returns if a document has a workflow.
+     * @param logger The logger.
+     * @param document The document.
+     * @return A boolean value.
+     * @throws WorkflowException if an error occurs.
+     */
+    public static boolean hasWorkflow(Document document) throws WorkflowException {
+        Workflowable workflowable = new DocumentWorkflowable(document);
+        return getWorkflowManager().hasWorkflow(workflowable);
+    }
+
+    /**
+     * Returns the workflow schema of a document.
+     * @param logger The logger.
+     * @param document The document.
+     * @return A workflow schema.
+     * @throws WorkflowException if an error occurs.
+     */
+    public static Workflow getWorkflowSchema(Document document)
+            throws WorkflowException {
+        WorkflowManager wfManager = getWorkflowManager();
+        Workflowable workflowable = getWorkflowable(document);
+        if (wfManager.hasWorkflow(workflowable)) {
+            return wfManager.getWorkflowSchema(workflowable);
+        } else {
+            throw new WorkflowException("The document [" + document + "] has no workflow!");
+        }
+    }
+
+    /**
+     * Returns a workflowable for a document.
+     * @param logger The logger.
+     * @param document The document.
+     * @return A workflowable.
+     */
+    public static Workflowable getWorkflowable(Document document) {
+        return new DocumentWorkflowable(document);
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Action.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Action.java
new file mode 100644
index 0000000..febbacb
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Action.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.workflow;
+
+
+/**
+ * Workflow action.
+ */
+public interface Action {
+	
+    /**
+     * Executes this action for a given workflow instance.
+     * @param resultingVersion The resulting version.
+     * @throws WorkflowException if the execution failed
+     */
+    void execute(Version resultingVersion) throws WorkflowException;
+}
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/BooleanVariable.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/BooleanVariable.java
new file mode 100644
index 0000000..d1d4651
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/BooleanVariable.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.workflow;
+
+
+/**
+ * <p>Boolean state variable.</p>
+ * <p>
+ * A workflow schema can contain a set of state variables.
+ * For each instance, the state variables hold certain values.
+ * Values can be assigned during transitions, so a variable can
+ * change its value when a transition fires. Currently,
+ * the workflow supports only boolean state variables.
+ * </p>
+ */
+public interface BooleanVariable {
+	
+    /**
+     * Returns the name of this variable.
+     * @return the name
+     */
+    String getName();
+
+    /**
+     * Returns the initial value of this variable.
+     * @return A boolean value.
+     */
+    boolean getInitialValue();
+}
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/BooleanVariableAssignment.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/BooleanVariableAssignment.java
new file mode 100644
index 0000000..8b2021f
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/BooleanVariableAssignment.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.workflow;
+
+
+/**
+ * Boolean variable assignment.
+ */
+public interface BooleanVariableAssignment extends Action {
+    // do nothing
+}
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Condition.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Condition.java
new file mode 100644
index 0000000..ee758a3
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Condition.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.workflow;
+
+/**
+ * <p>A condition can prevent a transition from firing,
+ * based on the current situation. Examples:</p>
+ * <ul>
+ * <li>Does the current user have a certain role on the current URL?</li>
+ * <li>Does a certain state variable have a certain value (e.g., is the document published)? (BooleanVariableCondition)<li>
+ * <li>Is the sun shining? (e.g., if the weather report may only be published on sunny days)</li>
+ * </ul>
+ */
+public interface Condition {
+
+    /**
+     * Returns if the condition is complied in a certain situation.
+     * @param workflow The workflow to use.
+     * @param workflowable The workflowable to check the condition on.
+     * @return if the condition is complied.
+     * @throws WorkflowException when the expression could not be evaluated.
+     */
+    boolean isComplied(Workflow workflow, Workflowable workflowable) throws WorkflowException;
+
+    /** Sets the expression for this condition.
+     * @param expression The expression.
+     * @throws WorkflowException when the expression is not valid.
+     */
+    void setExpression(String expression) throws WorkflowException;
+}
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Transition.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Transition.java
new file mode 100644
index 0000000..c026ee1
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Transition.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.workflow;
+
+
+/**
+ * <p>
+ * A transition describes the switching of a workflow instance
+ * from one state to another. A transition has
+ * </p>
+ * 
+ * <ul>
+ * <li>a source state,</li>
+ * <li>a destination state,</li>
+ * <li>an event,</li>
+ * <li>a set of conditions,</li>
+ * <li>a set of assignments.</li>
+ * </ul>
+ * 
+ * <p>
+ * Additionally, a transition can be marked as synchronized.
+ * </p>
+ */
+public interface Transition {
+	
+    /**
+     * Returns the event of this transition.
+     * @return the event
+     */
+    String getEvent();
+    
+    /**
+     * @return The source state.
+     */
+    String getSource();
+    
+    /**
+     * @return The destination state.
+     */
+    String getDestination();
+
+    /**
+     * Returns the actions of this transition.
+     * @return the actions
+     */
+    Action[] getActions();
+    
+    /**
+     * @return The conditions.
+     */
+    Condition[] getConditions();
+
+    /**
+     * Returns if this transition is synchronized.
+     * @return A boolean value.
+     */
+    boolean isSynchronized();
+}
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Version.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Version.java
new file mode 100644
index 0000000..86bfcf8
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Version.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.workflow;
+
+import java.util.Date;
+
+/**
+ * A version of the workflow history.
+ * 
+ * @version $Id$
+ */
+public interface Version {
+    
+    /**
+     * Returns the event.
+     * @return An event.
+     */
+    String getEvent();
+
+    /**
+     * Returns the state.
+     * @return A state.
+     */
+    String getState();
+    
+
+    /**
+     * Returns the date.
+     * @return A string.
+     */
+    Date getDate();
+
+    /**
+     * Sets the date.
+     * @param _date A date.
+     */
+    void setDate(Date _date);
+
+    /**
+     * Returns the user ID.
+     * @return A string.
+     */
+    public String getUserId();
+
+    /**
+     * Sets the user ID.
+     * @param _userId A user ID.
+     */
+    public void setUserId(String _userId);
+
+    /**
+     * Returns the ip address.
+     * @return A string.
+     */
+    public String getIPAddress();
+
+    /**
+     * Sets the ip address.
+     * @param _ipaddress A ip address.
+     */
+    public void setIPAddress(String _ipaddress);
+    
+    /**
+     * Returns the value of a variable.
+     * @param variableName The variable name.
+     * @return A boolean value.
+     */
+    boolean getValue(String variableName);
+
+    /**
+     * Sets a variable value.
+     * @param variableName The variable name.
+     * @param value The value.
+     */
+    void setValue(String variableName, boolean value);
+    
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Workflow.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Workflow.java
new file mode 100644
index 0000000..2ea9c01
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Workflow.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.workflow;
+
+/**
+ * <p>A workflow schema.</p>
+ * <p>
+ * A workflow schema defines a state machine (deterministic finite
+ * automaton - DFA), consisting of
+ * </p>
+ * <ul>
+ * <li>states, including a marked initial state,</li>
+ * <li>transitions, and</li>
+ * <li>state variables.</li>
+ */
+public interface Workflow {
+    /**
+     * <code>NAMESPACE</code> Workflow namespace URI
+     */
+    String NAMESPACE = "http://apache.org/cocoon/lenya/workflow/1.0";
+    /**
+     * <code>DEFAULT_PREFIX</code> Default workflow namespace prefix
+     */
+    String DEFAULT_PREFIX = "wf";
+    
+    /**
+     * Returns the initial state of this workflow.
+     * @return The initial state
+     */
+    String getInitialState();
+
+    /**
+     * Returns the transitions that leave a state.
+     * This method is used, e.g., to disable menu items.
+     * @param state A state.
+     * @return The transitions that leave the state.
+     * @throws WorkflowException if the state is not contained.
+     */
+    Transition[] getLeavingTransitions(String state) throws WorkflowException;
+    
+    /**
+     * Returns the variable names.
+     * @return A string array.
+     */
+    String[] getVariableNames();
+    
+    /**
+     * @return The name of this workflow.
+     */
+    String getName();
+    
+    /**
+     * @param variableName The variable name.
+     * @return The initial value of the variable.
+     * @throws WorkflowException if the variable does not exist.
+     */
+    boolean getInitialValue(String variableName) throws WorkflowException;
+    
+    /**
+     * @return The events.
+     */
+    String[] getEvents();
+
+    /**
+     * @return The states.
+     */
+    String[] getStates();
+}
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/WorkflowEngine.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/WorkflowEngine.java
new file mode 100644
index 0000000..7d14807
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/WorkflowEngine.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.workflow;
+
+/**
+ * Workflow engine.
+ * 
+ * @version $Id$
+ */
+public interface WorkflowEngine {
+
+    /**
+     * Checks if an event can be invoked.
+     * @param workflowable The workflowable.
+     * @param workflow The workflow schema.
+     * @param event The event.
+     * @return A boolean value.
+     * @throws WorkflowException if an error occurs.
+     */
+    boolean canInvoke(Workflowable workflowable, Workflow workflow, String event)
+            throws WorkflowException;
+
+    /**
+     * Invokes an event.
+     * @param workflowable The workflowable.
+     * @param workflow The workflow.
+     * @param event The event.
+     * @throws WorkflowException if an error occurs.
+     */
+    void invoke(Workflowable workflowable, Workflow workflow, String event)
+            throws WorkflowException;
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/WorkflowException.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/WorkflowException.java
new file mode 100644
index 0000000..a3c8aea
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/WorkflowException.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.workflow;
+
+
+/**
+ * Workflow exception.
+ */
+public class WorkflowException extends Exception {
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+     * Constructor
+     */
+    public WorkflowException() {
+        super();
+    }
+
+    /**
+     * Create a WorkflowException.
+     * @param message The message.
+     */
+    public WorkflowException(String message) {
+        super(message);
+    }
+
+    /**
+     * Create a WorkflowException.
+     * @param message The message.
+     * @param cause The cause.
+     */
+    public WorkflowException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Create a WorkflowException.
+     * @param cause The cause.
+     */
+    public WorkflowException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/WorkflowManager.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/WorkflowManager.java
new file mode 100644
index 0000000..13c32b6
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/WorkflowManager.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.workflow;
+
+/**
+ * Manager for workflow issues. This is the main entry point for
+ * workflow-related tasks. You can safely invoke all methods for non-workflow
+ * documents.
+ * 
+ * @version $Id: WorkflowManager.java 179751 2005-06-03 09:13:35Z andreas $
+ */
+public interface WorkflowManager {
+
+    /**
+     * The Avalon role.
+     */
+    String ROLE = WorkflowManager.class.getName();
+
+    /**
+     * Invokes a workflow event on a document. This is the same as
+     * <code>invoke(Document, String, true)</code>.
+     * @param workflowable The workflowable.
+     * @param event The name of the event.
+     * @throws WorkflowException if the event could not be invoked in the
+     *             current situation.
+     */
+    void invoke(Workflowable workflowable, String event) throws WorkflowException;
+
+    /**
+     * Invokes a workflow event on a document.
+     * @param workflowable The document.
+     * @param event The name of the event.
+     * @param force If this is set to <code>true</code>, the execution is
+     *            forced, which means an exception is thrown if the workflowable in
+     *            the set does not support the event. If set to
+     *            <code>false</code>, non-supporting documents are ignored.
+     * @throws WorkflowException if the event could not be invoked in the
+     *             current situation.
+     */
+    void invoke(Workflowable workflowable, String event, boolean force) throws WorkflowException;
+
+    /**
+     * Checks if an event can be invoked on a document.
+     * @param workflowable The workflowable.
+     * @param event The event.
+     * @return A boolean value.
+     */
+    boolean canInvoke(Workflowable workflowable, String event);
+
+    /**
+     * Checks if a workflowable has a workflow.
+     * @param workflowable The workflowable.
+     * @return A boolean value.
+     */
+    boolean hasWorkflow(Workflowable workflowable);
+
+    /**
+     * Resolves the workflow schema of a workflowable.
+     * @param workflowable The workflowable.
+     * @return A workflow schema.
+     * @throws WorkflowException if the document has no workflow.
+     */
+    Workflow getWorkflowSchema(Workflowable workflowable) throws WorkflowException;
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Workflowable.java b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Workflowable.java
new file mode 100644
index 0000000..f735fad
--- /dev/null
+++ b/org.apache.lenya.core.workflow/src/main/java/org/apache/lenya/workflow/Workflowable.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.workflow;
+
+/**
+ * Interface for objects which can be workflowed.
+ *
+ * @version $Id$
+ */
+public interface Workflowable {
+
+    /**
+     * @return The versions in chronological order.
+     */
+    Version[] getVersions();
+    
+    /**
+     * @return The latest version.
+     */
+    Version getLatestVersion();
+    
+    /**
+     * Adds a new version.
+     * @param workflow The workflow.
+     * @param version The version.
+     */
+    void newVersion(Workflow workflow, Version version);
+    
+    /**
+     * @return The URI to resolve the schema configuration from.
+     */
+    String getWorkflowSchemaURI();
+    
+}