001/**
002 * Copyright 2005-2017 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.krms.impl.repository;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.criteria.QueryByCriteria;
020import org.kuali.rice.core.api.criteria.QueryResults;
021import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
022import org.kuali.rice.krad.data.DataObjectService;
023import org.kuali.rice.krad.data.PersistenceOption;
024import org.kuali.rice.krad.service.KRADServiceLocator;
025import org.kuali.rice.krms.api.repository.term.TermDefinition;
026import org.kuali.rice.krms.api.repository.term.TermResolverDefinition;
027import org.kuali.rice.krms.api.repository.term.TermSpecificationDefinition;
028import org.kuali.rice.krms.impl.util.KrmsImplConstants;
029import org.springframework.util.CollectionUtils;
030
031import java.util.ArrayList;
032import java.util.Collections;
033import java.util.HashMap;
034import java.util.List;
035import java.util.Map;
036
037import static org.kuali.rice.krms.impl.repository.BusinessObjectServiceMigrationUtils.findMatching;
038import static org.kuali.rice.krms.impl.repository.BusinessObjectServiceMigrationUtils.findSingleMatching;
039
040/**
041 * Implementation of {@link TermBoService}
042 *
043 * @author Kuali Rice Team (rice.collab@kuali.org)
044 */
045public class TermBoServiceImpl implements TermBoService {
046
047    private DataObjectService dataObjectService;
048
049    /**
050     * @see org.kuali.rice.krms.impl.repository.TermBoService#getTermSpecificationById(java.lang.String)
051     */
052    @Override
053    public TermSpecificationDefinition getTermSpecificationById(String id) {
054        TermSpecificationDefinition result = null;
055
056        if (StringUtils.isBlank(id)) {
057            throw new RiceIllegalArgumentException("id must not be blank or null");
058        }
059
060        TermSpecificationBo termSpecificationBo = getDataObjectService().find(TermSpecificationBo.class, id);
061
062        if (termSpecificationBo != null) {
063            List<ContextValidTermBo> contextValidTermBos =
064                    findMatching(getDataObjectService(), ContextValidTermBo.class,
065                            Collections.singletonMap("termSpecification.id", termSpecificationBo.getId()));
066
067            if (contextValidTermBos != null) for (ContextValidTermBo contextValidTerm : contextValidTermBos) {
068                termSpecificationBo.getContextIds().add(contextValidTerm.getContextId());
069            }
070
071            result = TermSpecificationDefinition.Builder.create(termSpecificationBo).build();
072        }
073
074        return result;
075    }
076
077    /**
078     * @see org.kuali.rice.krms.impl.repository.TermBoService#createTermSpecification(org.kuali.rice.krms.api.repository.term.TermSpecificationDefinition)
079     */
080    @Override
081    public TermSpecificationDefinition createTermSpecification(TermSpecificationDefinition termSpec) {
082        if (!StringUtils.isBlank(termSpec.getId())) {
083            throw new RiceIllegalArgumentException("for creation, TermSpecification.id must be null");
084        }
085
086        TermSpecificationBo termSpecBo = TermSpecificationBo.from(termSpec);
087
088        // save relations to the contexts on the BO
089        if (!CollectionUtils.isEmpty(termSpec.getContextIds())) {
090            for (String contextId : termSpec.getContextIds()) {
091                ContextValidTermBo contextValidTerm = new ContextValidTermBo();
092                contextValidTerm.setContextId(contextId);
093                contextValidTerm.setTermSpecification(termSpecBo);
094
095                termSpecBo.getContextValidTerms().add(contextValidTerm);
096            }
097        }
098
099        termSpecBo = getDataObjectService().save(termSpecBo, PersistenceOption.FLUSH);
100
101        return TermSpecificationBo.to(termSpecBo);
102    }
103
104    @Override
105    public void updateTermSpecification(TermSpecificationDefinition termSpec) throws RiceIllegalArgumentException {
106        if (termSpec == null) {
107            throw new IllegalArgumentException("term specification is null");
108        }
109
110        // must already exist to be able to update
111        final String termSpecificationId = termSpec.getId();
112        final TermSpecificationBo existing = getDataObjectService().find(TermSpecificationBo.class, termSpecificationId);
113
114        if (existing == null) {
115            throw new IllegalStateException("the term specification does not exist: " + termSpec);
116        }
117
118        final TermSpecificationDefinition toUpdate;
119
120        if (!existing.getId().equals(termSpec.getId())) {
121            // if passed in id does not match existing id, correct it
122            final TermSpecificationDefinition.Builder builder = TermSpecificationDefinition.Builder.create(termSpec);
123            builder.setId(existing.getId());
124            toUpdate = builder.build();
125        } else {
126            toUpdate = termSpec;
127        }
128
129        // copy all updateable fields to bo
130        TermSpecificationBo boToUpdate = TermSpecificationBo.from(toUpdate);
131        reconcileContextValidTerms(existing, boToUpdate);
132
133        // update the rule and create new attributes
134        getDataObjectService().save(boToUpdate, PersistenceOption.FLUSH);
135    }
136
137    /**
138     * Transfer any ContextValidTermBos that still apply from existing to boToUpdate, and create new ContextValidTermBos
139     * for any new context IDs that are found in boToUpdate.
140     *
141     * <p>This method is side effecting, it makes modifications to boToUpdate.contextValidTerms. </p>
142     *
143     * @param existing the TermSpecificationBo which has been fetched from the database
144     * @param boToUpdate the new TermSpecificationBo which will (later) be persisted
145     */
146    private void reconcileContextValidTerms(TermSpecificationBo existing,
147            TermSpecificationBo boToUpdate) {
148
149        // add all contextValidTerms that still apply
150        for (ContextValidTermBo contextValidTerm : existing.getContextValidTerms()) {
151            if (boToUpdate.getContextIds().contains(contextValidTerm.getContextId())) {
152                boToUpdate.getContextValidTerms().add(contextValidTerm);
153            }
154        }
155
156        // add new contextValidTerms for new context IDs
157        for (String contextId : boToUpdate.getContextIds()) {
158            boolean alreadyInContextValidTerms = false;
159
160            for (ContextValidTermBo contextValidTerm : boToUpdate.getContextValidTerms()) {
161                if (contextId.equals(contextValidTerm.getContextId())) {
162                    alreadyInContextValidTerms = true;
163                    break;
164                }
165            }
166
167            if (!alreadyInContextValidTerms) {
168                ContextValidTermBo contextValidTerm = new ContextValidTermBo();
169                contextValidTerm.setContextId(contextId);
170                contextValidTerm.setTermSpecification(boToUpdate);
171
172                boToUpdate.getContextValidTerms().add(contextValidTerm);
173            }
174        }
175    }
176
177    @Override
178    public void deleteTermSpecification(String id) throws RiceIllegalArgumentException {
179        if (id == null) {
180            throw new RiceIllegalArgumentException("agendaId is null");
181        }
182
183        final TermSpecificationBo existing = getDataObjectService().find(TermSpecificationBo.class, id);
184
185        if (existing == null) {
186            throw new IllegalStateException("the TermSpecification to delete does not exists: " + id);
187        }
188
189        getDataObjectService().delete(existing);
190    }
191
192    /**
193     * @see org.kuali.rice.krms.impl.repository.TermBoService#createTerm(org.kuali.rice.krms.api.repository.term.TermDefinition)
194     */
195    @Override
196    public TermDefinition createTerm(TermDefinition termDef) {
197        if (!StringUtils.isBlank(termDef.getId())) {
198            throw new RiceIllegalArgumentException("for creation, TermDefinition.id must be null");
199        }
200
201        TermBo termBo = TermBo.from(termDef);
202        termBo = getDataObjectService().save(termBo, PersistenceOption.FLUSH);
203
204        return TermBo.to(termBo);
205    }
206
207    @Override
208    public void updateTerm(TermDefinition term) throws RiceIllegalArgumentException {
209        if (term == null) {
210            throw new IllegalArgumentException("term is null");
211        }
212
213        // must already exist to be able to update
214        final String termId = term.getId();
215        final TermBo existing = getDataObjectService().find(TermBo.class, termId);
216
217        if (existing == null) {
218            throw new IllegalStateException("the term resolver does not exist: " + term);
219        }
220
221        final TermDefinition toUpdate;
222
223        if (!existing.getId().equals(term.getId())) {
224            // if passed in id does not match existing id, correct it
225            final TermDefinition.Builder builder = TermDefinition.Builder.create(term);
226            builder.setId(existing.getId());
227            toUpdate = builder.build();
228        } else {
229            toUpdate = term;
230        }
231
232        // copy all updateable fields to bo
233        TermBo boToUpdate = TermBo.from(toUpdate);
234
235        // update the rule and create new attributes
236        getDataObjectService().save(boToUpdate, PersistenceOption.FLUSH);
237    }
238
239    @Override
240    public void deleteTerm(String id) throws RiceIllegalArgumentException {
241        if (id == null) {
242            throw new RiceIllegalArgumentException("termId is null");
243        }
244
245        TermBo existing = getDataObjectService().find(TermBo.class, id);
246
247        if (existing == null) {
248            throw new IllegalStateException("the term to delete does not exists: " + id);
249        }
250
251        getDataObjectService().delete(existing);
252    }
253
254    /**
255     * @see org.kuali.rice.krms.impl.repository.TermBoService#createTermResolver(org.kuali.rice.krms.api.repository.term.TermResolverDefinition)
256     */
257    @Override
258    public TermResolverDefinition createTermResolver(TermResolverDefinition termResolver) {
259        if (!StringUtils.isBlank(termResolver.getId())) {
260            throw new RiceIllegalArgumentException("for creation, TermResolverDefinition.id must be null");
261        }
262
263        TermResolverBo termResolverBo = TermResolverBo.from(termResolver);
264
265        termResolverBo = (TermResolverBo) getDataObjectService().save(termResolverBo, PersistenceOption.FLUSH);
266
267        return TermResolverBo.to(termResolverBo);
268    }
269
270    @Override
271    public void updateTermResolver(TermResolverDefinition termResolver) throws RiceIllegalArgumentException {
272        if (termResolver == null) {
273            throw new IllegalArgumentException("term resolver is null");
274        }
275
276        // must already exist to be able to update
277        final String termResolverId = termResolver.getId();
278        final TermResolverBo existing = getDataObjectService().find(TermResolverBo.class, termResolverId);
279
280        if (existing == null) {
281            throw new IllegalStateException("the term resolver does not exist: " + termResolver);
282        }
283
284        final TermResolverDefinition toUpdate;
285
286        if (!existing.getId().equals(termResolver.getId())) {
287            // if passed in id does not match existing id, correct it
288            final TermResolverDefinition.Builder builder = TermResolverDefinition.Builder.create(termResolver);
289            builder.setId(existing.getId());
290            toUpdate = builder.build();
291        } else {
292            toUpdate = termResolver;
293        }
294
295        // copy all updateable fields to bo
296        TermResolverBo boToUpdate = TermResolverBo.from(toUpdate);
297
298        // delete any old, existing attributes
299        QueryByCriteria crit =
300                QueryByCriteria.Builder.forAttribute(KrmsImplConstants.PropertyNames.TermResolver.TERM_RESOLVER_ID, toUpdate.getId()).build();
301
302        getDataObjectService().deleteMatching(TermResolverAttributeBo.class, crit);
303
304        // update the rule and create new attributes
305        getDataObjectService().save(boToUpdate, PersistenceOption.FLUSH);
306    }
307
308    @Override
309    public void deleteTermResolver(String id) throws RiceIllegalArgumentException {
310        if (id == null) {
311            throw new RiceIllegalArgumentException("agendaId is null");
312        }
313
314        TermSpecificationBo existing = getDataObjectService().find(TermSpecificationBo.class, id);
315
316        if (existing == null) {
317            throw new IllegalStateException("the TermResolver to delete does not exists: " + id);
318        }
319
320        getDataObjectService().delete(existing);
321    }
322
323    /**
324     * @see org.kuali.rice.krms.impl.repository.TermBoService#getTerm(java.lang.String)
325     */
326    @Override
327    public TermDefinition getTerm(String id) {
328        TermDefinition result = null;
329
330        if (StringUtils.isBlank(id)) {
331            throw new RiceIllegalArgumentException("id must not be blank or null");
332        }
333
334        TermBo termBo = getDataObjectService().find(TermBo.class, id);
335
336        if (termBo != null) {
337            result = TermBo.to(termBo);
338        }
339
340        return result;
341    }
342
343    /**
344     * @see org.kuali.rice.krms.impl.repository.TermBoService#getTermResolverById(java.lang.String)
345     */
346    @Override
347    public TermResolverDefinition getTermResolverById(String id) {
348        TermResolverDefinition result = null;
349
350        if (StringUtils.isBlank(id)) {
351            throw new RiceIllegalArgumentException("id must not be blank or null");
352        }
353
354        TermResolverBo termResolverBo = getDataObjectService().find(TermResolverBo.class, id);
355
356        if (termResolverBo != null) {
357            result = TermResolverBo.to(termResolverBo);
358        }
359
360        return result;
361    }
362
363    @Override
364    public List<TermResolverDefinition> findTermResolversByOutputId(String id, String namespace) {
365        List<TermResolverDefinition> results = null;
366
367        if (StringUtils.isBlank(id)) {
368            throw new RiceIllegalArgumentException("id must not be blank or null");
369        }
370
371        if (StringUtils.isBlank(namespace)) {
372            throw new RiceIllegalArgumentException("namespace must not be blank or null");
373        }
374
375        Map<String, String> critMap = new HashMap<String, String>(2);
376
377        critMap.put("outputId", id);
378        critMap.put("namespace", namespace);
379
380        QueryByCriteria crit = QueryByCriteria.Builder.andAttributes(critMap).build();
381
382        QueryResults<TermResolverBo> termResolverBos = getDataObjectService().findMatching(TermResolverBo.class, crit);
383
384        if (!CollectionUtils.isEmpty(termResolverBos.getResults())) {
385            results = new ArrayList<TermResolverDefinition>(termResolverBos.getResults().size());
386
387            for (TermResolverBo termResolverBo : termResolverBos.getResults()) {
388                results.add(TermResolverBo.to(termResolverBo));
389            }
390        } else {
391            results = Collections.emptyList();
392        }
393
394        return results;
395    }
396
397    @Override
398    public List<TermResolverDefinition> findTermResolversByNamespace(String namespace) {
399        List<TermResolverDefinition> results = null;
400
401        if (StringUtils.isBlank(namespace)) {
402            throw new RiceIllegalArgumentException("namespace must not be blank or null");
403        }
404
405        QueryByCriteria crit = QueryByCriteria.Builder.forAttribute("namespace", namespace).build();
406
407        QueryResults<TermResolverBo> termResolverBos = getDataObjectService().findMatching(TermResolverBo.class, crit);
408
409        if (!CollectionUtils.isEmpty(termResolverBos.getResults())) {
410            results = new ArrayList<TermResolverDefinition>(termResolverBos.getResults().size());
411
412            for (TermResolverBo termResolverBo : termResolverBos.getResults()) {
413                if (termResolverBo != null) {
414                    results.add(TermResolverBo.to(termResolverBo));
415                }
416            }
417        } else {
418            results = Collections.emptyList();
419        }
420
421        return results;
422    }
423
424    @Override
425    public TermResolverDefinition getTermResolverByNameAndNamespace(String name,
426            String namespace) throws RiceIllegalArgumentException {
427        if (StringUtils.isBlank(name)) {
428            throw new IllegalArgumentException("name is null or blank");
429        }
430
431        if (StringUtils.isBlank(namespace)) {
432            throw new IllegalArgumentException("namespace is null or blank");
433        }
434
435        final Map<String, Object> map = new HashMap<String, Object>();
436        map.put("name", name);
437        map.put("namespace", namespace);
438        TermResolverBo bo = getDataObjectService().find(TermResolverBo.class, map);
439
440        return TermResolverBo.to(bo);
441    }
442
443    @Override
444    public TermSpecificationDefinition getTermSpecificationByNameAndNamespace(String name,
445            String namespace) throws RiceIllegalArgumentException {
446        if (StringUtils.isBlank(name)) {
447            throw new IllegalArgumentException("name is null or blank");
448        }
449
450        if (StringUtils.isBlank(namespace)) {
451            throw new IllegalArgumentException("namespace is null or blank");
452        }
453
454        final Map<String, Object> map = new HashMap<String, Object>();
455        map.put("name", name);
456        map.put("namespace", namespace);
457        TermSpecificationBo bo = findSingleMatching(getDataObjectService(), TermSpecificationBo.class, map);
458
459        return TermSpecificationBo.to(bo);
460    }
461
462    @Override
463    public List<TermSpecificationDefinition> findAllTermSpecificationsByContextId(String contextId) {
464        List<TermSpecificationDefinition> results = null;
465
466        if (StringUtils.isBlank(contextId)) {
467            throw new RiceIllegalArgumentException("contextId must not be blank or null");
468        }
469
470        QueryByCriteria crit = QueryByCriteria.Builder.forAttribute("contextId", contextId).build();
471
472        QueryResults<ContextValidTermBo> contextValidTerms =
473                getDataObjectService().findMatching(ContextValidTermBo.class, crit);
474
475        if (!CollectionUtils.isEmpty(contextValidTerms.getResults())) {
476            results = new ArrayList<TermSpecificationDefinition>(contextValidTerms.getResults().size());
477
478            for (ContextValidTermBo validTerm : contextValidTerms.getResults()) {
479                results.add(TermSpecificationBo.to(validTerm.getTermSpecification()));
480            }
481        } else {
482            results = Collections.emptyList();
483        }
484
485        return results;
486    }
487
488    /**
489     * Gets the {@link DataObjectService}.
490     *
491     * @return the {@link DataObjectService}
492     */
493    public DataObjectService getDataObjectService() {
494        if (dataObjectService == null) {
495            dataObjectService = KRADServiceLocator.getDataObjectService();
496        }
497
498        return dataObjectService;
499    }
500
501    /**
502     * Sets the {@link DataObjectService}.
503     *
504     * @param dataObjectService the {@link DataObjectService} to set
505     */
506    public void setDataObjectService(DataObjectService dataObjectService) {
507        this.dataObjectService = dataObjectService;
508    }
509
510}