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