View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.data.provider.impl;
17  
18  import java.util.Set;
19  
20  import org.kuali.rice.core.api.criteria.QueryByCriteria;
21  import org.kuali.rice.core.api.criteria.QueryResults;
22  import org.kuali.rice.krad.data.CompoundKey;
23  import org.kuali.rice.krad.data.CopyOption;
24  import org.kuali.rice.krad.data.DataObjectService;
25  import org.kuali.rice.krad.data.DataObjectWrapper;
26  import org.kuali.rice.krad.data.PersistenceOption;
27  import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
28  import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
29  import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
30  import org.kuali.rice.krad.data.metadata.MetadataChild;
31  import org.kuali.rice.krad.data.metadata.MetadataRepository;
32  import org.kuali.rice.krad.data.provider.PersistenceProvider;
33  import org.kuali.rice.krad.data.provider.ProviderRegistry;
34  import org.kuali.rice.krad.data.util.ReferenceLinker;
35  import org.springframework.beans.factory.annotation.Required;
36  import org.springframework.dao.IncorrectResultSizeDataAccessException;
37  
38  import com.google.common.collect.Sets;
39  
40  /**
41   * DataObjectService implementation backed by the {@link ProviderRegistry}.
42   *
43   * @author Kuali Rice Team (rice.collab@kuali.org)
44   */
45  public class ProviderBasedDataObjectService implements DataObjectService {
46  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
47  			.getLogger(ProviderBasedDataObjectService.class);
48  
49      /**
50       * The provider registry.
51       */
52  	protected ProviderRegistry providerRegistry;
53  
54      /**
55       * The metadata repository.
56       */
57  	protected MetadataRepository metadataRepository;
58  
59      /**
60       * The reference linker.
61       */
62      protected ReferenceLinker referenceLinker;
63  
64      /**
65       * {@inheritDoc}
66       */
67      @Override
68      public <T> T find(Class<T> type, Object id) {
69          return persistenceProviderForType(type).find(type, reduceCompoundKey(id));
70      }
71  
72      /**
73       * If the given id object is an instance of CompoundKey but there is only one entry in the key map, then just grab
74       * that single value and treat it as a single id.
75       *
76       * @param id the potentially CompoundKey to reduce
77       *
78       * @return the single value from the CompoundKey map if the given id is a CompoundKey with a single entry, otherwise
79       *         the original id that was passed in is returned
80       */
81      protected Object reduceCompoundKey(Object id) {
82          if (id instanceof CompoundKey) {
83              CompoundKey compoundKey = (CompoundKey)id;
84              if (compoundKey.getKeys().size() == 1) {
85                  id = compoundKey.getKeys().values().iterator().next();
86              }
87          }
88          return id;
89      }
90  
91      /**
92       * {@inheritDoc}
93       */
94      @Override
95      public <T> QueryResults<T> findMatching(Class<T> type, QueryByCriteria queryByCriteria) {
96          return persistenceProviderForType(type).findMatching(type, queryByCriteria);
97      }
98  
99      /**
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 }