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