View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.api;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.config.ConfigurationException;
20  import org.kuali.rice.core.api.util.ClassLoaderUtils;
21  import org.kuali.rice.kew.api.action.InvalidActionTakenException;
22  import org.kuali.rice.kew.api.doctype.IllegalDocumentTypeException;
23  import org.kuali.rice.kew.api.document.DocumentContentUpdate;
24  import org.kuali.rice.kew.api.document.DocumentUpdate;
25  
26  import java.io.BufferedReader;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.InputStreamReader;
30  import java.lang.reflect.InvocationTargetException;
31  import java.lang.reflect.Method;
32  
33  /**
34   * Factory which manufactures WorkflowDocuments.  This is the main entry point for interaction with the
35   * Kuali Enterprise Workflow System.
36   *
37   * The WorkflowDocumentFactory uses the {@link org.kuali.rice.kew.impl.document.WorkflowDocumentProvider} SPI as a strategy
38   * for creating WorkflowDocument instances.
39   *
40   * The provider class is specified in the following file in the class loader: "META-INF/services/org.kuali.rice.kew.api.WorkflowDocument",
41   * and should implement the WorkflowDocumentProvider interface.
42   */
43  public final class WorkflowDocumentFactory {
44  
45      private static final String CREATE_METHOD_NAME = "createDocument";
46      private static final String LOAD_METHOD_NAME = "loadDocument";
47  
48      /**
49       * A lazy initialization holder class for the Provider.  Allows for
50       * thread-safe initialization of shared resource.
51       *
52       * NOTE: ProviderHolder and its fields are static, therefore there
53       * will only be a simple WorkflowDocumentProvider instance so it needs to be
54       * thread-safe.
55       */
56      private static final class ProviderHolder {
57          static final Object provider;
58          static final Method createMethod;
59          static final Method loadMethod;
60          static {
61              provider = loadProvider();
62              createMethod = locateCreateMethod(provider);
63              loadMethod = locateLoadMethod(provider);
64          }
65      }
66  
67      /**
68       * Creates a new workflow document of the given type with the given initiator.
69       * 
70       * @param principalId the document initiator
71       * @param documentTypeName the document type
72       * 
73       * @return a WorkflowDocument object through which to interact with the new workflow document
74       * 
75       * @throws IllegalArgumentException if principalId is null or blank
76       * @throws IllegalArgumentException if documentTypeName is null or blank
77       * @throws IllegalDocumentTypeException if the document type does not allow for creation of a document,
78       * this can occur when the given document type is used only as a parent and has no route path configured
79       * @throws InvalidActionTakenException if the caller is not allowed to execute this action
80       */
81      public static WorkflowDocument createDocument(String principalId, String documentTypeName) {
82          return createDocument(principalId, documentTypeName, null, null);
83      }
84  
85      /**
86       * Creates a new workflow document of the given type with the given initiator.
87       *
88       * @param principalId the document initiator
89       * @param documentTypeName the document type
90       * @param title the title of the new document
91       *
92       * @return a WorkflowDocument object through which to interact with the new workflow document
93       * 
94       * @throws IllegalArgumentException if principalId is null or blank
95       * @throws IllegalArgumentException if documentTypeName is null or blank
96       * @throws IllegalDocumentTypeException if documentTypeName does not represent a valid document type
97       */
98      public static WorkflowDocument createDocument(String principalId, String documentTypeName, String title) {
99          DocumentUpdate.Builder builder = DocumentUpdate.Builder.create();
100         builder.setTitle(title);
101         return createDocument(principalId, documentTypeName, builder.build(), null);
102     }
103 
104     /**
105      * Creates a new workflow document of the given type with the given initiator.
106      *
107      * @param principalId the document initiator
108      * @param documentTypeName the document type
109      * @param documentUpdate pre-constructed state with which to initialize the document
110      * @param documentContentUpdate pre-constructed document content with which to initialize the document
111      *
112      * @return a WorkflowDocument object through which to interact with the new workflow document
113      * 
114      * @throws IllegalArgumentException if principalId is null or blank
115      * @throws IllegalArgumentException if documentTypeName is null or blank
116      * @throws IllegalDocumentTypeException if documentTypeName does not represent a valid document type
117      * @see org.kuali.rice.kew.impl.document.WorkflowDocumentProvider#createDocument(String, String, DocumentUpdate, DocumentContentUpdate)
118      */
119     public static WorkflowDocument createDocument(String principalId, String documentTypeName, DocumentUpdate documentUpdate, DocumentContentUpdate documentContentUpdate) {
120         if (StringUtils.isBlank(principalId)) {
121             throw new IllegalArgumentException("principalId was null or blank");
122         }
123         if (StringUtils.isBlank(documentTypeName)) {
124             throw new IllegalArgumentException("documentTypeName was null or blank");
125         }
126 
127         Object workflowDocument = null;
128 
129         try {
130             workflowDocument = ProviderHolder.createMethod.invoke(ProviderHolder.provider, principalId, documentTypeName, documentUpdate, documentContentUpdate);
131         } catch (IllegalAccessException e) {
132             throw new ConfigurationException("Failed to invoke " + CREATE_METHOD_NAME, e);
133         } catch (InvocationTargetException e) {
134             if (e.getCause() instanceof RuntimeException) {
135                 throw (RuntimeException)e.getCause();
136             }
137             throw new ConfigurationException("Failed to invoke " + CREATE_METHOD_NAME, e);
138         }
139 
140         if (!(workflowDocument instanceof WorkflowDocument)) {
141             throw new ConfigurationException("Created document is not a proper instance of " + WorkflowDocument.class + ", was instead " + workflowDocument.getClass());
142         }
143         return (WorkflowDocument)workflowDocument;
144     }
145 
146     /**
147      * Loads an existing workflow document.
148      * @param principalId the principal id under which to perform document actions
149      * @param documentId the id of the document to load
150      *
151      * @return a WorkflowDocument object through which to interact with the loaded workflow document
152      *
153      * @throws IllegalArgumentException if principalId is null or blank
154      * @throws IllegalArgumentException if documentTypeName is null or blank
155      * @throws IllegalDocumentTypeException if the specified document type is not active
156      * @throws IllegalDocumentTypeException if the specified document type does not support document
157      *         creation (in other words, it's a document type that is only used as a parent)
158      * @throws InvalidActionTakenException if the supplied principal is not allowed to execute this
159      *         action
160      * @see org.kuali.rice.kew.impl.document.WorkflowDocumentProvider#loadDocument(String, String)
161      */
162     public static WorkflowDocument loadDocument(String principalId, String documentId) {
163         if (StringUtils.isBlank(principalId)) {
164             throw new IllegalArgumentException("principalId was null or blank");
165         }
166         if (StringUtils.isBlank(documentId)) {
167             throw new IllegalArgumentException("documentId was null or blank");
168         }
169 
170         Object workflowDocument = null;
171 
172         try {
173             workflowDocument = ProviderHolder.loadMethod.invoke(ProviderHolder.provider, principalId, documentId);
174         } catch (IllegalAccessException e) {
175             throw new ConfigurationException("Failed to invoke " + LOAD_METHOD_NAME, e);
176         } catch (InvocationTargetException e) {
177             if (e.getCause() instanceof RuntimeException) {
178                 throw (RuntimeException)e.getCause();
179             }
180             throw new ConfigurationException("Failed to invoke " + LOAD_METHOD_NAME, e);
181         }
182 
183         if (!(workflowDocument instanceof WorkflowDocument)) {
184             throw new ConfigurationException("Loaded document is not a proper instance of " + WorkflowDocument.class + ", was instead " + workflowDocument.getClass());
185         }
186         return (WorkflowDocument)workflowDocument;
187     }
188 
189     /**
190      * Loads a global WorkflowDocumentProvider implementation
191      * @return the WorkflowDocumentProvider
192      */
193     private static Object loadProvider() {
194         String providerClassName = null;
195         String resource = null;
196         try {
197             resource = new StringBuilder().append("META-INF/services/").append(WorkflowDocument.class.getName()).toString();
198             final InputStream resourceStream = ClassLoaderUtils.getDefaultClassLoader().getResourceAsStream(resource.toString());
199             if (resourceStream != null) {
200                 BufferedReader reader = new BufferedReader(new InputStreamReader(resourceStream, "UTF-8"));
201                 providerClassName = reader.readLine().trim();
202                 reader.close();
203                 Class<?> providerClass = Class.forName(providerClassName);
204                 return newInstance(providerClass);
205             } else {
206                 throw new ConfigurationException("Failed to locate a services definition file at " + resource);
207             }
208         } catch (IOException e) {
209             throw new ConfigurationException("Failure processing services definition file at " + resource, e);
210         } catch (ClassNotFoundException e) {
211             throw new ConfigurationException("Failed to load provider class: " + providerClassName, e);
212         }
213     }
214 
215     private static Object newInstance(Class<?> providerClass) {
216         try {
217             return providerClass.newInstance();
218         } catch (InstantiationException e) {
219             throw new ConfigurationException("Failed to instantiate provider class: " + providerClass.getName(), e);
220         } catch (IllegalAccessException e) {
221             throw new ConfigurationException("Failed to instantiate provider class: " + providerClass.getName(), e);
222         }
223     }
224 
225     private static Method locateCreateMethod(Object provider) {
226         try {
227             return provider.getClass().getMethod(CREATE_METHOD_NAME, String.class, String.class, DocumentUpdate.class, DocumentContentUpdate.class);
228         } catch (NoSuchMethodException e) {
229             throw new ConfigurationException("Failed to locate valid createDocument method signature on provider class: " + provider.getClass().getName(), e);
230         } catch (SecurityException e) {
231             throw new ConfigurationException("Encountered security issue when attempting to access createDocument method on provider class: " + provider.getClass().getName(), e);
232         }
233     }
234 
235     private static Method locateLoadMethod(Object provider) {
236         try {
237             return provider.getClass().getMethod(LOAD_METHOD_NAME, String.class, String.class);
238         } catch (NoSuchMethodException e) {
239             throw new ConfigurationException("Failed to locate valid createDocument method signature on provider class: " + provider.getClass().getName(), e);
240         } catch (SecurityException e) {
241             throw new ConfigurationException("Encountered security issue when attempting to access createDocument method on provider class: " + provider.getClass().getName(), e);
242         }
243     }
244 
245 }