001package org.kuali.rice.krad.data.provider.impl;
002
003import java.util.Set;
004
005import org.kuali.rice.core.api.criteria.QueryByCriteria;
006import org.kuali.rice.core.api.criteria.QueryResults;
007import org.kuali.rice.krad.data.CompoundKey;
008import org.kuali.rice.krad.data.CopyOption;
009import org.kuali.rice.krad.data.DataObjectService;
010import org.kuali.rice.krad.data.DataObjectWrapper;
011import org.kuali.rice.krad.data.PersistenceOption;
012import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
013import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
014import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
015import org.kuali.rice.krad.data.metadata.MetadataChild;
016import org.kuali.rice.krad.data.metadata.MetadataRepository;
017import org.kuali.rice.krad.data.provider.PersistenceProvider;
018import org.kuali.rice.krad.data.provider.ProviderRegistry;
019import org.kuali.rice.krad.data.util.ReferenceLinker;
020import org.springframework.beans.factory.annotation.Required;
021import org.springframework.dao.IncorrectResultSizeDataAccessException;
022
023import com.google.common.collect.Sets;
024
025/**
026 * Created by sheiksalahudeenm on 10/10/14.
027 *
028 * Overridden for fixing the issue in KRAD Transaction document/JPA with composite primary key.
029 * Modified method name: reduceCompoundKey.
030 * Changes description : Commented the reduceCompoundKey method functionality. Just returning the same object which is given to this method.
031 */
032public class ProviderBasedDataObjectService implements DataObjectService {
033        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
034                        .getLogger(ProviderBasedDataObjectService.class);
035
036    /**
037     * The provider registry.
038     */
039    protected ProviderRegistry providerRegistry;
040
041    /**
042     * The metadata repository.
043     */
044    protected MetadataRepository metadataRepository;
045
046    /**
047     * The reference linker.
048     */
049    protected ReferenceLinker referenceLinker;
050
051    /**
052     * {@inheritDoc}
053     */
054    @Override
055    public <T> T find(Class<T> type, Object id) {
056        return persistenceProviderForType(type).find(type, reduceCompoundKey(id));
057    }
058
059    /**
060     * If the given id object is an instance of CompoundKey but there is only one entry in the key map, then just grab
061     * that single value and treat it as a single id.
062     *
063     * @param id the potentially CompoundKey to reduce
064     * @return the single value from the CompoundKey map if the given id is a CompoundKey with a single entry, otherwise
065     *         the original id that was passed in is returned
066     */
067    protected Object reduceCompoundKey(Object id) {
068        /* Modified by 'Sheik Salahudeen' for fixing the 'workflow document is null' issue in KRAD Transaction document with JPA.
069        * Commented the reduceCompoundKey method functionality. Just returning the same object which is given to this method */
070
071         /*if (id instanceof CompoundKey) {
072            CompoundKey compoundKey = (CompoundKey)id;
073            if (compoundKey.getKeys().size() == 1) {
074                id = compoundKey.getKeys().values().iterator().next();
075            }
076        }*/
077        return id;
078    }
079
080    /**
081     * {@inheritDoc}
082     */
083    @Override
084    public <T> QueryResults<T> findMatching(Class<T> type, QueryByCriteria queryByCriteria) {
085        return persistenceProviderForType(type).findMatching(type, queryByCriteria);
086    }
087
088    /**
089     * {@inheritDoc}
090     */
091    @Override
092    public <T> QueryResults<T> findAll(Class<T> type) {
093        return persistenceProviderForType(type).findAll(type);
094    }
095
096    /**
097     * {@inheritDoc}
098     */
099    @Override
100    public <T> T findUnique(Class<T> type, QueryByCriteria queryByCriteria) {
101        QueryResults<T> results = findMatching(type, queryByCriteria);
102        if (results.getResults().isEmpty()) {
103            return null;
104        } else if (results.getResults().size() > 1) {
105            throw new IncorrectResultSizeDataAccessException("Attempted to find single result but found more than "
106                    + "one for class " + type + " and criteria " + queryByCriteria, 1, results.getResults().size());
107        } else {
108            return results.getResults().get(0);
109        }
110    }
111
112    /**
113     * {@inheritDoc}
114     */
115    @Override
116    public void delete(Object dataObject) {
117        persistenceProviderForObject(dataObject).delete(dataObject);
118    }
119
120    /**
121     * {@inheritDoc}
122     */
123    @Override
124    public <T> void deleteMatching(Class<T> type, QueryByCriteria queryByCriteria) {
125        persistenceProviderForType(type).deleteMatching(type, queryByCriteria);
126    }
127
128    /**
129     *
130     *{@inheritDoc}
131     */
132    @Override
133    public <T> void deleteAll(Class<T> type) {
134        persistenceProviderForType(type).deleteAll(type);
135    }
136
137    /**
138     * {@inheritDoc}
139     */
140    @Override
141        public <T> T save(T dataObject, PersistenceOption... options) {
142        Set<PersistenceOption> optionSet = Sets.newHashSet(options);
143                pushOneToOneKeysToChildObjects(dataObject);
144        T saved = persistenceProviderForObject(dataObject).save(dataObject, options);
145        if (optionSet.contains(PersistenceOption.LINK_KEYS)) {
146            DataObjectWrapper<T> wrapper = wrap(saved);
147            wrapper.linkForeignKeys(true);
148        }
149        return saved;
150    }
151
152        protected void pushOneToOneKeysToChildObjects(Object dataObject) {
153                DataObjectWrapper<Object> wrappedParent = wrap(dataObject);
154                if (wrappedParent.getMetadata() == null) {
155                        return;
156                }
157                // Loop over all relationships
158                for (DataObjectRelationship rel : wrappedParent.getMetadata().getRelationships()) {
159                        // look for those which are part of this object exclusively
160                        if (rel.isSavedWithParent() && rel.isDeletedWithParent()) {
161                                Object child = wrappedParent.getPropertyValueNullSafe(rel.getName());
162                                // if the child is null, just skip
163                                if (child == null) {
164                                        continue;
165                                }
166                                DataObjectWrapper<Object> wrappedChild = wrap(child);
167                                // REMOVED THIS FOR NOW - THE ATTRIBUTES DON'T EXIST IN THIS DIRECTION
168                                // loop over the attributes, setting them on the child object
169                                // for (DataObjectAttributeRelationship attr : rel.getAttributeRelationships()) {
170                                // wrappedChild.setPropertyValue(attr.getChildAttributeName(),
171                                // dataObjectWrapper.getPropertyValueNullSafe(attr.getParentAttributeName()));
172                                // }
173                                // inverse relationship - if it exists, add the parent object in
174                                // the applicable property
175                                MetadataChild inverseRelationship = rel.getInverseRelationship();
176                                if (inverseRelationship != null && inverseRelationship instanceof DataObjectRelationship) {
177                                        try {
178                                                wrappedChild.setPropertyValue(inverseRelationship.getName(), dataObject);
179                                                for (DataObjectAttributeRelationship attr : inverseRelationship.getAttributeRelationships()) {
180                                                        // note the reversal of child and parent - remember this is the *child's*
181                                                        // relationship with the parent
182                                                        // and like many children, the they they are in charge
183                                                        wrappedChild.setPropertyValue(attr.getParentAttributeName(),
184                                                                        wrappedParent.getPropertyValueNullSafe(attr.getChildAttributeName()));
185                                                }
186                                        } catch (Exception ex) {
187                                                LOG.warn("Unable to set 1:1 child keys.  Persistance of child object may not be correct.  Parent Object.property: "
188                                                                + dataObject.getClass().getName()
189                                                                + "."
190                                                                + rel.getName()
191                                                                + " / Child Type: "
192                                                                                + child.getClass().getName(), ex);
193                                        }
194                                }
195                        }
196                }
197
198        }
199
200    /**
201     * {@inheritDoc}
202     */
203    @Override
204    public MetadataRepository getMetadataRepository() {
205        return metadataRepository;
206    }
207
208    /**
209     * {@inheritDoc}
210     */
211    @Override
212        public <T> T copyInstance(T dataObject, CopyOption... options) {
213                return persistenceProviderForObject(dataObject).copyInstance(dataObject, options);
214    }
215
216    /**
217     * {@inheritDoc}
218     */
219    @Override
220    public <T> DataObjectWrapper<T> wrap(T dataObject) {
221        if (dataObject == null) {
222            throw new IllegalArgumentException("data object was null");
223        }
224        DataObjectMetadata metadata = getMetadataRepository().getMetadata(dataObject.getClass());
225        // Checking for metadata and failing here. Otherwise a null gets stored in the wrapper
226        // and most later operations on the object will fail with an NPE.
227        if (metadata == null) {
228            LOG.warn("Non KRAD Data object passed - no metadata found for: " + dataObject.getClass());
229            // throw new IllegalArgumentException("Non KRAD Data object passed - no metadata found for: " +
230            // dataObject.getClass());
231        }
232        return new DataObjectWrapperImpl<T>(dataObject, metadata, this, referenceLinker);
233    }
234
235    /**
236     * {@inheritDoc}
237     */
238    @Override
239    public <T> boolean supports(Class<T> type) {
240        return providerRegistry.getPersistenceProvider(type) != null;
241    }
242
243    /**
244     * Gets the PersistenceProvider returned by the ProviderRegistry for the given type.
245     *
246     * @param type the type for which to get the provider.
247     * @return the PersistenceProvider returned by the ProviderRegistry for the given type.
248     * @throws RuntimeException if not PersistenceProvider handles given type.
249     */
250    protected PersistenceProvider persistenceProviderForType(Class<?> type) {
251        PersistenceProvider provider = providerRegistry.getPersistenceProvider(type);
252        if (provider == null) {
253            throw new RuntimeException("No PersistenceProvider handles type: " + type);
254        }
255        return provider;
256    }
257
258    /**
259     * Gets the PersistenceProvider returned by the ProviderRegistry for the given object.
260     *
261     * @param object the object for which to get the provider.
262     * @return the PersistenceProvider returned by the ProviderRegistry for the given object.
263     * @throws RuntimeException if not PersistenceProvider handles given type.
264     * @throws IllegalArgumentException if null object passed in.
265     */
266    protected PersistenceProvider persistenceProviderForObject(Object object) {
267        if (object == null) {
268            throw new IllegalArgumentException("data object was null");
269        }
270        return persistenceProviderForType(object.getClass());
271    }
272
273    /**
274     * {@inheritDoc}
275     */
276    @Override
277    public void flush(Class<?> type){
278        PersistenceProvider persistenceProvider = persistenceProviderForType(type);
279        if (persistenceProvider == null) {
280            throw new RuntimeException("No PersistenceProvider handles type: " + type);
281        }
282        persistenceProvider.flush(type);
283    }
284
285    /**
286     * Setter for the provider registry.
287     *
288     * @param providerRegistry the provider registry to set.
289     */
290    @Required
291    public void setProviderRegistry(ProviderRegistry providerRegistry) {
292        this.providerRegistry = providerRegistry;
293    }
294
295    /**
296     * Setter for the metadata repository.
297     *
298     * @param metadataRepository the metadata repository to set.
299     */
300    @Required
301    public void setMetadataRepository(MetadataRepository metadataRepository) {
302        this.metadataRepository = metadataRepository;
303    }
304
305    /**
306     * Gets the reference linker.
307     *
308     * @return the reference linker.
309     */
310    public ReferenceLinker getReferenceLinker() {
311        return referenceLinker;
312    }
313
314    /**
315     * Setter for the reference linker.
316     *
317     * @param referenceLinker the reference linker to set.
318     */
319    @Required
320    public void setReferenceLinker(ReferenceLinker referenceLinker) {
321        this.referenceLinker = referenceLinker;
322    }
323
324    /**
325     * Defines a very basic implementation for {@link org.kuali.rice.krad.data.provider.impl.DataObjectWrapperBase}.
326     * @param <T> the type of the data object to wrap.
327     */
328    private static final class DataObjectWrapperImpl<T> extends DataObjectWrapperBase<T> {
329
330        /**
331         * Creates a data object wrapper.
332         *
333         * @param dataObject the data object to wrap.
334         * @param metadata the metadata of the data object.
335         * @param dataObjectService the data object service to use.
336         * @param referenceLinker the reference linker implementation.
337         */
338        private DataObjectWrapperImpl(T dataObject, DataObjectMetadata metadata, DataObjectService dataObjectService,
339                                      ReferenceLinker referenceLinker) {
340            super(dataObject, metadata, dataObjectService, referenceLinker);
341        }
342    }
343
344}