View Javadoc
1   /**
2    * Copyright 2005-2015 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(readOnly=true)
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     @Transactional
124     public void publishDerivedComponents(String componentSetId, List<Component> components) {
125         if (StringUtils.isBlank(componentSetId)) {
126             throw new RiceIllegalArgumentException("componentSetId was a null or blank value");
127         }
128         components = validateAndNormalizeComponents(componentSetId, components);
129         LOG.info("Requesting to publish " + components.size() + " derived components for componentSetId=" + componentSetId);
130         ComponentSetBo componentSet = getDataObjectService().find(ComponentSetBo.class,componentSetId);
131         if (componentSet == null) {
132             componentSet = new ComponentSetBo();
133             componentSet.setComponentSetId(componentSetId);
134         }
135         String checksum = calculateChecksum(components);
136         if (!checksum.equals(componentSet.getChecksum())) {
137             LOG.info("Checksums were different, proceeding with update of derived components for componentSetId=" + componentSetId);
138             componentSet.setChecksum(checksum);
139             componentSet.setLastUpdateTimestamp(new Timestamp(System.currentTimeMillis()));
140             if (getComponentSetDao().saveIgnoreLockingFailure(componentSet)) {
141                 updateDerivedComponents(componentSetId, components);
142             }
143         } else {
144             LOG.info("Checksums were the same, no derived component update needed for componentSetId=" + componentSetId);
145         }
146     }
147 
148     protected List<Component> validateAndNormalizeComponents(String componentSetId, List<Component> components) {
149         List<Component> processedComponents = new ArrayList<Component>();
150 
151         // normalize and copy component list, we will later sort this list possibly so don't want to hold onto the original
152         if (components == null) {
153             components = new ArrayList<Component>();
154         } else {
155             components = new ArrayList<Component>(components);
156         }
157         // components must either have a null componentSetId or one which matches the componentSetId being published
158         for (Component component : components) {
159             // if componentSetId is null, recreate the component with that value
160             if (component.getComponentSetId() == null) {
161                 Component.Builder builder = Component.Builder.create(component);
162                 builder.setComponentSetId(componentSetId);
163                 component = builder.build();
164             }
165             String currentComponentSetId = component.getComponentSetId();
166             if (!componentSetId.equals(currentComponentSetId)) {
167                 throw new RiceIllegalArgumentException("Encountered a component with an invalid componentSetId of '" +
168                         currentComponentSetId + "'.  Expected null or '" + componentSetId + "'.");
169             }
170             processedComponents.add(component);
171         }
172         return processedComponents;
173     }
174 
175     /**
176      * Calculates the checksum for the list of components.  The list of components should be sorted in a
177      * consistent way prior to generation of the checksum to ensure that the checksum value comes out the same regardless
178      * of the ordering of components contained therein.  The checksum allows us to easily determine if the component set
179      * has been updated or not.
180      */
181     protected String calculateChecksum(List<Component> components) {
182         Collections.sort(components, new Comparator<Component>() {
183             @Override
184             public int compare(Component component1, Component component2) {
185                 return CompareToBuilder.reflectionCompare(component1, component2);
186             }
187         });
188         return ChecksumUtils.calculateChecksum(components);
189     }
190 
191     protected void updateDerivedComponents(String componentSetId, List<Component> components) {
192         Map<String, Object> deleteCriteria = new HashMap<String, Object>();
193         deleteCriteria.put("componentSetId", componentSetId);
194         dataObjectService.deleteMatching(DerivedComponentBo.class,
195                     QueryByCriteria.Builder.andAttributes(deleteCriteria).build());
196         dataObjectService.flush(DerivedComponentBo.class);
197         if (CollectionUtils.isNotEmpty(components)) {
198             List<DerivedComponentBo> derivedComponentBos = new ArrayList<DerivedComponentBo>();
199             for (Component component : components) {
200                 derivedComponentBos.add(DerivedComponentBo.from(component));
201             }
202             for(DerivedComponentBo component : derivedComponentBos){
203                 dataObjectService.save(component);
204             }
205         }
206     }
207 
208     protected List<Component> translateCollections(QueryResults<ComponentBo> componentBos,
209             QueryResults<DerivedComponentBo> derivedComponentBos){
210         List<Component> components = new ArrayList<Component>();
211         if (componentBos != null && CollectionUtils.isNotEmpty(componentBos.getResults())) {
212             for (ComponentBo componentBo : componentBos.getResults()) {
213                 components.add(ComponentBo.to(componentBo));
214             }
215         }
216         if (derivedComponentBos != null && CollectionUtils.isNotEmpty(derivedComponentBos.getResults())) {
217             for (DerivedComponentBo derivedComponentBo : derivedComponentBos.getResults()) {
218                 components.add(DerivedComponentBo.to(derivedComponentBo));
219             }
220         }
221         return Collections.unmodifiableList(components);
222     }
223 
224     public ComponentSetDao getComponentSetDao() {
225         return componentSetDao;
226     }
227 
228     public void setComponentSetDao(ComponentSetDao componentSetDao) {
229         this.componentSetDao = componentSetDao;
230     }
231 
232     public DataObjectService getDataObjectService() {
233         return dataObjectService;
234     }
235 
236     @Required
237     public void setDataObjectService(DataObjectService dataObjectService) {
238         this.dataObjectService = dataObjectService;
239     }
240 
241 }