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