001/**
002 * Copyright 2005-2016 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.doctype.bo;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.commons.lang.StringUtils;
020import org.kuali.rice.core.api.config.CoreConfigHelper;
021import org.kuali.rice.core.api.config.property.ConfigContext;
022import org.kuali.rice.core.api.exception.RiceRemoteServiceConnectionException;
023import org.kuali.rice.core.api.mo.common.active.MutableInactivatable;
024import org.kuali.rice.core.api.reflect.ObjectDefinition;
025import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
026import org.kuali.rice.core.web.format.FormatException;
027import org.kuali.rice.kew.actionlist.CustomActionListAttribute;
028import org.kuali.rice.kew.api.KEWPropertyConstants;
029import org.kuali.rice.kew.api.KewApiConstants;
030import org.kuali.rice.kew.api.KewApiServiceLocator;
031import org.kuali.rice.kew.api.WorkflowRuntimeException;
032import org.kuali.rice.kew.api.doctype.DocumentTypeAttribute;
033import org.kuali.rice.kew.api.doctype.DocumentTypeAttributeContract;
034import org.kuali.rice.kew.api.doctype.DocumentTypeContract;
035import org.kuali.rice.kew.api.exception.ResourceUnavailableException;
036import org.kuali.rice.kew.api.extension.ExtensionDefinition;
037import org.kuali.rice.kew.api.extension.ExtensionUtils;
038import org.kuali.rice.kew.api.util.CodeTranslator;
039import org.kuali.rice.kew.doctype.ApplicationDocumentStatus;
040import org.kuali.rice.kew.doctype.ApplicationDocumentStatusCategory;
041import org.kuali.rice.kew.doctype.DocumentTypeAttributeBo;
042import org.kuali.rice.kew.doctype.DocumentTypePolicy;
043import org.kuali.rice.kew.doctype.DocumentTypeSecurity;
044import org.kuali.rice.kew.doctype.service.DocumentTypeService;
045import org.kuali.rice.kew.engine.node.ProcessDefinitionBo;
046import org.kuali.rice.kew.framework.document.attribute.SearchableAttribute;
047import org.kuali.rice.kew.framework.postprocessor.PostProcessor;
048import org.kuali.rice.kew.mail.CustomEmailAttribute;
049import org.kuali.rice.kew.notes.CustomNoteAttribute;
050import org.kuali.rice.kew.postprocessor.DefaultPostProcessor;
051import org.kuali.rice.kew.rule.bo.RuleAttribute;
052import org.kuali.rice.kew.service.KEWServiceLocator;
053import org.kuali.rice.kew.util.Utilities;
054import org.kuali.rice.kim.api.group.Group;
055import org.kuali.rice.kim.api.group.GroupService;
056import org.kuali.rice.kim.api.services.KimApiServiceLocator;
057import org.kuali.rice.krad.bo.PersistableBusinessObjectBase;
058import org.kuali.rice.krad.data.DataObjectWrapper;
059import org.kuali.rice.krad.data.KradDataServiceLocator;
060import org.kuali.rice.krad.data.jpa.converters.Boolean01Converter;
061import org.kuali.rice.krad.data.jpa.PortableSequenceGenerator;
062import org.kuali.rice.krad.util.KRADUtils;
063
064import javax.persistence.Basic;
065import javax.persistence.CascadeType;
066import javax.persistence.Column;
067import javax.persistence.Convert;
068import javax.persistence.Entity;
069import javax.persistence.FetchType;
070import javax.persistence.GeneratedValue;
071import javax.persistence.Id;
072import javax.persistence.JoinColumn;
073import javax.persistence.Lob;
074import javax.persistence.NamedQueries;
075import javax.persistence.NamedQuery;
076import javax.persistence.OneToMany;
077import javax.persistence.OrderBy;
078import javax.persistence.Table;
079import javax.persistence.Transient;
080import java.lang.reflect.InvocationTargetException;
081import java.util.ArrayList;
082import java.util.Arrays;
083import java.util.Collection;
084import java.util.Collections;
085import java.util.HashMap;
086import java.util.Iterator;
087import java.util.List;
088import java.util.Map;
089import java.util.Set;
090
091import static org.kuali.rice.kew.api.doctype.DocumentTypePolicy.*;
092
093/**
094 * Model bean mapped to ojb representing a document type.  Provides component lookup behavior that
095 * can construct {@link ObjectDefinition} objects correctly to account for application id inheritance.
096 * Can also navigate parent hierarchy when getting data/components.
097 *
098 * @author Kuali Rice Team (rice.collab@kuali.org)
099 */
100@Entity
101@Table(name = "KREW_DOC_TYP_T")
102@NamedQueries({
103        @NamedQuery(name = "DocumentType.GetAppIdByDocumentId", query = "SELECT DT.actualApplicationId FROM DocumentType DT, "
104                + "DocumentRouteHeaderValue DH WHERE DH.documentTypeId=DT.documentTypeId AND DH.documentId = :documentId"),
105        @NamedQuery(name = "DocumentType.GetIdByName", query = "SELECT d.documentTypeId FROM DocumentType d WHERE "+
106                "d.name = :docTypeName AND d.currentInd = true"),
107        @NamedQuery(name = "DocumentType.FindDocumentTypeNameById", query = "SELECT d.name FROM DocumentType d WHERE "+
108                "d.documentTypeId = :documentTypeId AND d.currentInd = true"),
109        @NamedQuery(name = "DocumentType.GetMaxVersionNumber", query = "SELECT MAX(d.version) FROM DocumentType d WHERE "+
110                "d.name = :docTypeName"),
111        @NamedQuery(name = "DocumentType.GetChildDocumentTypeIds", query = "SELECT d.documentTypeId FROM DocumentType d "
112                + "WHERE d.currentInd = true AND d.docTypeParentId = :parentDocumentTypeId"),
113        @NamedQuery(name = "DocumentType.GetDocumentTypeByDocumentId", query = "SELECT DT FROM DocumentType DT, "
114                + "DocumentRouteHeaderValue DH WHERE DH.documentTypeId=DT.documentTypeId AND DH.documentId = :documentId"),
115        @NamedQuery(name = "DocumentType.GetDocumentTypeByName", query = "SELECT d FROM DocumentType d "
116                + "WHERE d.currentInd = true AND d.name = :name"),
117        @NamedQuery(name = "DocumentType.QuickLinks.FindInitiatedDocumentTypesListByInitiatorWorkflowId", query =
118                "SELECT DISTINCT dt.name, dt.label FROM DocumentType dt, DocumentRouteHeaderValue drhv " +
119                "WHERE drhv.initiatorWorkflowId = :initiatorWorkflowId AND drhv.documentTypeId = dt.documentTypeId "
120                + "AND dt.active = true AND dt.currentInd = true " +
121                "ORDER BY UPPER(dt.label)"),
122        @NamedQuery(name = "DocumentType.parentNameByName",
123                query = "SELECT pdt.name FROM DocumentType dt, DocumentType pdt " +
124                "WHERE dt.docTypeParentId=pdt.documentTypeId AND dt.name = :docTypeName AND dt.currentInd = true")
125})
126public class DocumentType extends PersistableBusinessObjectBase implements MutableInactivatable, DocumentTypeEBO, DocumentTypeContract {
127    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentType.class);
128
129    private static final long serialVersionUID = 1312830153583125069L;
130
131    @Id
132    @GeneratedValue(generator = "KREW_DOC_HDR_S")
133    @PortableSequenceGenerator(name = "KREW_DOC_HDR_S")
134    @Column(name = "DOC_TYP_ID", nullable = false)
135    private String documentTypeId;
136
137    @Column(name = "PARNT_ID")
138    private String docTypeParentId;
139
140    @Column(name = "DOC_TYP_NM")
141    private String name;
142
143    @Column(name = "DOC_TYP_VER_NBR")
144    private Integer version = new Integer(0);
145
146    @Column(name = "ACTV_IND")
147    @Convert(converter=Boolean01Converter.class)
148    private Boolean active;
149
150    @Column(name = "CUR_IND")
151    @Convert(converter=Boolean01Converter.class)
152    private Boolean currentInd;
153
154    @Column(name = "DOC_TYP_DESC")
155    private String description;
156
157    @Column(name = "LBL", nullable = false)
158    private String label;
159
160    @Column(name = "PREV_DOC_TYP_VER_NBR")
161    private String previousVersionId;
162
163    /**
164     * The id of the document which caused the last modification of this document type.
165     * Null if this doc type was never modified via a document routing (UI).
166     */
167    @Column(name = "DOC_HDR_ID")
168    private String documentId;
169
170    @Column(name = "HELP_DEF_URL")
171    private String unresolvedHelpDefinitionUrl;
172
173    @Column(name = "DOC_SEARCH_HELP_URL")
174    private String unresolvedDocSearchHelpUrl;
175
176    @Column(name = "DOC_HDLR_URL")
177    private String unresolvedDocHandlerUrl;
178
179    @Column(name = "POST_PRCSR")
180    private String postProcessorName;
181
182    @Column(name = "GRP_ID")
183    private String workgroupId;
184
185    @Column(name = "BLNKT_APPR_GRP_ID")
186    private String blanketApproveWorkgroupId;
187
188    @Column(name = "BLNKT_APPR_PLCY")
189    private String blanketApprovePolicy;
190
191    @Column(name = "RPT_GRP_ID")
192    private String reportingWorkgroupId;
193
194    @Column(name = "APPL_ID")
195    private String actualApplicationId;
196
197    /**
198     * @since 2.1.3
199     */
200    @Column(name = "AUTHORIZER")
201    private String authorizer;
202
203    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "documentType")
204    private Collection<DocumentTypePolicy> documentTypePolicies;
205
206    /* This property contains the list of valid ApplicationDocumentStatus values,
207    * if defined, for the document type.  If these status values are defined, only these
208    * values may be assigned as the status.  If not valid values are defined, the status may
209    * be set to any value by the client.
210    */
211    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
212    @JoinColumn(name = "DOC_TYP_ID")
213    private List<ApplicationDocumentStatus> validApplicationStatuses;
214
215    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
216    @JoinColumn(name = "DOC_TYP_ID")
217    private List<ApplicationDocumentStatusCategory> applicationStatusCategories;
218
219    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "documentType")
220    @OrderBy("orderIndex ASC")
221    private List<DocumentTypeAttributeBo> documentTypeAttributes;
222
223    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "documentType")
224    private List<ProcessDefinitionBo> processes = new ArrayList<ProcessDefinitionBo>();
225
226    @Column(name = "RTE_VER_NBR")
227    private String routingVersion = KewApiConstants.CURRENT_ROUTING_VERSION;
228
229    @Column(name = "NOTIFY_ADDR")
230    private String actualNotificationFromAddress;
231
232    @Lob
233    @Basic(fetch = FetchType.LAZY)
234    @Column(name = "SEC_XML")
235    private String documentTypeSecurityXml;
236
237    /* XSLT-based email message customization */
238    @Column(name = "EMAIL_XSL")
239    private String customEmailStylesheet;
240
241
242    /* these two fields are for the web tier lookupable
243     * DocumentType is doing double-duty as a web/business tier object
244     */
245    @Transient private String returnUrl;
246    @Transient private String actionsUrl;
247    @Transient private Boolean applyRetroactively = Boolean.FALSE;
248
249    /* The default exception workgroup to apply to nodes that lack an exception workgroup definition.
250     * Used at parse-time only; not stored in db.
251     */
252    @Transient private Group defaultExceptionWorkgroup;
253    @Transient private Collection<DocumentType> childrenDocTypes;
254    @Transient private DocumentTypeSecurity documentTypeSecurity;
255
256    public DocumentType() {
257        documentTypeAttributes = new ArrayList<DocumentTypeAttributeBo>();
258        documentTypePolicies = new ArrayList<DocumentTypePolicy>();
259        version = new Integer(0);
260        label = null;
261    }
262
263    public void populateDataDictionaryEditableFields(Set<String> propertyNamesEditableViaUI, DocumentType dataDictionaryEditedType) {
264        DataObjectWrapper<DocumentType> wrapper = KradDataServiceLocator.getDataObjectService().wrap(dataDictionaryEditedType);
265
266        String currentPropertyName = "";
267        try {
268            for (String propertyName : propertyNamesEditableViaUI) {
269                currentPropertyName = propertyName;
270                if (KEWPropertyConstants.PARENT_DOC_TYPE_NAME.equals(propertyName)) {
271                    // this is trying to set the parent document type name so lets set the entire parent document
272                    String parentDocumentTypeName = (String) wrapper.getPropertyValueNullSafe(propertyName);
273                    if (StringUtils.isNotBlank(parentDocumentTypeName)) {
274                        DocumentType parentDocType = KEWServiceLocator.getDocumentTypeService().findByName(parentDocumentTypeName);
275                        if (parentDocType == null) {
276                            throw new WorkflowRuntimeException("Could not find valid document type for document type name '" + parentDocumentTypeName + "' to set as Parent Document Type");
277                        }
278                        setDocTypeParentId(parentDocType.getDocumentTypeId());
279                    }
280                }
281//                else if (!FIELD_PROPERTY_NAME_DOCUMENT_TYPE_ID.equals(propertyName)) {
282                else {
283                    LOG.info("*** COPYING PROPERTY NAME FROM OLD BO TO NEW BO: " + propertyName);
284                    KRADUtils.setObjectProperty(this, propertyName, wrapper.getPropertyValueNullSafe(propertyName));
285                }
286            }
287        } catch (FormatException e) {
288            throw new WorkflowRuntimeException("Error setting property '" + currentPropertyName + "' in Document Type", e);
289        } catch (IllegalAccessException e) {
290            throw new WorkflowRuntimeException("Error setting property '" + currentPropertyName + "' in Document Type", e);
291        } catch (InvocationTargetException e) {
292            throw new WorkflowRuntimeException("Error setting property '" + currentPropertyName + "' in Document Type", e);
293        } catch (NoSuchMethodException e) {
294            throw new WorkflowRuntimeException("Error setting property '" + currentPropertyName + "' in Document Type", e);
295        } catch (Exception e) {
296            throw new WorkflowRuntimeException("Error setting property '" + currentPropertyName + "' in Document Type", e);
297        }
298    }
299
300    public DocumentTypePolicy getAllowUnrequestedActionPolicy() {
301        return getPolicyByName(ALLOW_UNREQUESTED_ACTION.getCode(), Boolean.TRUE);
302    }
303
304    public DocumentTypePolicy getDefaultApprovePolicy() {
305        return getPolicyByName(DEFAULT_APPROVE.getCode(), Boolean.TRUE);
306    }
307
308    public DocumentTypePolicy getUseWorkflowSuperUserDocHandlerUrl() {
309        return getPolicyByName(USE_KEW_SUPERUSER_DOCHANDLER.getCode(), Boolean.TRUE);
310    }
311
312    public DocumentTypePolicy getInitiatorMustRoutePolicy() {
313        return getPolicyByName(INITIATOR_MUST_ROUTE.getCode(), Boolean.TRUE);
314    }
315
316    public DocumentTypePolicy getInitiatorMustSavePolicy() {
317        return getPolicyByName(INITIATOR_MUST_SAVE.getCode(), Boolean.TRUE);
318    }
319
320    public DocumentTypePolicy getInitiatorMustCancelPolicy() {
321        return getPolicyByName(INITIATOR_MUST_CANCEL.getCode(), Boolean.TRUE);
322    }
323
324    public DocumentTypePolicy getInitiatorMustBlanketApprovePolicy() {
325        return getPolicyByName(INITIATOR_MUST_BLANKET_APPROVE.getCode(), Boolean.TRUE);
326    }
327
328    public DocumentTypePolicy getLookIntoFuturePolicy() {
329        return getPolicyByName(LOOK_FUTURE.getCode(), Boolean.FALSE);
330    }
331
332    public DocumentTypePolicy getSuperUserApproveNotificationPolicy() {
333        return getPolicyByName(SEND_NOTIFICATION_ON_SU_APPROVE.getCode(), Boolean.FALSE);
334    }
335
336    public DocumentTypePolicy getSupportsQuickInitiatePolicy() {
337        return getPolicyByName(SUPPORTS_QUICK_INITIATE.getCode(), Boolean.TRUE);
338    }
339
340    public DocumentTypePolicy getNotifyOnSavePolicy() {
341        return getPolicyByName(NOTIFY_ON_SAVE.getCode(), Boolean.FALSE);
342    }
343
344    /**
345     * This method returns a DocumentTypePolicy object related to the DocumentStatusPolicy defined for this document type.
346     */
347    public DocumentTypePolicy getDocumentStatusPolicy() {
348        return getPolicyByName(DOCUMENT_STATUS_POLICY.getCode(), KewApiConstants.DOCUMENT_STATUS_POLICY_KEW_STATUS);
349    }
350
351    /**
352     * This method returns a DocumentTypePolicy object related to the DocumentStatusPolicy defined for this document type.
353     */
354    public DocumentTypePolicy getSuPostprocessorOverridePolicy() {
355        return getPolicyByName(ALLOW_SU_POSTPROCESSOR_OVERRIDE.getCode(), Boolean.TRUE);
356    }
357        
358    public DocumentTypePolicy getFailOnInactiveGroup() {
359        return getPolicyByName(FAIL_ON_INACTIVE_GROUP.getCode(), Boolean.TRUE);
360    }
361    
362    public DocumentTypePolicy getEnrouteErrorSuppression() {
363        return getPolicyByName(ENROUTE_ERROR_SUPPRESSION.getCode(), Boolean.FALSE);
364    }
365    
366    public DocumentTypePolicy getRegenerateActionRequestsOnChange() {
367        return getPolicyByName(REGENERATE_ACTION_REQUESTS_ON_CHANGE.getCode(), Boolean.TRUE);
368    }
369
370    /**
371     * Returns the RECALL_NOTIFICATION policy on the document if defined, or
372     * the default value for this policy which is true.
373     * @return the RECALL_NOTIFICATION document type policy
374     * @since 2.1
375     */
376    public DocumentTypePolicy getRecallNotification() {
377        return getPolicyByName(RECALL_NOTIFICATION.getCode(), (String) null);
378    }
379
380    /**
381     * Returns the SUPPRESS_IMMEDIATE_EMAILS_ON_SU_ACTION policy on the document if defined, or
382     * the default value for this policy which is false.
383     * @return the SUPPRESS_IMMEDIATE_EMAILS_ON_SU_ACTION document type policy
384     * @since 2.1.3
385     */
386    public DocumentTypePolicy getSuppressImmediateEmailsOnSuActionPolicy() {
387        return getPolicyByName(SUPPRESS_IMMEDIATE_EMAILS_ON_SU_ACTION.getCode(), Boolean.FALSE);
388    }
389
390    /**
391     * Returns the ALLOW_SU_FINAL_APPROVAL policy on the document if defined, or
392     * the default value for this policy which is true.
393     * @return the ALLOW_SU_FINAL_APPROVAL document type policy
394     * @since 2.1.3
395     */
396    public DocumentTypePolicy getAllowSuperUserFinalApprovalPolicy() {
397        return getPolicyByName(ALLOW_SU_FINAL_APPROVAL.getCode(), Boolean.TRUE);
398    }
399
400    /**
401     * Returns the DOC_SEARCH_TARGET policy on the document if defined
402     * @return the DOC_SEARCH_TARGET document type policy
403     * @since 2.5.0
404     */
405    public DocumentTypePolicy getDocSearchTarget() {
406        return getPolicyByName(DOC_SEARCH_TARGET.getCode(), (String) null);
407    }
408
409    /**
410     * This method returns a boolean denoting whether the KEW Route Status is to be displayed.
411     * The KEW Route Status is updated by the workflow engine regardless of whether it is to be displayed or not.
412     *
413     * @return true  - if the status is to be displayed  (Policy is set to either use KEW (default) or both)
414     *         false - if the KEW Route Status is not to be displayed
415     */
416    public Boolean isKEWStatusInUse() {
417        if (isPolicyDefined(DOCUMENT_STATUS_POLICY)) {
418            String policyValue = getPolicyByName(DOCUMENT_STATUS_POLICY.getCode(), KewApiConstants.DOCUMENT_STATUS_POLICY_KEW_STATUS).getPolicyStringValue();
419            return (policyValue == null || "".equals(policyValue)
420                    || KewApiConstants.DOCUMENT_STATUS_POLICY_KEW_STATUS.equalsIgnoreCase(policyValue)
421                    || KewApiConstants.DOCUMENT_STATUS_POLICY_BOTH.equalsIgnoreCase(policyValue)) ? Boolean.TRUE : Boolean.FALSE;
422        } else {
423            return Boolean.TRUE;
424        }
425    }
426
427    /**
428     * This method returns a boolean denoting whether the Application Document Status is to be used for this document type.
429     *
430     * @return true  - if the status is to be displayed  (Policy is set to either use the application document status or both)
431     *         false - if only the KEW Route Status is to be displayed (default)
432     */
433    public Boolean isAppDocStatusInUse() {
434        if (isPolicyDefined(DOCUMENT_STATUS_POLICY)) {
435            String policyValue = getPolicyByName(DOCUMENT_STATUS_POLICY.getCode(), KewApiConstants.DOCUMENT_STATUS_POLICY_KEW_STATUS).getPolicyStringValue();
436            return (KewApiConstants.DOCUMENT_STATUS_POLICY_APP_DOC_STATUS.equalsIgnoreCase(policyValue)
437                    || KewApiConstants.DOCUMENT_STATUS_POLICY_BOTH.equalsIgnoreCase(policyValue)) ? Boolean.TRUE : Boolean.FALSE;
438        } else {
439            return Boolean.FALSE;
440        }
441    }
442
443    /**
444     * This method returns a boolean denoting if both the KEW Route Status and the Application Document Status
445     * are to be used in displays.
446     *
447     * @return true  - if both the KEW Route Status and Application Document Status are to be displayed.
448     *         false - if only one status is to be displayed.
449     */
450    public Boolean areBothStatusesInUse() {
451        if (isPolicyDefined(DOCUMENT_STATUS_POLICY)) {
452            String policyValue = getPolicyByName(DOCUMENT_STATUS_POLICY.getCode(), KewApiConstants.DOCUMENT_STATUS_POLICY_KEW_STATUS).getPolicyStringValue();
453            return (KewApiConstants.DOCUMENT_STATUS_POLICY_BOTH.equalsIgnoreCase(policyValue)) ? Boolean.TRUE : Boolean.FALSE;
454        } else {
455            return Boolean.FALSE;
456        }
457    }
458
459    public String getUseWorkflowSuperUserDocHandlerUrlValue() {
460        if (getUseWorkflowSuperUserDocHandlerUrl() != null) {
461            return getUseWorkflowSuperUserDocHandlerUrl().getPolicyDisplayValue();
462        }
463        return null;
464    }
465
466    public String getAllowUnrequestedActionPolicyDisplayValue() {
467        if (getAllowUnrequestedActionPolicy() != null) {
468            return getAllowUnrequestedActionPolicy().getPolicyDisplayValue();
469        }
470        return null;
471    }
472
473    public String getDefaultApprovePolicyDisplayValue() {
474        if (getDefaultApprovePolicy() != null) {
475            return getDefaultApprovePolicy().getPolicyDisplayValue();
476        }
477        return null;
478    }
479
480    public String getInitiatorMustRouteDisplayValue() {
481        if (getInitiatorMustRoutePolicy() != null) {
482            return getInitiatorMustRoutePolicy().getPolicyDisplayValue();
483        }
484        return null;
485    }
486
487    public String getInitiatorMustSaveDisplayValue() {
488        if (getInitiatorMustSavePolicy() != null) {
489            return getInitiatorMustSavePolicy().getPolicyDisplayValue();
490        }
491        return null;
492    }
493
494    public boolean isPolicyDefined(org.kuali.rice.kew.api.doctype.DocumentTypePolicy policyToCheck) {
495        Iterator<DocumentTypePolicy> policyIter = getDocumentTypePolicies().iterator();
496        while (policyIter.hasNext()) {
497            DocumentTypePolicy policy = policyIter.next();
498            if (policyToCheck.getCode().equals(policy.getPolicyName())) {
499                return true;
500            }
501        }
502        return getParentDocType() != null && getParentDocType().isPolicyDefined(policyToCheck);
503    }
504
505    public List<DocumentTypeAttributeBo> getDocumentTypeAttributes(String... attributeTypes) {
506        List<DocumentTypeAttributeBo> filteredAttributes = new ArrayList<DocumentTypeAttributeBo>();
507        if (CollectionUtils.isNotEmpty(documentTypeAttributes)) {
508            if (attributeTypes == null) {
509                filteredAttributes.addAll(documentTypeAttributes);
510            } else {
511                List<String> attributeTypeList = Arrays.asList(attributeTypes);
512                for (DocumentTypeAttributeBo documentTypeAttribute : documentTypeAttributes) {
513                    RuleAttribute ruleAttribute = documentTypeAttribute.getRuleAttribute();
514                    if (attributeTypeList.contains(ruleAttribute.getType())) {
515                        filteredAttributes.add(documentTypeAttribute);
516                    }
517                }
518            }
519        }
520        if (filteredAttributes.isEmpty() && getParentDocType() != null) {
521            return getParentDocType().getDocumentTypeAttributes(attributeTypes);
522        }
523        return Collections.unmodifiableList(filteredAttributes);
524    }
525
526    public boolean hasSearchableAttributes() {
527        return !getSearchableAttributes().isEmpty();
528    }
529
530    public List<DocumentTypeAttributeBo> getSearchableAttributes() {
531        return getDocumentTypeAttributes(KewApiConstants.SEARCHABLE_ATTRIBUTE_TYPE, KewApiConstants.SEARCHABLE_XML_ATTRIBUTE_TYPE);
532    }
533
534    public DocumentTypeAttributeBo getCustomizerAttribute() {
535        List<DocumentTypeAttributeBo> documentTypeAttributes = getDocumentTypeAttributes(KewApiConstants.DOCUMENT_SEARCH_CUSTOMIZER_ATTRIBUTE_TYPE);
536        if (documentTypeAttributes.size() > 1) {
537            throw new IllegalStateException("Encountered more than one DocumentSearchCustomizer attribute on this document type: " + getName());
538        }
539        if (documentTypeAttributes.isEmpty()) {
540            return null;
541        }
542        return documentTypeAttributes.get(0);
543    }
544
545    /**
546     * Returns the RuleAttribute for the action list attribute for this DocumentType.  Walks the document type hierarchy
547     * if none exists directly on this DocumentType.
548     * @return The RuleAttribute.  May be null.
549     */
550    public RuleAttribute getCustomActionListRuleAttribute() {
551        List<DocumentTypeAttributeBo> documentTypeAttributes = getDocumentTypeAttributes(KewApiConstants.ACTION_LIST_ATTRIBUTE_TYPE);
552        if (documentTypeAttributes.size() > 1) {
553            throw new IllegalStateException("Encountered more than one ActionListAttribute on this document type: " + getName());
554        }
555        if (documentTypeAttributes.isEmpty()) {
556            return null;
557        }
558        return documentTypeAttributes.get(0).getRuleAttribute();
559    }
560
561    public List<ExtensionHolder<SearchableAttribute>> loadSearchableAttributes() {
562        List<DocumentTypeAttributeBo> searchableAttributes = getSearchableAttributes();
563        List<ExtensionHolder<SearchableAttribute>> loadedAttributes = new ArrayList<ExtensionHolder<SearchableAttribute>>();
564        for (DocumentTypeAttributeBo documentTypeAttribute : searchableAttributes) {
565            RuleAttribute ruleAttribute = documentTypeAttribute.getRuleAttribute();
566            try {
567                ExtensionDefinition extensionDefinition = KewApiServiceLocator.getExtensionRepositoryService().getExtensionById(ruleAttribute.getId());
568                SearchableAttribute attributeService = ExtensionUtils.loadExtension(extensionDefinition, getApplicationId());
569                loadedAttributes.add(new ExtensionHolder<SearchableAttribute>(extensionDefinition, attributeService));
570            } catch (RiceRemoteServiceConnectionException e) {
571                LOG.warn("Unable to connect to load searchable attribute for " + ruleAttribute, e);
572            }
573        }
574        return loadedAttributes;
575    }
576
577    public static final class ExtensionHolder<T> {
578
579        //private final RuleAttribute ruleAttribute;
580        private final ExtensionDefinition extensionDefinition;
581        private final T extension;
582
583        public ExtensionHolder(ExtensionDefinition extensionDefinition, T extension) {
584            //this.ruleAttribute = ruleAttribute;
585            this.extensionDefinition = extensionDefinition;
586            this.extension = extension;
587        }
588
589        /*public RuleAttribute getRuleAttribute() {
590            return ruleAttribute;
591        }*/
592
593        public ExtensionDefinition getExtensionDefinition() {
594            return extensionDefinition;
595        }
596
597        public T getExtension() {
598            return extension;
599        }
600    }
601
602    public DocumentTypeAttributeBo getDocumentTypeAttribute(int index) {
603        while (getDocumentTypeAttributes().size() <= index) {
604            DocumentTypeAttributeBo attribute = new DocumentTypeAttributeBo();
605            //attribute.setDocumentTypeId(this.documentTypeId);
606            getDocumentTypeAttributes().add(attribute);
607        }
608        return (DocumentTypeAttributeBo) getDocumentTypeAttributes().get(index);
609    }
610
611    public void setDocumentTypeAttribute(int index, DocumentTypeAttributeBo documentTypeAttribute) {
612        documentTypeAttributes.set(index, documentTypeAttribute);
613    }
614
615    public String getDocTypeActiveIndicatorDisplayValue() {
616        if (getActive() == null) {
617            return KewApiConstants.INACTIVE_LABEL_LOWER;
618        }
619        return CodeTranslator.getActiveIndicatorLabel(getActive());
620    }
621
622    public Collection<DocumentType> getChildrenDocTypes() {
623        if (this.childrenDocTypes == null) {
624            this.childrenDocTypes = KEWServiceLocator.getDocumentTypeService().getChildDocumentTypes(getDocumentTypeId());
625        }
626        return childrenDocTypes;
627    }
628
629    public String getDocTypeParentId() {
630        return docTypeParentId;
631    }
632
633    public void setDocTypeParentId(String docTypeParentId) {
634        this.docTypeParentId = docTypeParentId;
635    }
636
637    public DocumentType getParentDocType() {
638        return KEWServiceLocator.getDocumentTypeService().findById(this.docTypeParentId);
639    }
640
641    public Collection<DocumentTypePolicy> getDocumentTypePolicies() {
642        return documentTypePolicies;
643    }
644
645    public void setDocumentTypePolicies(Collection<DocumentTypePolicy> policies) {
646        this.documentTypePolicies = policies;
647    }
648    
649    @Override
650    public Map<org.kuali.rice.kew.api.doctype.DocumentTypePolicy, String> getPolicies() {
651        Map<org.kuali.rice.kew.api.doctype.DocumentTypePolicy, String> policies = new HashMap<org.kuali.rice.kew.api.doctype.DocumentTypePolicy, String>();
652        if (this.documentTypePolicies != null) {
653            for (DocumentTypePolicy policy : this.documentTypePolicies) {
654                policies.put(fromCode(policy.getPolicyName()), policy.getActualPolicyValue());
655            }
656        }
657        return policies;
658    }
659
660    public List<ApplicationDocumentStatus> getValidApplicationStatuses()  {
661        if((this.validApplicationStatuses == null || this.validApplicationStatuses.isEmpty())
662                && getParentDocType() != null && isAppDocStatusInUse()) {
663            return getParentDocType().getValidApplicationStatuses();
664       }
665        return this.validApplicationStatuses;
666    }
667
668    public void setValidApplicationStatuses(
669            List<ApplicationDocumentStatus> validApplicationStatuses) {
670        this.validApplicationStatuses = validApplicationStatuses;
671    }
672
673    /**
674     * Get the application document status categories for this document type
675     *
676     * @see ApplicationDocumentStatusCategory
677     * @return the application document status categories for this document type
678     */
679    public List<ApplicationDocumentStatusCategory> getApplicationStatusCategories() {
680        if((this.validApplicationStatuses == null || this.validApplicationStatuses.isEmpty())
681                && KRADUtils.isNotNull(getParentDocType()) && isAppDocStatusInUse()) {
682            return getParentDocType().getApplicationStatusCategories();
683        }
684        return applicationStatusCategories;
685    }
686
687    /**
688     * Set the application document status categories for this document type
689     * @param applicationStatusCategories
690     */
691    public void setApplicationStatusCategories(List<ApplicationDocumentStatusCategory> applicationStatusCategories) {
692        this.applicationStatusCategories = applicationStatusCategories;
693    }
694
695    public String getDocumentTypeSecurityXml() {
696        return documentTypeSecurityXml;
697    }
698
699    public void setDocumentTypeSecurityXml(String documentTypeSecurityXml) {
700        this.documentTypeSecurityXml = documentTypeSecurityXml;
701        if (StringUtils.isNotBlank(documentTypeSecurityXml)) {
702            this.documentTypeSecurity = new DocumentTypeSecurity(this.getApplicationId(), documentTypeSecurityXml);
703        } else {
704            this.documentTypeSecurity = null;
705        }
706    }
707
708    public DocumentTypeSecurity getDocumentTypeSecurity() {
709        if (this.documentTypeSecurity == null &&
710                this.documentTypeSecurityXml != null &&
711                !org.apache.commons.lang.StringUtils.isEmpty(documentTypeSecurityXml.trim())) {
712            this.documentTypeSecurity = new DocumentTypeSecurity(this.getApplicationId(), documentTypeSecurityXml);
713        }
714        if ((this.documentTypeSecurity == null) && (getParentDocType() != null)) {
715            return getParentDocType().getDocumentTypeSecurity();
716        }
717        return this.documentTypeSecurity;
718    }
719
720    public String getActionsUrl() {
721        return actionsUrl;
722    }
723
724    public void setActionsUrl(String actions) {
725        this.actionsUrl = actions;
726    }
727
728    public Boolean getActive() {
729        return active;
730    }
731
732    public void setActive(java.lang.Boolean activeInd) {
733        this.active = activeInd;
734    }
735
736    public java.lang.Boolean getCurrentInd() {
737        return currentInd;
738    }
739    
740    @Override
741    public boolean isCurrent() {
742        if (currentInd == null) {
743            return true;
744        }
745        return currentInd.booleanValue();
746    }
747
748    public void setCurrentInd(java.lang.Boolean currentInd) {
749        this.currentInd = currentInd;
750    }
751
752    public java.lang.String getDescription() {
753        return description;
754    }
755
756    public void setDescription(java.lang.String description) {
757        this.description = description;
758    }
759
760    /**
761     * This method retrieves the unresolved document handler URL from this object
762     */
763    /*public String getDocHandlerUrl() {
764        return getUnresolvedDocHandlerUrl();
765    }*/
766    
767    /**
768     * This method gets the document handler url from this object or from a parent document type and resolves any
769     * potential variables that may be in use
770     */
771    @Override
772    public String getResolvedDocumentHandlerUrl() {
773        return resolveDocHandlerUrl(getUnresolvedInheritedDocHandlerUrl(false));
774    }
775
776    /**
777     * This method retrieves the unresolved document handler URL either from this object or from a parent document type
778     * object. If the forDisplayPurposes value is true the value returned will be invalid for system use.
779     * <p/>
780     * This method will first call the {@link #getUnresolvedDocHandlerUrl()} method to check for a value on this object.
781     * If none is found a parent document type must exist because the document handler URL is required and is used. The
782     * system will use inheritance to find the document handler url from a document type somewhere in the hierarchy.
783     *
784     * @param forDisplayPurposes - if true then the string returned will have a label explaining where the value came from
785     * @return the unresolved document handler URL value or a displayable value with sourcing information
786     */
787    protected String getUnresolvedInheritedDocHandlerUrl(boolean forDisplayPurposes) {
788        if (StringUtils.isNotBlank(getUnresolvedDocHandlerUrl())) {
789            // this object has a direct value set, so return it
790            return getUnresolvedDocHandlerUrl();
791        }
792        // check for a parent document to see if the doc handler url can be inherited
793        DocumentType docType = getParentDocType();
794        if (KRADUtils.isNotNull(docType)) {
795            String parentValue = docType.getUnresolvedDocHandlerUrl();
796            if (StringUtils.isNotBlank(parentValue)) {
797                // found a parent value set on the immediate parent object so return it
798                if (forDisplayPurposes) {
799                    parentValue += " " + KewApiConstants.DOCUMENT_TYPE_INHERITED_VALUE_INDICATOR;
800                }
801                return parentValue;
802            }
803            // no valid value exists on the immediate parent, so check the hierarchy
804            return docType.getUnresolvedInheritedDocHandlerUrl(forDisplayPurposes);
805        }
806        return null;
807    }
808
809    /**
810     * Returns the same value as the {@link #getUnresolvedInheritedDocHandlerUrl(boolean)} method but will also have label
811     * information about whether the value returned came from this object or the parent document type associated with this object
812     */
813    public String getDisplayableUnresolvedDocHandlerUrl() {
814        return getUnresolvedInheritedDocHandlerUrl(true);
815    }
816
817    /**
818     * EMPTY METHOD. Use {@link #setUnresolvedDocHandlerUrl(String)} instead.
819     *
820     * @deprecated
821     */
822    public void setDisplayableUnresolvedDocHandlerUrl(String displayableUnresolvedDocHandlerUrl) {
823        // do nothing
824    }
825
826    /**
827     * @return the unresolvedDocHandlerUrl
828     */
829    @Override
830    public String getUnresolvedDocHandlerUrl() {
831        return this.unresolvedDocHandlerUrl;
832    }
833
834    /**
835     * @param unresolvedDocHandlerUrl the unresolvedDocHandlerUrl to set
836     */
837    public void setUnresolvedDocHandlerUrl(String unresolvedDocHandlerUrl) {
838        this.unresolvedDocHandlerUrl = unresolvedDocHandlerUrl;
839    }
840
841    /**
842     * If the doc handler URL has variables in it that need to be replaced, this will look up the values
843     * for those variables and replace them in the doc handler URL.
844     */
845    protected String resolveDocHandlerUrl(String docHandlerUrl) {
846        if (StringUtils.isBlank(docHandlerUrl)) {
847            return "";
848        }
849        return Utilities.substituteConfigParameters(getApplicationId(), docHandlerUrl);
850    }
851
852    /**
853     * Use {@link #setDocHandlerUrl(String)} to add a document handler url to this object.
854     *
855     * @deprecated
856     */
857    public void setDocHandlerUrl(java.lang.String docHandlerUrl) {
858        setUnresolvedDocHandlerUrl(docHandlerUrl);
859    }
860
861    /**
862     * @return the unresolvedHelpDefinitionUrl
863     */
864    public String getUnresolvedHelpDefinitionUrl() {
865        return this.unresolvedHelpDefinitionUrl;
866    }
867
868    /**
869     * @param unresolvedHelpDefinitionUrl the unresolvedHelpDefinitionUrl to set
870     */
871    public void setUnresolvedHelpDefinitionUrl(String unresolvedHelpDefinitionUrl) {
872        this.unresolvedHelpDefinitionUrl = unresolvedHelpDefinitionUrl;
873    }
874
875    /**
876     * This method gets the help definition url from this object and resolves any
877     * potential variables that may be in use
878     */
879    public String getHelpDefinitionUrl() {
880        return resolveHelpUrl(getUnresolvedHelpDefinitionUrl());
881    }
882
883    /**
884     * If a help URL has variables in it that need to be replaced, this will look up the values
885     * for those variables and replace them.
886     */
887    protected String resolveHelpUrl(String helpDefinitionUrl) {
888        if (StringUtils.isBlank(helpDefinitionUrl)) {
889            return "";
890        }
891        return Utilities.substituteConfigParameters(helpDefinitionUrl);
892    }
893
894    /**
895     * @return the unresolvedDocSearchHelpUrl
896     */
897    public String getUnresolvedDocSearchHelpUrl() {
898        return this.unresolvedDocSearchHelpUrl;
899    }
900
901    /**
902     * @param unresolvedDocSearchHelpUrl the unresolvedDocSearchHelpUrl to set
903     */
904    public void setUnresolvedDocSearchHelpUrl(String unresolvedDocSearchHelpUrl) {
905        this.unresolvedDocSearchHelpUrl = unresolvedDocSearchHelpUrl;
906    }
907
908    /**
909     * This method gets the doc search help url from this object and resolves any
910     * potential variables that may be in use
911     */
912    public String getDocSearchHelpUrl() {
913        return resolveHelpUrl(getUnresolvedDocSearchHelpUrl());
914    }
915
916    public java.lang.String getLabel() {
917        return label;
918    }
919
920    public void setLabel(java.lang.String label) {
921        this.label = label;
922    }
923
924    public java.lang.String getName() {
925        return name;
926    }
927
928    public void setName(java.lang.String name) {
929        this.name = name;
930    }
931
932    public PostProcessor getPostProcessor() {
933        String pname = getPostProcessorName();
934
935        if (StringUtils.equals(pname, KewApiConstants.POST_PROCESSOR_NON_DEFINED_VALUE)) {
936            return new DefaultPostProcessor();
937        }
938        if (StringUtils.isBlank(pname)) {
939            if (getParentDocType() != null) {
940                return getParentDocType().getPostProcessor();
941            } else {
942                return new DefaultPostProcessor();
943            }
944        }
945
946        ObjectDefinition objDef = getObjectDefinition(pname);
947        Object postProcessor = GlobalResourceLoader.getObject(objDef);
948
949        if (postProcessor == null) {
950            throw new WorkflowRuntimeException("Could not locate PostProcessor in this JVM or at application id " + getApplicationId() + ": " + pname);
951        }
952
953        // TODO: KULRICE-5572 Determine whether it is safe to wrap all post processors in a fresh GlobalVariables context
954        //return (PostProcessor) Proxy.newProxyInstance(postProcessor.getClass().getClassLoader(), new Class[] { PostProcessor.class }, new GlobalVariablesContextInvocationHandler(postProcessor));
955        return (PostProcessor) postProcessor;
956    }
957
958    /**
959     * This method gets the post processor class value. If the forDisplayPurposes value is true
960     * the value will be invalid for system use.
961     * <p/>
962     * This method will first call the {@link #getPostProcessorName()} method to check the value on this object.
963     * If none is found the system checks for a parent document type.  If a valid parent type exists for this document type
964     * then the system will use inheritance from that parent document type as long as at least one document type in the
965     * hierarchy has a value set.  If no value is set on any parent document type or if no parent document type exists the
966     * system will return null.
967     *
968     * @param forDisplayPurposes - if true then the string returned will have a label explaining where the value came from
969     * @return the post processor class value or a displayable value with sourcing information
970     */
971    protected String getInheritedPostProcessorName(boolean forDisplayPurposes) {
972        if (StringUtils.isNotBlank(getPostProcessorName())) {
973            // this object has a post processor class so return it
974            return getPostProcessorName();
975        }
976        if (KRADUtils.isNotNull(getParentDocType())) {
977            // direct parent document type exists
978            String parentValue = getParentDocType().getPostProcessorName();
979            if (StringUtils.isNotBlank(parentValue)) {
980                // found a post processor class set on the immediate parent object so return it
981                if (forDisplayPurposes) {
982                    parentValue += " " + KewApiConstants.DOCUMENT_TYPE_INHERITED_VALUE_INDICATOR;
983                }
984                return parentValue;
985            }
986            // did not find a valid value on the immediate parent, so use hierarchy
987            return getParentDocType().getInheritedPostProcessorName(forDisplayPurposes);
988        }
989        return null;
990    }
991
992    public java.lang.String getPostProcessorName() {
993        return postProcessorName;
994    }
995
996    public void setPostProcessorName(java.lang.String postProcessorName) {
997        this.postProcessorName = postProcessorName;
998    }
999
1000    public String getDisplayablePostProcessorName() {
1001        return getInheritedPostProcessorName(true);
1002    }
1003
1004    /**
1005     * EMPTY METHOD. Use {@link #setPostProcessorName(String)} instead.
1006     *
1007     * @deprecated
1008     */
1009    public void setDisplayablePostProcessorName(String displayablePostProcessorName) {
1010        // do nothing
1011    }
1012
1013    public String getPreviousVersionId() {
1014        return previousVersionId;
1015    }
1016
1017    public void setPreviousVersionId(String previousVersionId) {
1018        this.previousVersionId = previousVersionId;
1019    }
1020
1021    public java.lang.String getDocumentId() {
1022        return documentId;
1023    }
1024
1025    public void setDocumentId(java.lang.String documentId) {
1026        this.documentId = documentId;
1027    }
1028
1029    public java.lang.Integer getVersion() {
1030        return version;
1031    }
1032
1033    public void setVersion(java.lang.Integer version) {
1034        this.version = version;
1035    }
1036
1037    public String getDocumentTypeId() {
1038        return documentTypeId;
1039    }
1040
1041    public void setDocumentTypeId(String docTypeGrpId) {
1042        this.documentTypeId = docTypeGrpId;
1043    }
1044    
1045    @Override
1046    public String getId() {
1047        return getDocumentTypeId();
1048    }
1049
1050    public java.lang.String getReturnUrl() {
1051        return returnUrl;
1052    }
1053
1054    public void setReturnUrl(java.lang.String returnUrl) {
1055        this.returnUrl = returnUrl;
1056    }
1057
1058    /**
1059     * Returns the policy value of the specified policy, consulting parent document type definitions
1060     * if not defined on the immediate DocumentType.  If not found, a policy with the specified default
1061     * value is returned.  If policy is found on parent but boolean value is undefined, TRUE is used.
1062     * @param policyName the policy name to look up
1063     * @param defaultValue the default boolean value to return if policy is not found
1064     * @return DocumenTypePolicy defined on immediate or parent document types, or new instance initialized with
1065     *         specified default boolean value
1066     */
1067    public DocumentTypePolicy getPolicyByName(String policyName, Boolean defaultValue) {
1068
1069        Iterator policyIter = getDocumentTypePolicies().iterator();
1070        while (policyIter.hasNext()) {
1071            DocumentTypePolicy policy = (DocumentTypePolicy) policyIter.next();
1072            if (policyName.equals(policy.getPolicyName())) {
1073                policy.setInheritedFlag(Boolean.FALSE);
1074                return policy;
1075            }
1076        }
1077
1078        if (getParentDocType() != null) {
1079            DocumentTypePolicy policy = getParentDocType().getPolicyByName(policyName, defaultValue);
1080            policy.setInheritedFlag(Boolean.TRUE);
1081            if (policy.getPolicyValue() == null) {
1082                policy.setPolicyValue(Boolean.TRUE);
1083            }
1084            return policy;
1085        }
1086        DocumentTypePolicy policy = new DocumentTypePolicy();
1087        policy.setPolicyName(policyName);
1088        policy.setInheritedFlag(Boolean.FALSE);
1089        policy.setPolicyValue(defaultValue);
1090        return policy;
1091    }
1092
1093    /**
1094     * Returns the policy value of the specified policy, consulting parent document type definitions
1095     * if not defined on the immediate DocumentType.  If not found, a policy with a boolean value of True
1096     * and a string value of the specified default value is returned.
1097     * If policy is found on parent but boolean value is undefined, TRUE is used.
1098     * @param policyName the policy name to look up
1099     * @param defaultValue the default string value to return if policy is not found
1100     * @return DocumenTypePolicy defined on immediate or parent document types, or new instance initialized with
1101     *         specified default string value
1102     */
1103    public DocumentTypePolicy getPolicyByName(String policyName, String defaultValue) {
1104
1105        Iterator policyIter = getDocumentTypePolicies().iterator();
1106        while (policyIter.hasNext()) {
1107            DocumentTypePolicy policy = (DocumentTypePolicy) policyIter.next();
1108            if (policyName.equals(policy.getPolicyName())) {
1109                policy.setInheritedFlag(Boolean.FALSE);
1110                return policy;
1111            }
1112        }
1113
1114        if (getParentDocType() != null) {
1115            DocumentTypePolicy policy = getParentDocType().getPolicyByName(policyName, defaultValue);
1116            policy.setInheritedFlag(Boolean.TRUE);
1117            if (policy.getPolicyValue() == null) {
1118                policy.setPolicyValue(Boolean.TRUE);
1119            }
1120            return policy;
1121        }
1122        DocumentTypePolicy policy = new DocumentTypePolicy();
1123        policy.setPolicyName(policyName);
1124        policy.setInheritedFlag(Boolean.FALSE);
1125        policy.setPolicyValue(Boolean.TRUE);
1126        policy.setPolicyStringValue(defaultValue);
1127        return policy;
1128    }
1129
1130    private DocumentTypeService getDocumentTypeService() {
1131        return (DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE);
1132    }
1133
1134    public Group getSuperUserWorkgroup() {
1135        Group superUserWorkgroup = getSuperUserWorkgroupNoInheritence();
1136        if (superUserWorkgroup == null && getParentDocType() != null) {
1137            return getParentDocType().getSuperUserWorkgroup();
1138        }
1139        return superUserWorkgroup;
1140    }
1141
1142    public Group getSuperUserWorkgroupNoInheritence() {
1143        if (workgroupId == null) {
1144            return null;
1145        }
1146        return getGroupService().getGroup(this.workgroupId);
1147    }
1148
1149    public void setSuperUserWorkgroupNoInheritence(Group suWorkgroup) {
1150        this.workgroupId = null;
1151        if (KRADUtils.isNotNull(suWorkgroup)) {
1152            this.workgroupId = suWorkgroup.getId();
1153        }
1154    }
1155
1156    /**
1157     * Set the immediate super user workgroup id field
1158     * @param suWorkgroupId the super user workgroup id
1159     */
1160    public void setSuperUserWorkgroupIdNoInheritence(String suWorkgroupId) {
1161        this.workgroupId = suWorkgroupId;
1162    }
1163
1164    /**
1165     * Returns true if this DocumentType has a super user group defined.
1166     */
1167    public boolean isSuperUserGroupDefined() {
1168        if (this.workgroupId == null) {
1169            return getParentDocType() != null && getParentDocType().isSuperUserGroupDefined();
1170        }
1171        return true;
1172    }
1173
1174    public DocumentType getPreviousVersion() {
1175        return getDocumentTypeService().findById(previousVersionId);
1176    }
1177
1178    public Group getBlanketApproveWorkgroup() {
1179        if (StringUtils.isBlank(blanketApproveWorkgroupId)) {
1180            return null;
1181        }
1182        return getGroupService().getGroup(blanketApproveWorkgroupId);
1183    }
1184
1185    public void setBlanketApproveWorkgroup(Group blanketApproveWorkgroup) {
1186        this.blanketApproveWorkgroupId = null;
1187        if (KRADUtils.isNotNull(blanketApproveWorkgroup)) {
1188            this.blanketApproveWorkgroupId = blanketApproveWorkgroup.getId();
1189        }
1190    }
1191
1192    public String getBlanketApprovePolicy() {
1193        return this.blanketApprovePolicy;
1194    }
1195
1196    public void setBlanketApprovePolicy(String blanketApprovePolicy) {
1197        this.blanketApprovePolicy = blanketApprovePolicy;
1198    }
1199
1200    public Group getBlanketApproveWorkgroupWithInheritance() {
1201        if (getParentDocType() != null && this.blanketApproveWorkgroupId == null) {
1202            return getParentDocType().getBlanketApproveWorkgroupWithInheritance();
1203        } else if(this.blanketApproveWorkgroupId != null) {
1204            return getGroupService().getGroup(blanketApproveWorkgroupId);
1205        }
1206        return null;
1207    }
1208
1209    public boolean isBlanketApprover(String principalId) {
1210        if (KewApiConstants.DOCUMENT_TYPE_BLANKET_APPROVE_POLICY_NONE.equalsIgnoreCase(getBlanketApprovePolicy())) {
1211            // no one can blanket approve this doc type
1212            return false;
1213        } else if (KewApiConstants.DOCUMENT_TYPE_BLANKET_APPROVE_POLICY_ANY.equalsIgnoreCase(getBlanketApprovePolicy())) {
1214            // anyone can blanket approve this doc type
1215            return true;
1216        }
1217        if (blanketApproveWorkgroupId != null) {
1218            return getGroupService().isMemberOfGroup(principalId, blanketApproveWorkgroupId);
1219        }
1220        DocumentType parentDoc = getParentDocType();
1221        if (parentDoc != null) {
1222            // found parent doc so try to get blanket approver info from it
1223            return parentDoc.isBlanketApprover(principalId);
1224        }
1225        return false;
1226    }
1227
1228    /**
1229     * Returns true if either a blanket approve group or blanket approve policy is defined
1230     * on this Document Type.
1231     */
1232    public boolean isBlanketApproveGroupDefined() {
1233        if (StringUtils.isBlank(getBlanketApprovePolicy()) && this.blanketApproveWorkgroupId == null) {
1234            return getParentDocType() != null && getParentDocType().isBlanketApproveGroupDefined();
1235        }
1236        return true;
1237    }
1238
1239    /**
1240     * @return the reportingWorkgroupId
1241     */
1242    public String getReportingWorkgroupId() {
1243        return this.reportingWorkgroupId;
1244    }
1245
1246    /**
1247     * @param reportingWorkgroupId the reportingWorkgroupId to set
1248     */
1249    public void setReportingWorkgroupId(String reportingWorkgroupId) {
1250        this.reportingWorkgroupId = reportingWorkgroupId;
1251    }
1252
1253    public Group getReportingWorkgroup() {
1254        if (StringUtils.isBlank(this.reportingWorkgroupId)) {
1255                return null;
1256        }
1257        return getGroupService().getGroup(this.reportingWorkgroupId);
1258    }
1259
1260    public void setReportingWorkgroup(Group reportingWorkgroup) {
1261        this.reportingWorkgroupId = null;
1262        if (KRADUtils.isNotNull(reportingWorkgroup)) {
1263            this.reportingWorkgroupId = reportingWorkgroup.getId();
1264        }
1265    }
1266
1267    public Group getDefaultExceptionWorkgroup() {
1268        return defaultExceptionWorkgroup;
1269    }
1270
1271    public void setDefaultExceptionWorkgroup(Group defaultExceptionWorkgroup) {
1272        this.defaultExceptionWorkgroup = defaultExceptionWorkgroup;
1273    }
1274
1275    public CustomActionListAttribute getCustomActionListAttribute() throws ResourceUnavailableException {
1276        CustomActionListAttribute result = null;
1277        RuleAttribute customActionListRuleAttribute = getCustomActionListRuleAttribute();
1278
1279        if (customActionListRuleAttribute != null) {
1280            try {
1281                ExtensionDefinition extensionDefinition =
1282                        KewApiServiceLocator.getExtensionRepositoryService().getExtensionById(customActionListRuleAttribute.getId());
1283
1284                if (extensionDefinition != null) {
1285                    result = ExtensionUtils.loadExtension(extensionDefinition, customActionListRuleAttribute.getApplicationId());
1286                } else {
1287                    LOG.warn("Could not load ExtensionDefinition for " + customActionListRuleAttribute);
1288                }
1289
1290            } catch (RiceRemoteServiceConnectionException e) {
1291                LOG.warn("Unable to connect to load custom action list attribute for " + customActionListRuleAttribute, e);
1292            }
1293        }
1294
1295        return result;
1296    }
1297
1298    public CustomEmailAttribute getCustomEmailAttribute() throws ResourceUnavailableException {
1299        ObjectDefinition objDef = getAttributeObjectDefinition(KewApiConstants.EMAIL_ATTRIBUTE_TYPE);
1300        if (objDef == null) {
1301            return null;
1302        }
1303        return (CustomEmailAttribute) GlobalResourceLoader.getObject(objDef);
1304    }
1305
1306    public ObjectDefinition getAttributeObjectDefinition(String typeCode) {
1307        for (Iterator iter = getDocumentTypeAttributes().iterator(); iter.hasNext();) {
1308            RuleAttribute attribute = ((DocumentTypeAttributeBo) iter.next()).getRuleAttribute();
1309            if (attribute.getType().equals(typeCode)) {
1310                return getAttributeObjectDefinition(attribute);
1311            }
1312        }
1313        if (getParentDocType() != null) {
1314            return getParentDocType().getAttributeObjectDefinition(typeCode);
1315        }
1316        return null;
1317    }
1318
1319    public ObjectDefinition getAttributeObjectDefinition(RuleAttribute ruleAttribute) {
1320        if (ruleAttribute.getApplicationId() == null) {
1321            return new ObjectDefinition(ruleAttribute.getResourceDescriptor(), this.getApplicationId());
1322        } else {
1323            return new ObjectDefinition(ruleAttribute.getResourceDescriptor(), ruleAttribute.getApplicationId());
1324        }
1325    }
1326
1327    public CustomNoteAttribute getCustomNoteAttribute() throws ResourceUnavailableException {
1328        ObjectDefinition objDef = getAttributeObjectDefinition(KewApiConstants.NOTE_ATTRIBUTE_TYPE);
1329        if (objDef == null) {
1330            String defaultNoteClass = ConfigContext.getCurrentContextConfig().getDefaultKewNoteClass();
1331            if (defaultNoteClass == null) {
1332                // attempt to use deprecated parameter
1333                defaultNoteClass = ConfigContext.getCurrentContextConfig().getDefaultKewNoteClass();
1334                if (defaultNoteClass == null) {
1335                    return null;
1336                }
1337            }
1338            objDef = new ObjectDefinition(defaultNoteClass);
1339        }
1340        return (CustomNoteAttribute) GlobalResourceLoader.getObject(objDef);
1341    }
1342
1343    public ObjectDefinition getObjectDefinition(String objectName) {
1344        return new ObjectDefinition(objectName, getApplicationId());
1345    }
1346
1347    /**
1348     * Returns true if this document type defines it's own routing, false if it inherits its routing
1349     * from a parent document type.
1350     */
1351    public boolean isRouteInherited() {
1352        return processes.isEmpty() && getParentDocType() != null;
1353    }
1354
1355    /**
1356     * Returns the DocumentType which defines the route for this document.  This is the DocumentType
1357     * from which we inherit our Processes which define our routing.
1358     */
1359    public DocumentType getRouteDefiningDocumentType() {
1360        if (isRouteInherited()) {
1361            return getParentDocType().getRouteDefiningDocumentType();
1362        }
1363        return this;
1364    }
1365
1366    public boolean isDocTypeActive() {
1367        if (!getActive().booleanValue()) {
1368            return false;
1369        }
1370        if (getParentDocType() != null) {
1371            if (!getParentActiveInd(getParentDocType())) {
1372                return false;
1373            }
1374        }
1375        return true;
1376    }
1377
1378    private boolean getParentActiveInd(DocumentType parentDocType) {
1379        if (parentDocType.getActive() == null || parentDocType.getActive().booleanValue()) {
1380            if (parentDocType.getParentDocType() != null) {
1381                return getParentActiveInd(parentDocType.getParentDocType());
1382            }
1383            return true;
1384        } else {
1385            return false;
1386        }
1387    }
1388
1389    /**
1390     * @param documentTypeAttributes The documentTypeAttributes to set.
1391     */
1392    public void setDocumentTypeAttributes(List<DocumentTypeAttributeBo> documentTypeAttributes) {
1393        this.documentTypeAttributes = documentTypeAttributes;
1394    }
1395
1396    /**
1397     * @return Returns the documentTypeAttributes.
1398     */
1399    public List<DocumentTypeAttributeBo> getDocumentTypeAttributes() {
1400        return documentTypeAttributes;
1401    }
1402
1403    public void addProcess(ProcessDefinitionBo process) {
1404        processes.add(process);
1405    }
1406
1407    /**
1408     * Gets the processes of this document by checking locally for processes, and if none are
1409     * present, retrieves them from it's parent document type.  The list returned is an immutable
1410     * list.  To add processes to a document type, use the addProcess method.
1411     * <p/>
1412     * NOTE: Since OJB uses direct field access, this will not interfere with the proper
1413     * mapping of the processes field.
1414     *
1415     * @return
1416     */
1417    public List getProcesses() {
1418        if (processes.isEmpty() && getParentDocType() != null) {
1419            return getParentProcesses(getParentDocType());
1420        }
1421        return Collections.unmodifiableList(processes);
1422    }
1423
1424    public void setProcesses(List routeNodes) {
1425        this.processes = routeNodes;
1426    }
1427
1428    private List getParentProcesses(DocumentType parent) {
1429        List parentProcesses = parent.getProcesses();
1430        if (parentProcesses == null) {
1431            parentProcesses = getParentProcesses(parent.getParentDocType());
1432        }
1433        return parentProcesses;
1434    }
1435
1436    public ProcessDefinitionBo getPrimaryProcess() {
1437        for (Iterator iterator = getProcesses().iterator(); iterator.hasNext();) {
1438            ProcessDefinitionBo process = (ProcessDefinitionBo) iterator.next();
1439            if (process.isInitial()) {
1440                return process;
1441            }
1442        }
1443        return null;
1444    }
1445
1446    public ProcessDefinitionBo getNamedProcess(String name) {
1447        for (Iterator iterator = getProcesses().iterator(); iterator.hasNext();) {
1448            ProcessDefinitionBo process = (ProcessDefinitionBo) iterator.next();
1449            if (org.apache.commons.lang.ObjectUtils.equals(name, process.getName())) {
1450                return process;
1451            }
1452        }
1453        return null;
1454    }
1455
1456    public String getRoutingVersion() {
1457        return routingVersion;
1458    }
1459
1460    public void setRoutingVersion(String routingVersion) {
1461        this.routingVersion = routingVersion;
1462    }
1463
1464    /**
1465     * @return the actualNotificationFromAddress
1466     */
1467    public String getActualNotificationFromAddress() {
1468        return this.actualNotificationFromAddress;
1469    }
1470
1471    /**
1472     * @param actualNotificationFromAddress the actualNotificationFromAddress to set
1473     */
1474    public void setActualNotificationFromAddress(String actualNotificationFromAddress) {
1475        this.actualNotificationFromAddress = actualNotificationFromAddress;
1476    }
1477
1478    /**
1479     * Returns the same value as the {@link #getNotificationFromAddress()} method but will also have label information if
1480     * the value is inherited from a parent document type
1481     */
1482    public String getDisplayableNotificationFromAddress() {
1483        return getNotificationFromAddress(true);
1484    }
1485
1486    /**
1487     * EMPTY METHOD. Use {@link #setActualNotificationFromAddress(String)} instead.
1488     *
1489     * @deprecated
1490     */
1491    public void setDisplayableNotificationFromAddress(String displayableNotificationFromAddress) {
1492        // do nothing
1493    }
1494
1495    public String getNotificationFromAddress() {
1496        return getNotificationFromAddress(false);
1497    }
1498
1499    /**
1500     * This method gets the notification from address value. If the forDisplayPurposes value is true
1501     * the notification from address value will be invalid for system use
1502     * <p/>
1503     * This method will first call the {@link #getActualNotificationFromAddress()} method to check the value on this object.
1504     * If none is found the system checks for a parent document type.  If a valid parent type exists for this document type
1505     * then the system will use inheritance from that parent document type as long as at least one document type in the
1506     * hierarchy has a value set.  If no value is set on any parent document type or if no parent document type exists the
1507     * system will return null
1508     *
1509     * @param forDisplayPurposes - if true then the string returned will have a label explaining where the value came from
1510     * @return the notification from address value or a displayable value with sourcing information
1511     */
1512    protected String getNotificationFromAddress(boolean forDisplayPurposes) {
1513        if (StringUtils.isNotBlank(getActualNotificationFromAddress())) {
1514            // this object has an address so return it
1515            return getActualNotificationFromAddress();
1516        }
1517        if (KRADUtils.isNotNull(getParentDocType())) {
1518            // direct parent document type exists
1519            String parentNotificationFromAddress = getParentDocType().getActualNotificationFromAddress();
1520            if (StringUtils.isNotBlank(parentNotificationFromAddress)) {
1521                // found an address set on the immediate parent object so return it
1522                if (forDisplayPurposes) {
1523                    parentNotificationFromAddress += " " + KewApiConstants.DOCUMENT_TYPE_INHERITED_VALUE_INDICATOR;
1524                }
1525                return parentNotificationFromAddress;
1526            }
1527            // did not find a valid address on the immediate parent to use hierarchy
1528            return getParentDocType().getNotificationFromAddress(forDisplayPurposes);
1529        }
1530        return null;
1531    }
1532
1533    /**
1534     * Use {@link #setActualNotificationFromAddress(String)} instead
1535     *
1536     * @deprecated
1537     */
1538    public void setNotificationFromAddress(String notificationFromAddress) {
1539        setActualNotificationFromAddress(notificationFromAddress);
1540    }
1541
1542    public boolean isParentOf(DocumentType documentType) {
1543        // this is a depth-first search which works for our needs
1544        for (Iterator iterator = getChildrenDocTypes().iterator(); iterator.hasNext();) {
1545            DocumentType child = (DocumentType) iterator.next();
1546            if (child.getName().equals(documentType.getName()) || child.isParentOf(documentType)) {
1547                return true;
1548            }
1549        }
1550        return false;
1551    }
1552
1553    /**
1554     * this exists because the lookup wants to make a call on a bean method when displaying results and those calls are
1555     * entered programatically into the framework by method name
1556     *
1557     * @return
1558     */
1559    public String getLookupParentName() {
1560        DocumentType parent = getParentDocType();
1561        if (parent == null) {
1562            return "Root";
1563        }
1564        return parent.getName();
1565    }
1566
1567    public boolean isSuperUser(String principalId) {
1568        Group workgroup = getSuperUserWorkgroup();
1569        if (workgroup == null) {
1570            return false;
1571        }
1572        return getGroupService().isMemberOfGroup(principalId, workgroup.getId());
1573    }
1574
1575    public boolean hasPreviousVersion() {
1576        if (this.documentTypeId == null) {
1577            return false;
1578        }
1579        return !this.documentTypeId.equals(this.previousVersionId);
1580    }
1581
1582    /**
1583     * @return the actual application id
1584     */
1585    public String getActualApplicationId() {
1586        return this.actualApplicationId;
1587    }
1588
1589    /**
1590     * @param actualApplicationId the actualApplicationId to set
1591     */
1592    public void setActualApplicationId(String actualApplicationId) {
1593        this.actualApplicationId = actualApplicationId;
1594    }
1595
1596    /**
1597     * Returns the application id for this DocumentType which can be specified on the document type itself,
1598     * inherited from the parent, or defaulted to the configured application id of the application.
1599     */
1600    public String getApplicationId() {
1601        return getApplicationId(false);
1602    }
1603
1604    /**
1605     * This method gets the string for the application id value. If the forDisplayPurposes value is true
1606     * the application id value will be invalid for system use.
1607     * <p/>
1608     * This method will first call the {@link #getActualApplicationId()} method to check for a value on this object. If
1609     * none is found a parent document type is used.  If a valid parent type exists for this document type then the system
1610     * will use inheritance from that parent document type as long as at least one document type in the hierarchy has a
1611     * value set.  If no value is set on any parent document type or if no parent document type exists for this object the
1612     * system default is used: {@link CoreConfigHelper#getApplicationId()}
1613     *
1614     * @param forDisplayPurposes - if true then the string returned will have a label explaining where the value came from
1615     * @return the application id value or a displayable value with sourcing information
1616     */
1617    protected String getApplicationId(boolean forDisplayPurposes) {
1618        if (StringUtils.isNotBlank(getActualApplicationId())) {
1619            // this object has a application id set, so return it
1620            return getActualApplicationId();
1621        }
1622        // this object has no application id... check for a parent document type
1623        if (KRADUtils.isNotNull(getParentDocType())) {
1624            // direct parent document type exists
1625            String parentValue = getParentDocType().getActualApplicationId();
1626            if (StringUtils.isNotBlank(parentValue)) {
1627                // found a parent value set on the immediate parent object so return it
1628                if (forDisplayPurposes) {
1629                    parentValue += " " + KewApiConstants.DOCUMENT_TYPE_INHERITED_VALUE_INDICATOR;
1630                }
1631                return parentValue;
1632            }
1633            // no valid application id on direct parent, so use hierarchy to find correct value
1634            return getParentDocType().getApplicationId(forDisplayPurposes);
1635        }
1636        String defaultValue = CoreConfigHelper.getApplicationId();
1637        if (forDisplayPurposes) {
1638            defaultValue += " " + KewApiConstants.DOCUMENT_TYPE_SYSTEM_DEFAULT_INDICATOR;
1639        }
1640        return defaultValue;
1641    }
1642
1643    /**
1644     * Returns the same value as the {@link #getApplicationId()} method but will also have label information about
1645     * where the application id came from (ie: inherited from the parent document type)
1646     */
1647    public String getDisplayableApplicationId() {
1648        return getApplicationId(true);
1649    }
1650
1651    /**
1652     * Gets the name of the custom email stylesheet to use to render email (if any has been set, null otherwise)
1653     *
1654     * @return name of the custom email stylesheet to use to render email (if any has been set, null otherwise)
1655     */
1656    public String getCustomEmailStylesheet() {
1657        return customEmailStylesheet;
1658    }
1659
1660    /**
1661     * Sets the name of the custom email stylesheet to use to render email
1662     *
1663     * @return name of the custom email stylesheet to use to render email
1664     */
1665    public void setCustomEmailStylesheet(String customEmailStylesheet) {
1666        this.customEmailStylesheet = customEmailStylesheet;
1667    }
1668
1669    /**
1670     * @return the blanketApproveWorkgroupId
1671     */
1672    public String getBlanketApproveWorkgroupId() {
1673        return this.blanketApproveWorkgroupId;
1674    }
1675
1676
1677    /**
1678     * @param blanketApproveWorkgroupId the blanketApproveWorkgroupId to set
1679     */
1680    public void setBlanketApproveWorkgroupId(String blanketApproveWorkgroupId) {
1681        this.blanketApproveWorkgroupId = blanketApproveWorkgroupId;
1682    }
1683
1684    /**
1685     * @return the applyRetroactively
1686     */
1687    public Boolean getApplyRetroactively() {
1688        return this.applyRetroactively;
1689    }
1690
1691    /**
1692     * @param applyRetroactively the applyRetroactively to set
1693     */
1694    public void setApplyRetroactively(Boolean applyRetroactively) {
1695        this.applyRetroactively = applyRetroactively;
1696    }
1697
1698    private GroupService getGroupService() {
1699        return KimApiServiceLocator.getGroupService();
1700    }
1701
1702    /**
1703     * @see org.kuali.rice.core.api.mo.common.active.MutableInactivatable#isActive()
1704     */
1705    public boolean isActive() {
1706        boolean bRet = false;
1707
1708        if (active != null) {
1709            bRet = active.booleanValue();
1710        }
1711
1712        return bRet;
1713    }
1714
1715    /**
1716     * @see org.kuali.rice.core.api.mo.common.active.MutableInactivatable#setActive(boolean)
1717     */
1718    public void setActive(boolean active) {
1719        this.active = Boolean.valueOf(active);
1720    }
1721    
1722    @Override
1723    public Integer getDocumentTypeVersion() {
1724        return version;
1725    }
1726
1727    @Override
1728    public String getParentId() {
1729        return docTypeParentId;
1730    }
1731
1732    @Override
1733    public String getBlanketApproveGroupId() {
1734        return blanketApproveWorkgroupId;
1735    }
1736
1737    @Override
1738    public String getSuperUserGroupId() {
1739        return workgroupId;
1740    }
1741
1742    @Override
1743    public String getAuthorizer() {
1744        String result = authorizer;
1745
1746        if (StringUtils.isBlank(result)) {
1747            if (getParentDocType() != null) {
1748                return getParentDocType().getAuthorizer();
1749            }
1750        }
1751
1752        return result;
1753    }
1754
1755    public void setAuthorizer(String authorizer) {
1756        this.authorizer = authorizer;
1757    }
1758
1759    public static org.kuali.rice.kew.api.doctype.DocumentType to(DocumentType documentTypeBo) {
1760        if (documentTypeBo == null) {
1761            return null;
1762        }
1763        org.kuali.rice.kew.api.doctype.DocumentType.Builder builder = org.kuali.rice.kew.api.doctype.DocumentType.Builder.create(documentTypeBo);
1764        builder.setApplicationId(documentTypeBo.getActualApplicationId());
1765        return builder.build();
1766    }
1767
1768    public static DocumentType from(org.kuali.rice.kew.api.doctype.DocumentTypeContract dt) {
1769        if (dt == null) return null;
1770
1771        // DocumentType BO and DTO are not symmetric
1772        // set what fields we can
1773        DocumentType ebo = new DocumentType();
1774        //ebo.setActionsUrl();
1775        ebo.setDocumentTypeId(dt.getId());
1776        ebo.setActive(dt.isActive());
1777        ebo.setActualApplicationId(dt.getApplicationId());
1778        //ebo.setActualNotificationFromAddress();
1779        ebo.setBlanketApproveWorkgroupId(dt.getBlanketApproveGroupId());
1780        ebo.setCurrentInd(dt.isCurrent());
1781        ebo.setDescription(dt.getDescription());
1782        ebo.setVersionNumber(dt.getVersionNumber());
1783        ebo.setVersion(dt.getDocumentTypeVersion());
1784        ebo.setUnresolvedDocHandlerUrl(dt.getUnresolvedDocHandlerUrl());
1785        ebo.setUnresolvedDocSearchHelpUrl(dt.getDocSearchHelpUrl());
1786        ebo.setUnresolvedHelpDefinitionUrl(dt.getHelpDefinitionUrl());
1787        ebo.setLabel(dt.getLabel());
1788        ebo.setName(dt.getName());
1789        ebo.setDocTypeParentId(dt.getParentId());
1790        ebo.setPostProcessorName(dt.getPostProcessorName());
1791        ebo.setSuperUserWorkgroupIdNoInheritence(dt.getSuperUserGroupId());
1792        List<DocumentTypePolicy> policies = new ArrayList<DocumentTypePolicy>();
1793        if (dt.getPolicies() != null) {
1794            for (Map.Entry<org.kuali.rice.kew.api.doctype.DocumentTypePolicy, String> entry: dt.getPolicies().entrySet()) {
1795                // NOTE: The policy value is actually a boolean field stored to a Decimal(1) column (although the db column is named PLCY_NM)
1796                // I'm not sure what the string value should be but the BO is simply toString'ing the Boolean value
1797                // so I am assuming here that "true"/"false" are the acceptable values
1798                policies.add(new DocumentTypePolicy(dt.getId(), entry.getKey().getCode(), Boolean.TRUE.toString().equals(
1799                        entry.getValue())));
1800            }
1801        }
1802        if (CollectionUtils.isNotEmpty(dt.getDocumentTypeAttributes())) {
1803            List<DocumentTypeAttributeBo> attributes = new ArrayList<DocumentTypeAttributeBo>();
1804            for (DocumentTypeAttributeContract attr : dt.getDocumentTypeAttributes()) {
1805                attributes.add(DocumentTypeAttributeBo.from(DocumentTypeAttribute.Builder.create(attr).build()));
1806            }
1807            
1808        }
1809        ebo.setDocumentTypePolicies(policies);
1810        ebo.setAuthorizer(dt.getAuthorizer());
1811        return ebo;
1812    }
1813
1814    public String getWorkgroupId() {
1815        return workgroupId;
1816    }
1817
1818    public void setWorkgroupId(String workgroupId) {
1819        this.workgroupId = workgroupId;
1820    }
1821
1822    @Override
1823    public void refresh() {
1824        // do nothing - this is an EBO - and this method is not safe to call on EBO-extending JPA-bytecode-weaved objects
1825        // See org.kuali.rice.krad.service.impl.PersistenceServiceImpl.retrieveNonKeyFields(Object)
1826    }
1827}