View Javadoc
1   /**
2    * Copyright 2005-2014 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.coreservice.impl.component;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.commons.lang.builder.CompareToBuilder;
21  import org.apache.log4j.Logger;
22  import org.kuali.rice.core.api.criteria.QueryByCriteria;
23  import org.kuali.rice.core.api.criteria.QueryResults;
24  import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
25  import org.kuali.rice.core.api.util.ChecksumUtils;
26  import org.kuali.rice.coreservice.api.component.Component;
27  import org.kuali.rice.coreservice.api.component.ComponentService;
28  import org.kuali.rice.krad.data.CompoundKey;
29  import org.kuali.rice.krad.data.DataObjectService;
30  import org.springframework.beans.factory.annotation.Required;
31  import org.springframework.transaction.annotation.Transactional;
32  
33  import java.sql.Timestamp;
34  import java.util.ArrayList;
35  import java.util.Collections;
36  import java.util.Comparator;
37  import java.util.HashMap;
38  import java.util.List;
39  import java.util.Map;
40  
41  /**
42   * Reference implementation of the {@code ComponentService}.
43   *
44   * @author Kuali Rice Team (rice.collab@kuali.org)
45   */
46  @Transactional
47  public class ComponentServiceImpl implements ComponentService {
48  
49      private static final Logger LOG = Logger.getLogger(ComponentServiceImpl.class);
50  
51      private ComponentSetDao componentSetDao;
52      private DataObjectService dataObjectService;
53  
54      @Override
55      public Component getComponentByCode(String namespaceCode, String componentCode) {
56          if (StringUtils.isBlank(namespaceCode)) {
57              throw new RiceIllegalArgumentException("namespaceCode was a null or blank value");
58          }
59          if (StringUtils.isBlank(componentCode)) {
60              throw new RiceIllegalArgumentException("componentCode was a null or blank value");
61          }
62          Map<String, String> primaryKeys = new HashMap<String, String>();
63          primaryKeys.put("namespaceCode", namespaceCode);
64          primaryKeys.put("code", componentCode);
65          ComponentBo componentBo = dataObjectService.find(ComponentBo.class,new CompoundKey(primaryKeys));
66          if (componentBo != null) {
67              return ComponentBo.to(componentBo);
68          }
69          DerivedComponentBo derivedComponentBo = dataObjectService.find(
70                          DerivedComponentBo.class,new CompoundKey(primaryKeys));
71          return derivedComponentBo == null ? null : DerivedComponentBo.to(derivedComponentBo);
72      }
73  
74      @Override
75      public List<Component> getAllComponentsByNamespaceCode(String namespaceCode) {
76          if (StringUtils.isBlank(namespaceCode)) {
77              throw new RiceIllegalArgumentException("namespaceCode was a null or blank value");
78          }
79          Map<String, String> criteria = new HashMap<String, String>();
80          criteria.put("namespaceCode", namespaceCode);
81          QueryResults<ComponentBo> componentBos =
82                  getDataObjectService().findMatching(ComponentBo.class,
83                          QueryByCriteria.Builder.andAttributes(criteria).build());
84  
85          QueryResults<DerivedComponentBo> derivedComponentBos =
86                  getDataObjectService().findMatching(DerivedComponentBo.class, QueryByCriteria.Builder.andAttributes(
87                          criteria).build());
88          return translateCollections(componentBos, derivedComponentBos);
89      }
90  
91      @Override
92      public List<Component> getActiveComponentsByNamespaceCode(String namespaceCode) {
93          if (StringUtils.isBlank(namespaceCode)) {
94              throw new RiceIllegalArgumentException("namespaceCode was a null or blank value");
95          }
96          Map<String, Object> criteria = new HashMap<String, Object>();
97          criteria.put("namespaceCode", namespaceCode);
98          criteria.put("active", Boolean.TRUE);
99          QueryResults<ComponentBo> componentBos =
100                 getDataObjectService().findMatching(ComponentBo.class,
101                         QueryByCriteria.Builder.andAttributes(criteria).build());
102         criteria.remove("active");
103         QueryResults<DerivedComponentBo> derivedComponentBos =
104                 getDataObjectService().findMatching(DerivedComponentBo.class,
105                         QueryByCriteria.Builder.andAttributes(criteria).build());
106         return translateCollections(componentBos, derivedComponentBos);
107     }
108 
109     @Override
110     public List<Component> getDerivedComponentSet(String componentSetId) {
111         if (StringUtils.isBlank(componentSetId)) {
112             throw new RiceIllegalArgumentException("componentSetId was a null or blank value");
113         }
114         Map<String, Object> criteria = new HashMap<String, Object>();
115         criteria.put("componentSetId", componentSetId);
116         QueryResults<DerivedComponentBo> derivedComponentBos =
117                 getDataObjectService().findMatching(DerivedComponentBo.class,
118                         QueryByCriteria.Builder.andAttributes(criteria).build());
119         return translateCollections(null, derivedComponentBos);
120     }
121 
122     @Override
123     public void publishDerivedComponents(String componentSetId, List<Component> components) {
124         if (StringUtils.isBlank(componentSetId)) {
125             throw new RiceIllegalArgumentException("componentSetId was a null or blank value");
126         }
127         components = validateAndNormalizeComponents(componentSetId, components);
128         LOG.info("Requesting to publish " + components.size() + " derived components for componentSetId=" + componentSetId);
129         ComponentSetBo componentSet = getDataObjectService().find(ComponentSetBo.class,componentSetId);
130         if (componentSet == null) {
131             componentSet = new ComponentSetBo();
132             componentSet.setComponentSetId(componentSetId);
133         }
134         String checksum = calculateChecksum(components);
135         if (!checksum.equals(componentSet.getChecksum())) {
136             LOG.info("Checksums were different, proceeding with update of derived components for componentSetId=" + componentSetId);
137             componentSet.setChecksum(checksum);
138             componentSet.setLastUpdateTimestamp(new Timestamp(System.currentTimeMillis()));
139             if (getComponentSetDao().saveIgnoreLockingFailure(componentSet)) {
140                 updateDerivedComponents(componentSetId, components);
141             }
142         } else {
143             LOG.info("Checksums were the same, no derived component update needed for componentSetId=" + componentSetId);
144         }
145     }
146 
147     protected List<Component> validateAndNormalizeComponents(String componentSetId, List<Component> components) {
148         List<Component> processedComponents = new ArrayList<Component>();
149 
150         // normalize and copy component list, we will later sort this list possibly so don't want to hold onto the original
151         if (components == null) {
152             components = new ArrayList<Component>();
153         } else {
154             components = new ArrayList<Component>(components);
155         }
156         // components must either have a null componentSetId or one which matches the componentSetId being published
157         for (Component component : components) {
158             // if componentSetId is null, recreate the component with that value
159             if (component.getComponentSetId() == null) {
160                 Component.Builder builder = Component.Builder.create(component);
161                 builder.setComponentSetId(componentSetId);
162                 component = builder.build();
163             }
164             String currentComponentSetId = component.getComponentSetId();
165             if (!componentSetId.equals(currentComponentSetId)) {
166                 throw new RiceIllegalArgumentException("Encountered a component with an invalid componentSetId of '" +
167                         currentComponentSetId + "'.  Expected null or '" + componentSetId + "'.");
168             }
169             processedComponents.add(component);
170         }
171         return processedComponents;
172     }
173 
174     /**
175      * Calculates the checksum for the list of components.  The list of components should be sorted in a
176      * consistent way prior to generation of the checksum to ensure that the checksum value comes out the same regardless
177      * of the ordering of components contained therein.  The checksum allows us to easily determine if the component set
178      * has been updated or not.
179      */
180     protected String calculateChecksum(List<Component> components) {
181         Collections.sort(components, new Comparator<Component>() {
182             @Override
183             public int compare(Component component1, Component component2) {
184                 return CompareToBuilder.reflectionCompare(component1, component2);
185             }
186         });
187         return ChecksumUtils.calculateChecksum(components);
188     }
189 
190     protected void updateDerivedComponents(String componentSetId, List<Component> components) {
191         Map<String, Object> deleteCriteria = new HashMap<String, Object>();
192         deleteCriteria.put("componentSetId", componentSetId);
193         dataObjectService.deleteMatching(DerivedComponentBo.class,
194                     QueryByCriteria.Builder.andAttributes(deleteCriteria).build());
195         dataObjectService.flush(DerivedComponentBo.class);
196         if (CollectionUtils.isNotEmpty(components)) {
197             List<DerivedComponentBo> derivedComponentBos = new ArrayList<DerivedComponentBo>();
198             for (Component component : components) {
199                 derivedComponentBos.add(DerivedComponentBo.from(component));
200             }
201             for(DerivedComponentBo component : derivedComponentBos){
202                 dataObjectService.save(component);
203             }
204         }
205     }
206 
207     protected List<Component> translateCollections(QueryResults<ComponentBo> componentBos,
208             QueryResults<DerivedComponentBo> derivedComponentBos){
209         List<Component> components = new ArrayList<Component>();
210         if (componentBos != null && CollectionUtils.isNotEmpty(componentBos.getResults())) {
211             for (ComponentBo componentBo : componentBos.getResults()) {
212                 components.add(ComponentBo.to(componentBo));
213             }
214         }
215         if (derivedComponentBos != null && CollectionUtils.isNotEmpty(derivedComponentBos.getResults())) {
216             for (DerivedComponentBo derivedComponentBo : derivedComponentBos.getResults()) {
217                 components.add(DerivedComponentBo.to(derivedComponentBo));
218             }
219         }
220         return Collections.unmodifiableList(components);
221     }
222 
223     public ComponentSetDao getComponentSetDao() {
224         return componentSetDao;
225     }
226 
227     public void setComponentSetDao(ComponentSetDao componentSetDao) {
228         this.componentSetDao = componentSetDao;
229     }
230 
231 
232     public DataObjectService getDataObjectService() {
233         return dataObjectService;
234     }
235     @Required
236     public void setDataObjectService(DataObjectService dataObjectService) {
237         this.dataObjectService = dataObjectService;
238     }
239 
240 }