001/** 002 * Copyright 2005-2018 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.kew.api; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.core.api.config.ConfigurationException; 020import org.kuali.rice.core.api.util.ClassLoaderUtils; 021import org.kuali.rice.kew.api.action.InvalidActionTakenException; 022import org.kuali.rice.kew.api.doctype.IllegalDocumentTypeException; 023import org.kuali.rice.kew.api.document.DocumentContentUpdate; 024import org.kuali.rice.kew.api.document.DocumentUpdate; 025 026import java.io.BufferedReader; 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.InputStreamReader; 030import java.lang.reflect.InvocationTargetException; 031import java.lang.reflect.Method; 032 033/** 034 * Factory which manufactures WorkflowDocuments. This is the main entry point for interaction with the 035 * Kuali Enterprise Workflow System. 036 * 037 * The WorkflowDocumentFactory uses the {@link org.kuali.rice.kew.impl.document.WorkflowDocumentProvider} SPI as a strategy 038 * for creating WorkflowDocument instances. 039 * 040 * The provider class is specified in the following file in the class loader: "META-INF/services/org.kuali.rice.kew.api.WorkflowDocument", 041 * and should implement the WorkflowDocumentProvider interface. 042 */ 043public final class WorkflowDocumentFactory { 044 045 private static final String CREATE_METHOD_NAME = "createDocument"; 046 private static final String LOAD_METHOD_NAME = "loadDocument"; 047 048 /** 049 * A lazy initialization holder class for the Provider. Allows for 050 * thread-safe initialization of shared resource. 051 * 052 * NOTE: ProviderHolder and its fields are static, therefore there 053 * will only be a simple WorkflowDocumentProvider instance so it needs to be 054 * thread-safe. 055 */ 056 private static final class ProviderHolder { 057 static final Object provider; 058 static final Method createMethod; 059 static final Method loadMethod; 060 static { 061 provider = loadProvider(); 062 createMethod = locateCreateMethod(provider); 063 loadMethod = locateLoadMethod(provider); 064 } 065 } 066 067 /** 068 * Creates a new workflow document of the given type with the given initiator. 069 * 070 * @param principalId the document initiator 071 * @param documentTypeName the document type 072 * 073 * @return a WorkflowDocument object through which to interact with the new workflow document 074 * 075 * @throws IllegalArgumentException if principalId is null or blank 076 * @throws IllegalArgumentException if documentTypeName is null or blank 077 * @throws IllegalDocumentTypeException if the document type does not allow for creation of a document, 078 * this can occur when the given document type is used only as a parent and has no route path configured 079 * @throws InvalidActionTakenException if the caller is not allowed to execute this action 080 */ 081 public static WorkflowDocument createDocument(String principalId, String documentTypeName) { 082 return createDocument(principalId, documentTypeName, null, null); 083 } 084 085 /** 086 * Creates a new workflow document of the given type with the given initiator. 087 * 088 * @param principalId the document initiator 089 * @param documentTypeName the document type 090 * @param title the title of the new document 091 * 092 * @return a WorkflowDocument object through which to interact with the new workflow document 093 * 094 * @throws IllegalArgumentException if principalId is null or blank 095 * @throws IllegalArgumentException if documentTypeName is null or blank 096 * @throws IllegalDocumentTypeException if documentTypeName does not represent a valid document type 097 */ 098 public static WorkflowDocument createDocument(String principalId, String documentTypeName, String title) { 099 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}