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.type;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.commons.lang.StringUtils;
020import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
021import org.kuali.rice.core.api.uif.RemotableAttributeError;
022import org.kuali.rice.core.api.uif.RemotableAttributeField;
023import org.kuali.rice.core.api.uif.RemotableTextInput;
024import org.kuali.rice.core.api.util.jaxb.MapStringStringAdapter;
025import org.kuali.rice.krad.datadictionary.validation.AttributeValidatingTypeServiceBase;
026import org.kuali.rice.krad.service.DataDictionaryRemoteFieldService;
027import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
028import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
029import org.kuali.rice.krms.api.repository.type.KrmsTypeAttribute;
030import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition;
031import org.kuali.rice.krms.api.repository.type.KrmsTypeRepositoryService;
032import org.kuali.rice.krms.framework.type.RemotableAttributeOwner;
033import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
034
035import javax.jws.WebParam;
036import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
037import java.util.ArrayList;
038import java.util.Collections;
039import java.util.Comparator;
040import java.util.HashMap;
041import java.util.List;
042import java.util.Map;
043
044/**
045 * {@link KrmsTypeServiceBase} is an abstract class providing default implementation and hooks for
046 * provisioning and validating the custom attributes of a krms type.  Is should probably be mentioned that the
047 * default validation methods don't actually check anything, they just return empty error lists.
048 */
049public abstract class KrmsTypeServiceBase extends AttributeValidatingTypeServiceBase implements RemotableAttributeOwner {
050
051    /**
052     * <p>get the {@link RemotableAttributeField}s for the custom attributes of this krms type.  This implementation
053     * will (by default) return any attributes mapped to the type via
054     * {@link org.kuali.rice.krms.impl.repository.KrmsTypeAttributeBo}. If there is is a component name defined on the
055     * related {@link org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionBo} then that will be used to generate
056     * the {@link RemotableAttributeField}.  If not, then a simple text input will be produced.</p>
057     *
058     * <p>An extending class can also override the
059     * {@link #translateTypeAttribute(org.kuali.rice.krms.api.repository.type.KrmsTypeAttribute,
060     * org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition)}
061     * method which is called from here, and within it hand create the RemotableAttributeField for a certain attribute.
062     * </p>
063     *
064     * <p>Also handy for extenders to know, this method delegates to {@link #getTypeAttributeDefinitions(String)} and
065     * then pulls out the {@link RemotableAttributeField}s from the returned {@link TypeAttributeDefinition}s</p>
066     *
067     * @param krmsTypeId the people flow type identifier.  Must not be null or blank.
068     * @return
069     * @throws RiceIllegalArgumentException
070     */
071    @Override
072    public List<RemotableAttributeField> getAttributeFields(@WebParam(name = "krmsTypeId") String krmsTypeId) throws RiceIllegalArgumentException {
073        List<TypeAttributeDefinition> typeAttributeDefinitions = getTypeAttributeDefinitions(krmsTypeId);
074
075        if (CollectionUtils.isEmpty(typeAttributeDefinitions)) {
076            return Collections.emptyList();
077        } else {
078            List<RemotableAttributeField> fields =
079                    new ArrayList<RemotableAttributeField>(typeAttributeDefinitions.size());
080
081            for (TypeAttributeDefinition typeAttributeDefinition : typeAttributeDefinitions) {
082                fields.add(typeAttributeDefinition.getField());
083            }
084            return fields;
085        }
086    }
087
088    /**
089     * Gets an ordered List of {@link TypeAttributeDefinition}s for the attributes on the KRMS type specified by the
090     * given krmsTypeId.
091     * @param krmsTypeId the ID of the KRMS Type whose attributes we are getting.
092     * @return a List of type-agnostic {@link TypeAttributeDefinition}s
093     * @see AttributeValidatingTypeServiceBase
094     */
095    @Override
096    protected List<TypeAttributeDefinition> getTypeAttributeDefinitions(String krmsTypeId) {
097
098        if (StringUtils.isBlank(krmsTypeId)) {
099            throw new RiceIllegalArgumentException("krmsTypeId must be non-null and non-blank");
100        }
101
102        List<TypeAttributeDefinition> results = new ArrayList<TypeAttributeDefinition>();
103
104        // keep track of how to sort these
105        final Map<String, Integer> sortCodeMap = new HashMap<String, Integer>();
106
107        KrmsTypeDefinition krmsType =
108                KrmsRepositoryServiceLocator.getKrmsTypeRepositoryService().getTypeById(krmsTypeId);
109
110        if (krmsType == null) {
111            throw new RiceIllegalArgumentException("krmsTypeId must be a valid id of a KRMS type");
112        } else {
113            // translate attributes
114
115            List<KrmsTypeAttribute> typeAttributes = krmsType.getAttributes();
116
117            List<RemotableAttributeField> typeAttributeFields = new ArrayList<RemotableAttributeField>(10);
118            if (!CollectionUtils.isEmpty(typeAttributes)) {
119                // translate the attribute and store the sort code in our map
120                for (KrmsTypeAttribute typeAttribute : typeAttributes) {
121
122                    KrmsTypeRepositoryService typeRepositoryService = KrmsRepositoryServiceLocator.getKrmsTypeRepositoryService();
123
124                    KrmsAttributeDefinition attributeDefinition =
125                            typeRepositoryService.getAttributeDefinitionById(typeAttribute.getAttributeDefinitionId());
126
127                    RemotableAttributeField attributeField = translateTypeAttribute(typeAttribute, attributeDefinition);
128
129                    if (typeAttribute.getSequenceNumber() == null) {
130                        throw new IllegalStateException(typeAttribute.toString() + " has a null sequenceNumber");
131                    } else {
132                        sortCodeMap.put(attributeField.getName(), typeAttribute.getSequenceNumber());
133                    }
134
135                    TypeAttributeDefinition typeAttributeDefinition =
136                            new TypeAttributeDefinition(attributeField, attributeDefinition.getName(), attributeDefinition.getComponentName(), attributeDefinition.getLabel(), null);
137
138                    results.add(typeAttributeDefinition);
139                }
140            }
141        }
142
143        sortFields(results, sortCodeMap);
144
145        return results;
146    }
147
148
149    protected void sortFields(List<TypeAttributeDefinition> results,
150            final Map<String, Integer> sortCodeMap) {// sort the results
151        Collections.sort(results, new Comparator<TypeAttributeDefinition>() {
152            @Override
153            public int compare(TypeAttributeDefinition o1, TypeAttributeDefinition o2) {
154                if (o1 == o2 || o1.equals(o2))
155                    return 0;
156                // we assume that each has a sort code based on our previous check
157                Integer o1SortCode = sortCodeMap.get(o1.getName());
158                Integer o2SortCode = sortCodeMap.get(o2.getName());
159
160                if (o1SortCode.compareTo(o2SortCode) != 0) {
161                    return o1SortCode.compareTo(o2SortCode);
162                } else {
163                    // if sort codes are the same, we still would like a consistent order
164                    return o1.getName().compareTo(o2.getName());
165                }
166            }
167        });
168    }
169
170    @Override
171    public List<RemotableAttributeError> validateAttributes(@WebParam(name = "krmsTypeId") String krmsTypeId,
172            @WebParam(name = "attributes") @XmlJavaTypeAdapter(
173                    value = MapStringStringAdapter.class) Map<String, String> attributes) throws RiceIllegalArgumentException {
174        return super.validateAttributes(krmsTypeId, attributes);
175    }
176
177    @Override
178    public List<RemotableAttributeError> validateAttributesAgainstExisting(
179            @WebParam(name = "krmsTypeId") String krmsTypeId, @WebParam(name = "newAttributes") @XmlJavaTypeAdapter(
180            value = MapStringStringAdapter.class) Map<String, String> newAttributes,
181            @WebParam(name = "oldAttributes") @XmlJavaTypeAdapter(
182                    value = MapStringStringAdapter.class) Map<String, String> oldAttributes) throws RiceIllegalArgumentException {
183        return validateAttributes(krmsTypeId, newAttributes);
184    }
185
186    /**
187     * Translate a {@link org.kuali.rice.krms.api.repository.type.KrmsTypeAttribute} into a {@link org.kuali.rice.core.api.uif.RemotableAttributeField}.
188     * Override this method to provide custom translation of certain attributes.
189     * @param inputAttribute the {@link org.kuali.rice.krms.api.repository.type.KrmsTypeAttribute} to translate
190     * @param attributeDefinition the {@link KrmsAttributeDefinition} for the given inputAttribute
191     * @return a {@link org.kuali.rice.core.api.uif.RemotableAttributeField} for the given inputAttribute
192     */
193    public RemotableAttributeField translateTypeAttribute(KrmsTypeAttribute inputAttribute,
194            KrmsAttributeDefinition attributeDefinition) {
195
196        if (StringUtils.isEmpty(attributeDefinition.getComponentName())) {
197            RemotableAttributeField.Builder builder = RemotableAttributeField.Builder.create(attributeDefinition.getName());
198
199            RemotableTextInput.Builder controlBuilder = RemotableTextInput.Builder.create();
200            controlBuilder.setSize(80);
201
202            controlBuilder.setWatermark(attributeDefinition.getDescription());
203
204            builder.setShortLabel(attributeDefinition.getLabel());
205            builder.setLongLabel(attributeDefinition.getLabel());
206            builder.setName(attributeDefinition.getName());
207
208//            builder.setHelpSummary("helpSummary: " + attributeDefinition.getDescription());
209//            builder.setHelpDescription("helpDescription: " + attributeDefinition.getDescription());
210
211            builder.setControl(controlBuilder);
212            builder.setMaxLength(400);
213
214            return builder.build();
215        } else {
216            return getDataDictionaryRemoteFieldService().buildRemotableFieldFromAttributeDefinition(
217                    attributeDefinition.getComponentName(),
218                    attributeDefinition.getName());
219        }
220    }
221
222    public DataDictionaryRemoteFieldService getDataDictionaryRemoteFieldService() {
223        return KRADServiceLocatorWeb.getDataDictionaryRemoteFieldService();
224    }
225
226    @Override
227    protected List<RemotableAttributeError> validateNonDataDictionaryAttribute(RemotableAttributeField attr, String key,
228            String value) {
229        return Collections.emptyList();
230    }
231}