001/**
002 * Copyright 2005-2016 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.krad.data.metadata.impl;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.commons.lang.StringUtils;
024import org.kuali.rice.krad.data.metadata.DataObjectAttribute;
025import org.kuali.rice.krad.data.metadata.MetadataCommon;
026import org.kuali.rice.krad.data.metadata.MetadataMergeAction;
027
028/**
029 * Class defining common attributes on many different components of the metadata (data objects, attributes, etc...)
030 * 
031 * @author Kuali Rice Team (rice.collab@kuali.org)
032 */
033public abstract class MetadataCommonBase implements MetadataCommonInternal {
034        private static final long serialVersionUID = 2610090812919046672L;
035        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MetadataCommonBase.class);
036
037        protected MetadataCommon embeddedCommonMetadata;
038        protected MetadataMergeAction mergeAction = MetadataMergeAction.MERGE;
039
040        protected String backingObjectName;
041        protected String name;
042        protected String label;
043        protected String shortLabel;
044        protected String description;
045        protected Boolean readOnly = false;
046
047        /**
048         * Returns the object's name without relying on embedded metadata. To override, this name must be set.
049         */
050        @Override
051        public Object getUniqueKeyForMerging() {
052                return name;
053        }
054
055        @Override
056        public String getBackingObjectName() {
057                if (backingObjectName != null) {
058                        return backingObjectName;
059                }
060                if (embeddedCommonMetadata != null) {
061                        return embeddedCommonMetadata.getBackingObjectName();
062                }
063                return getName();
064        }
065
066        public void setBackingObjectName(String backingObjectName) {
067                this.backingObjectName = backingObjectName;
068        }
069
070        @Override
071        public String getName() {
072                return name;
073        }
074
075        public void setName(String name) {
076                this.name = name;
077        }
078
079        @Override
080        public String getLabel() {
081                // locally set
082                if (label != null) {
083                        return label;
084                }
085                // we have an embedded, check it's label
086                if (embeddedCommonMetadata != null) {
087                        return embeddedCommonMetadata.getLabel();
088                }
089                return getLabelFromPropertyName(name);
090        }
091
092        public void setLabel(String label) {
093                this.label = label;
094        }
095
096        @Override
097        public String getShortLabel() {
098                // locally set
099                if (StringUtils.isNotBlank(shortLabel)) {
100                        return shortLabel;
101                }
102                // we have an embedded, check it's short label
103                if (embeddedCommonMetadata != null) {
104                        return embeddedCommonMetadata.getShortLabel();
105                }
106                // default to the label (local or embedded)
107                return getLabel();
108        }
109
110        public void setShortLabel(String shortLabel) {
111                this.shortLabel = shortLabel;
112        }
113
114        @Override
115        public String getDescription() {
116                if (description != null) {
117                        return description;
118                }
119                if (embeddedCommonMetadata != null) {
120                        return embeddedCommonMetadata.getDescription();
121                }
122                return "";
123        }
124
125        public void setDescription(String description) {
126                this.description = description;
127        }
128
129        @Override
130        public boolean isReadOnly() {
131                if (readOnly != null) {
132                        return readOnly;
133                }
134                if (embeddedCommonMetadata != null) {
135                        return embeddedCommonMetadata.isReadOnly();
136                }
137                return false;
138        }
139
140        public void setReadOnly(boolean readOnly) {
141                this.readOnly = readOnly;
142        }
143
144        @Override
145        public String toString() {
146                StringBuilder builder = new StringBuilder();
147                builder.append(this.getClass().getSimpleName()).append(" [");
148                builder.append("name=").append(getName()).append(", ");
149                builder.append("label=").append(getLabel()).append(", ");
150                builder.append("backingObjectName=").append(getBackingObjectName()).append(", ");
151                builder.append("readOnly=").append(isReadOnly());
152                builder.append(", ").append("mergeAction=").append(mergeAction);
153                builder.append("]");
154                return builder.toString();
155        }
156
157    /**
158    * Parses the label from the property name.
159    *
160    * @param propertyName the full property name including separators
161    */
162        protected String getLabelFromPropertyName(String propertyName) {
163                // We only want to include the component after the last property separator
164                if (propertyName.contains(".")) {
165                        propertyName = StringUtils.substringAfterLast(propertyName, ".");
166                }
167                StringBuilder label = new StringBuilder(propertyName);
168                // upper case the 1st letter
169                label.replace(0, 1, label.substring(0, 1).toUpperCase());
170                // loop through, inserting spaces when cap
171                for (int i = 0; i < label.length(); i++) {
172                        if (Character.isUpperCase(label.charAt(i)) || Character.isDigit(label.charAt(i))) {
173                                label.insert(i, ' ');
174                                i++;
175                        }
176                }
177
178                return label.toString().trim();
179        }
180
181        @Override
182        public MetadataCommon getEmbeddedCommonMetadata() {
183                return embeddedCommonMetadata;
184        }
185
186        @Override
187        public void setEmbeddedCommonMetadata(MetadataCommon embeddedCommonMetadata) {
188                this.embeddedCommonMetadata = embeddedCommonMetadata;
189        }
190
191        @Override
192        public MetadataMergeAction getMergeAction() {
193                return mergeAction;
194        }
195
196        public void setMergeAction(MetadataMergeAction mergeAction) {
197                this.mergeAction = mergeAction;
198        }
199
200    /**
201    * Merges multiple lists into one.
202    *
203    * <p>
204    *     Merges embedded and locallists.
205    * </p>
206    *
207    * @param embeddedList the embedded list.
208    * @param localList the local list.
209    */
210        protected <T extends MetadataCommon> List<T> mergeLists(List<T> embeddedList, List<T> localList) {
211                if (localList == null) {
212                        return new ArrayList<T>(embeddedList);
213                }
214                List<T> mergedList = new ArrayList<T>(embeddedList.size() + localList.size());
215                // Go through the local list (which can override the embedded list and add to a map by name)
216                Map<Object, T> localObjectMap = new HashMap<Object, T>(localList.size());
217                for (T item : localList) {
218                        if (item instanceof MetadataCommonInternal) {
219                                localObjectMap.put(((MetadataCommonInternal) item).getUniqueKeyForMerging(), item);
220                        } else {
221                                localObjectMap.put(item.getName(), item);
222                        }
223                }
224                // Go through Master (to be embedded) list - add to merged list
225                for (T item : embeddedList) {
226                        Object mergeKey = item.getName();
227                        if (item instanceof MetadataCommonInternal) {
228                                mergeKey = ((MetadataCommonInternal) item).getUniqueKeyForMerging();
229                        }
230                        // check for key match in local list
231                        T localItem = localObjectMap.get(mergeKey);
232                        // if no match, add to list
233                        if (localItem == null) {
234                                mergedList.add(item);
235                        } else {
236                                if (localItem.getMergeAction() == MetadataMergeAction.MERGE) {
237                                        // add the master item as embedded in the local item
238                                        if (localItem instanceof MetadataCommonInternal) {
239                                                ((MetadataCommonInternal) localItem).setEmbeddedCommonMetadata(item);
240                                                if (localItem instanceof DataObjectAttributeInternal && item instanceof DataObjectAttribute) {
241                                                        ((DataObjectAttributeInternal) localItem).setEmbeddedAttribute((DataObjectAttribute) item);
242                                                }
243                                        } else {
244                                                LOG.warn("List item implementation class ("
245                                                                + localItem.getClass().getName()
246                                                                + ") does not implement the MetadataCommonInternal interface.  It can not merge in previously extracted metadata.");
247                                        }
248                                        // add the local item to the list
249                                        mergedList.add(localItem);
250                                } else if (localItem.getMergeAction() == MetadataMergeAction.REPLACE) {
251                                        // use the local metadata and do not embed
252                                        mergedList.add(localItem);
253                                } else if (localItem.getMergeAction() == MetadataMergeAction.REMOVE) {
254                                        // Do nothing - just don't add to the list
255                                } else if (localItem.getMergeAction() == MetadataMergeAction.NO_OVERRIDE) {
256                                        // Ignore the overriding item and add the original
257                                        mergedList.add(item);
258                                } else {
259                                        LOG.warn("Unsupported MetadataMergeAction: " + localItem.getMergeAction() + " on " + localItem);
260                                }
261                                // remove the item from the map since it's been merged
262                                localObjectMap.remove(mergeKey);
263                        }
264                }
265                // now, the map only has the remaining items - add them to the end of the list
266                mergedList.addAll(localObjectMap.values());
267
268                return mergedList;
269        }
270}