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.krad.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.criteria.Predicate;
020import org.kuali.rice.core.api.criteria.PredicateFactory;
021import org.kuali.rice.core.api.criteria.QueryByCriteria;
022import org.kuali.rice.core.api.criteria.QueryResults;
023import org.kuali.rice.core.api.mo.common.active.Inactivatable;
024import org.kuali.rice.core.api.mo.common.active.MutableInactivatable;
025import org.kuali.rice.krad.bo.BusinessObject;
026import org.kuali.rice.krad.data.DataObjectService;
027import org.kuali.rice.krad.data.DataObjectWrapper;
028import org.kuali.rice.krad.data.KradDataServiceLocator;
029import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
030import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
031import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
032import org.kuali.rice.krad.datadictionary.InactivationBlockingMetadata;
033import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
034import org.kuali.rice.krad.service.DataObjectMetaDataService;
035import org.kuali.rice.krad.service.InactivationBlockingDetectionService;
036import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
037import org.kuali.rice.krad.service.LegacyDataAdapter;
038import org.kuali.rice.krad.util.KRADUtils;
039import org.kuali.rice.krad.util.LegacyUtils;
040import org.springframework.transaction.annotation.Transactional;
041
042import java.util.ArrayList;
043import java.util.Collection;
044import java.util.HashMap;
045import java.util.Iterator;
046import java.util.List;
047import java.util.Map;
048
049/**
050 * Performs checking of inactivation blocking 
051 * 
052 * @author Kuali Rice Team (rice.collab@kuali.org)
053 */
054@Transactional
055public class InactivationBlockingDetectionServiceImpl implements InactivationBlockingDetectionService {
056    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(InactivationBlockingDetectionServiceImpl.class);
057
058    protected DataObjectMetaDataService dataObjectMetaDataService;
059    private volatile DataObjectService dataObjectService;
060    protected LegacyDataAdapter legacyDataAdapter;
061    
062    /**
063     * Note we are checking the active getting after retrieving potential blocking records instead of setting criteria on the
064         * active field. This is because some implementations of {@link org.kuali.rice.core.api.mo.common.active.MutableInactivatable} might not have the active field, for example
065         * instances of {@link org.kuali.rice.krad.bo.InactivatableFromTo}
066         * 
067     * @see org.kuali.rice.krad.service.InactivationBlockingDetectionService#listAllBlockerRecords(org.kuali.rice.krad.bo.BusinessObject, org.kuali.rice.krad.datadictionary.InactivationBlockingMetadata)
068     * @see org.kuali.rice.core.api.mo.common.active.MutableInactivatable
069     */
070    @SuppressWarnings("unchecked")
071    @Deprecated
072    @Override
073        public Collection<BusinessObject> listAllBlockerRecords(BusinessObject blockedBo, InactivationBlockingMetadata inactivationBlockingMetadata) {
074                Collection<BusinessObject> blockingRecords = new ArrayList<BusinessObject>();
075
076                Map<String, String> queryMap = buildInactivationBlockerQueryMap(blockedBo, inactivationBlockingMetadata);
077                if (LOG.isDebugEnabled()) {
078                        LOG.debug("Checking for blocker records for object: " + blockedBo);
079                        LOG.debug("    With Metadata: " + inactivationBlockingMetadata);
080                        LOG.debug("    Resulting Query Map: " + queryMap);
081                }
082
083                if (queryMap != null) {
084                        Collection potentialBlockingRecords = legacyDataAdapter.findMatching(
085                                        inactivationBlockingMetadata.getBlockingReferenceBusinessObjectClass(), queryMap);
086                        for (Iterator iterator = potentialBlockingRecords.iterator(); iterator.hasNext();) {
087                                MutableInactivatable businessObject = (MutableInactivatable) iterator.next();
088                                if (businessObject.isActive()) {
089                                        blockingRecords.add((BusinessObject) businessObject);
090                                }
091                        }
092                }
093
094                return blockingRecords;
095        }
096
097        /**
098         * Note we are checking the active getting after retrieving potential blocking records instead of setting criteria on the
099         * active field. This is because some implementations of {@link org.kuali.rice.core.api.mo.common.active.MutableInactivatable} might not have the active field, for example
100         * instances of {@link org.kuali.rice.krad.bo.InactivatableFromTo}
101         * 
102         * @see org.kuali.rice.krad.service.InactivationBlockingDetectionService#hasABlockingRecord(org.kuali.rice.krad.bo.BusinessObject,
103         *      org.kuali.rice.krad.datadictionary.InactivationBlockingMetadata)
104         * @see org.kuali.rice.core.api.mo.common.active.MutableInactivatable
105         */
106    @Deprecated
107    @Override
108        public boolean hasABlockingRecord(BusinessObject blockedBo, InactivationBlockingMetadata inactivationBlockingMetadata) {
109                boolean hasBlockingRecord = false;
110
111                Map<String, String> queryMap = buildInactivationBlockerQueryMap(blockedBo, inactivationBlockingMetadata);
112                if (queryMap != null) {
113                        Collection potentialBlockingRecords = legacyDataAdapter.findMatching(
114                                        inactivationBlockingMetadata.getBlockingReferenceBusinessObjectClass(), queryMap);
115                        for (Iterator iterator = potentialBlockingRecords.iterator(); iterator.hasNext();) {
116                                MutableInactivatable businessObject = (MutableInactivatable) iterator.next();
117                                if (businessObject.isActive()) {
118                                        hasBlockingRecord = true;
119                                        break;
120                                }
121                        }
122                }
123
124                // if queryMap were null, means that we couldn't perform a query, and hence, need to return false
125                return hasBlockingRecord;
126        }
127
128    @Deprecated
129        protected Map<String, String> buildInactivationBlockerQueryMap(BusinessObject blockedBo, InactivationBlockingMetadata inactivationBlockingMetadata) {
130                BusinessObject blockingBo = (BusinessObject) KRADUtils.createNewObjectFromClass(
131                inactivationBlockingMetadata.getBlockingReferenceBusinessObjectClass());
132
133                org.kuali.rice.krad.bo.DataObjectRelationship dataObjectRelationship = legacyDataAdapter
134                                .getDataObjectRelationship(blockingBo, blockedBo.getClass(),
135                        inactivationBlockingMetadata.getBlockedReferencePropertyName(), "", true, false, false);
136
137        RelationshipDefinition relationshipDefinition = KRADServiceLocatorWeb.getLegacyDataAdapter().getDictionaryRelationship(blockedBo.getClass(),inactivationBlockingMetadata.getBlockedReferencePropertyName());
138
139                // note, this method assumes that all PK fields of the blockedBo have a non-null and, for strings, non-blank values
140                if (dataObjectRelationship != null) {
141                        Map<String, String> parentToChildReferences = dataObjectRelationship.getParentToChildReferences();
142                        Map<String, String> queryMap = new HashMap<String, String>();
143                        for (Map.Entry<String, String> parentToChildReference : parentToChildReferences.entrySet()) {
144                                String fieldName = parentToChildReference.getKey();
145                                Object fieldValue = KradDataServiceLocator.getDataObjectService().wrap(blockedBo).getPropertyValueNullSafe(parentToChildReference.getValue());
146                                if (fieldValue != null && StringUtils.isNotBlank(fieldValue.toString())) {
147                                        queryMap.put(fieldName, fieldValue.toString());
148                                } else {
149                                        LOG.error("Found null value for foreign key field " + fieldName
150                                                        + " while building inactivation blocking query map.");
151                                        throw new RuntimeException("Found null value for foreign key field '" + fieldName
152                                                        + "' while building inactivation blocking query map.");
153                                }
154                        }
155
156                        return queryMap;
157                }
158
159                return null;
160        }
161
162    /**
163     * Implementation which calls the legacy {@link #hasABlockingRecord(org.kuali.rice.krad.bo.BusinessObject, org.kuali.rice.krad.datadictionary.InactivationBlockingMetadata)}
164     * if the given data object is a legacy object. Calls new code to make the equivalent check if the given object is
165     * non-legacy.
166     */
167    @Override
168    public boolean detectBlockingRecord(Object dataObject, InactivationBlockingMetadata inactivationBlockingMetadata) {
169        if (LegacyUtils.useLegacyForObject(dataObject)) {
170            return hasABlockingRecord((BusinessObject)dataObject, inactivationBlockingMetadata);
171        }
172        QueryByCriteria criteria = buildInactivationBlockerCriteria(dataObject, inactivationBlockingMetadata);
173        if (criteria != null) {
174            Class<?> blockingType = inactivationBlockingMetadata.getBlockingDataObjectClass();
175            QueryResults<?> potentialBlockingRecords = getDataObjectService().findMatching(blockingType, criteria);
176            for (Object result : potentialBlockingRecords.getResults()) {
177                if (!(result instanceof Inactivatable)) {
178                    throw new IllegalStateException("Blocking records must implement Inactivatable, but encountered one which does not: " + result);
179                }
180                Inactivatable inactivatable = (Inactivatable)result;
181                if (inactivatable.isActive()) {
182                    return true;
183                }
184            }
185        }
186        // if criteria is null, means that we couldn't perform a query, and hence, need to return false
187        return false;
188    }
189
190    /**
191     * Implementation which calls the legacy {@link #listAllBlockerRecords(org.kuali.rice.krad.bo.BusinessObject, org.kuali.rice.krad.datadictionary.InactivationBlockingMetadata)}
192     * if the given data object is a legacy object. Calls new code to make the equivalent check if the given object is
193     * non-legacy.
194     */
195    @Override
196    public Collection<?> detectAllBlockingRecords(Object dataObject,
197            InactivationBlockingMetadata inactivationBlockingMetadata) {
198        if (LegacyUtils.useLegacyForObject(dataObject)) {
199            return listAllBlockerRecords((BusinessObject) dataObject, inactivationBlockingMetadata);
200        }
201        List<Object> blockingRecords = new ArrayList<Object>();
202
203        QueryByCriteria criteria = buildInactivationBlockerCriteria(dataObject, inactivationBlockingMetadata);
204        if (LOG.isDebugEnabled()) {
205            LOG.debug("Checking for blocker records for object: " + dataObject);
206            LOG.debug("    With Metadata: " + inactivationBlockingMetadata);
207            LOG.debug("    Resulting QueryByCriteria: " + criteria);
208        }
209
210        if (criteria != null) {
211            Class<?> blockingType = inactivationBlockingMetadata.getBlockingDataObjectClass();
212            QueryResults<?> potentialBlockingRecords = getDataObjectService().findMatching(blockingType, criteria);
213            for (Object result : potentialBlockingRecords.getResults()) {
214                if (!(result instanceof Inactivatable)) {
215                    throw new IllegalStateException("Blocking records must implement Inactivatable, but encountered one which does not: " + result);
216                }
217                Inactivatable inactivatable = (Inactivatable)result;
218                if (inactivatable.isActive()) {
219                    blockingRecords.add(result);
220                }
221            }
222        }
223
224        return blockingRecords;
225    }
226
227    protected QueryByCriteria buildInactivationBlockerCriteria(Object blockedObject, InactivationBlockingMetadata inactivationBlockingMetadata) {
228        DataObjectMetadata blockingObjectMetadata =
229                getDataObjectService().getMetadataRepository().getMetadata(inactivationBlockingMetadata.getBlockingDataObjectClass());
230        DataObjectRelationship dataObjectRelationship =
231                blockingObjectMetadata.getRelationship(inactivationBlockingMetadata.getBlockedAttributeName());
232
233        // note, this method assumes that all PK fields of the blockedBo have a non-null and, for strings, non-blank values
234        if (dataObjectRelationship != null && !dataObjectRelationship.getAttributeRelationships().isEmpty()) {
235            DataObjectWrapper<?> wrap = getDataObjectService().wrap(blockedObject);
236            List<Predicate> predicates = new ArrayList<Predicate>();
237            for (DataObjectAttributeRelationship relationship : dataObjectRelationship.getAttributeRelationships()) {
238                String fieldName = relationship.getParentAttributeName();
239                Object fieldValue = wrap.getPropertyValue(relationship.getChildAttributeName());
240                if (fieldValue != null && StringUtils.isNotBlank(fieldValue.toString())) {
241                    predicates.add(PredicateFactory.equal(fieldName, fieldValue));
242                }
243            }
244            return QueryByCriteria.Builder.fromPredicates(predicates.toArray(new Predicate[predicates.size()]));
245        }
246        return null;
247    }
248
249
250    public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
251        this.dataObjectMetaDataService = dataObjectMetaDataService;
252    }
253
254    public void setLegacyDataAdapter(LegacyDataAdapter legacyDataAdapter) {
255        this.legacyDataAdapter = legacyDataAdapter;
256    }
257
258    public DataObjectService getDataObjectService() {
259        if (dataObjectService == null) {
260            dataObjectService = KradDataServiceLocator.getDataObjectService();
261        }
262        return dataObjectService;
263    }
264
265    public void setDataObjectService(DataObjectService dataObjectService) {
266        this.dataObjectService = dataObjectService;
267    }
268}