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.krad.data.metadata.impl;
17  
18  import java.lang.reflect.Field;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.kuali.rice.krad.data.metadata.DataObjectAttribute;
28  import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
29  import org.kuali.rice.krad.data.metadata.DataObjectCollection;
30  import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
31  import org.kuali.rice.krad.data.metadata.MetadataMergeAction;
32  import org.kuali.rice.krad.data.provider.annotation.UifAutoCreateViewType;
33  
34  import com.google.common.annotations.Beta;
35  
36  /**
37   * Base implementation class for the metadata related to the data object as a whole.
38   *
39   * <p>
40   * Contains lists of all child elements.
41   * </p>
42   * 
43   * @author Kuali Rice Team (rice.collab@kuali.org)
44   */
45  public class DataObjectMetadataImpl extends MetadataCommonBase implements DataObjectMetadataInternal {
46  	private static final long serialVersionUID = 7722982931510558892L;
47  
48  	protected DataObjectMetadataInternal embedded;
49  	/**
50  	 * Property used to help with debugging.
51  	 * 
52  	 * It is used in the toString() method so you can determine from which provider this metadata was extracted.
53  	 */
54  	protected String providerName;
55  
56  	protected Class<?> type;
57  
58  	protected List<DataObjectAttribute> attributes;
59  	protected Map<String, DataObjectAttribute> attributeMap;
60  	protected List<String> removedAttributeNames;
61  	protected List<String> orderedAttributeList = new ArrayList<String>();
62  
63  	protected List<DataObjectCollection> collections;
64  	protected Map<String, DataObjectCollection> collectionMap;
65  	protected List<String> removedCollectionNames;
66  
67  	protected List<DataObjectRelationship> relationships;
68  	protected Map<String, DataObjectRelationship> relationshipMap;
69  	protected List<String> removedRelationshipNames;
70  
71  	protected Map<String, List<DataObjectRelationship>> attributeToRelationshipMap;
72  	protected Map<String, DataObjectRelationship> lastAttributeToRelationshipMap;
73  
74  	protected List<String> primaryKeyAttributeNames;
75  	protected List<String> businessKeyAttributeNames;
76  	protected String primaryDisplayAttributeName;
77  	protected boolean primaryDisplayAttributeSetManually;
78  
79  	protected Boolean supportsOptimisticLocking;
80  
81  	protected Collection<UifAutoCreateViewType> autoCreateUifViewTypes;
82  
83  	public DataObjectMetadataImpl() {
84  	}
85  
86      /**
87      * {@inheritDoc}
88      */
89  	@Override
90  	public Object getUniqueKeyForMerging() {
91  		return type;
92  	}
93  
94      /**
95      * {@inheritDoc}
96      */
97  	@Override
98  	public Class<?> getType() {
99  		return type;
100 	}
101 
102     /**
103     * Sets unknown class to determine type.
104     *
105     * @param type unknown class
106     */
107 	public void setType(Class<?> type) {
108 		if (type == null) {
109 			throw new IllegalArgumentException("The data object type may not be set to null.");
110 		}
111 		this.type = type;
112 	}
113 
114     /**
115     * Gets type based on unknown class.
116     *
117     * @return class type or null
118     */
119 	public String getTypeClassName() {
120 		if (type == null) {
121 			return null;
122 		}
123 		return type.getName();
124 	}
125 
126 	/**
127 	 * This is really a helper method for cases where these objects may need to be built up via Spring XML.
128      *
129      * @param typeClassName class type
130 	 */
131 	public void setTypeClassName(String typeClassName) {
132         try {
133 			setType(Class.forName(typeClassName));
134         } catch (ClassNotFoundException e) {
135             throw new IllegalArgumentException("ClassNotFoundException when setting data object type class name "
136 					+ typeClassName, e);
137         }
138     }
139 
140     /**
141     * {@inheritDoc}
142     */
143 	@Override
144 	public List<String> getPrimaryKeyAttributeNames() {
145 		if (primaryKeyAttributeNames != null) {
146 			return primaryKeyAttributeNames;
147 		}
148 
149 		if (embedded != null) {
150 			return embedded.getPrimaryKeyAttributeNames();
151 		}
152 
153 		return Collections.emptyList();
154 	}
155 
156     /**
157     * Sets list of primary attribute names which make up key.
158     *
159     * @param primaryKeyAttributeNames list of attribute names.
160     */
161 	public void setPrimaryKeyAttributeNames(List<String> primaryKeyAttributeNames) {
162 		if (primaryKeyAttributeNames == null) {
163 			primaryKeyAttributeNames = Collections.emptyList();
164 		}
165 
166 		this.primaryKeyAttributeNames = Collections.unmodifiableList( primaryKeyAttributeNames );
167 	}
168 
169     /**
170     * {@inheritDoc}
171     */
172 	@Override
173 	public List<String> getBusinessKeyAttributeNames() {
174 		// If we have business keys, use that
175 		if (businessKeyAttributeNames != null) {
176 			return businessKeyAttributeNames;
177 		}
178 
179 		// Otherwise, if we have an explicit PK, use that
180 		if (primaryKeyAttributeNames != null) {
181 			return primaryKeyAttributeNames;
182 		}
183 
184 		// If neither has been set, go up the chain
185 		if (embedded != null) {
186 			return embedded.getBusinessKeyAttributeNames();
187 		}
188 
189 		return Collections.emptyList();
190 	}
191 
192     /**
193     * Sets list of attribute names that make up business key.
194     *
195     * @param businessKeyAttributeNames attribute names
196     */
197 	public void setBusinessKeyAttributeNames(List<String> businessKeyAttributeNames) {
198 		if (businessKeyAttributeNames == null) {
199 			businessKeyAttributeNames = Collections.emptyList();
200 		}
201 
202 		this.businessKeyAttributeNames = Collections.unmodifiableList(businessKeyAttributeNames);
203 	}
204 
205     /**
206     * {@inheritDoc}
207     */
208 	@Override
209 	public Boolean hasDistinctBusinessKey() {
210 		return !getPrimaryKeyAttributeNames().equals(getBusinessKeyAttributeNames());
211 	}
212 
213     /**
214     * {@inheritDoc}
215     */
216 	@Override
217 	public String getPrimaryDisplayAttributeName() {
218 		if (primaryDisplayAttributeName == null && !getBusinessKeyAttributeNames().isEmpty()) {
219 			primaryDisplayAttributeName = getBusinessKeyAttributeNames().get(getBusinessKeyAttributeNames().size() - 1);
220 		}
221 
222 		return primaryDisplayAttributeName;
223 		// Notes for potential use cases if deemed necessary to implement.
224 		// Since the last field of the PK does not generally change between
225 		// metadata layers, these cases may not need to be considered.
226 		// CASES:
227 		// 1) primaryDisplayAttributeName != null ==> primaryDisplayAttributeName
228 		// 2) primaryDisplayAttributeName == null && pk fields == null/empty && embedded != null ==>
229 		// embedded.getprimaryDisplayAttributeName()
230 		// 3) primaryDisplayAttributeName == null && pk fields == null/empty && embedded == null ==> null
231 		// 4) primaryDisplayAttributeName == null && pk fields != null/empty && embedded == null ==> last field of PK
232 		// 5) primaryDisplayAttributeName == null && pk fields != null/empty && embedded != null && embedded.primary
233 		// display == null ==> last field of PK
234 		// 6) primaryDisplayAttributeName == null && pk fields != null/empty && embedded != null && embedded.primary
235 		// display != null ==> embedded.getprimaryDisplayAttributeName()
236 		// If not set locally
237 		// need to check embedded
238 		// But - embedded could be dynamically or manually set as well
239 		// how do we detect whether it's been set explicitly on the embedded? If we don't, then we always get the last
240 		// attribute of the PK list on the embedding object
241 	}
242 
243     /**
244     * Sets list of attribute names used for display.
245     *
246     * @param primaryDisplayAttributeName list of attribute names.
247     */
248 	public void setPrimaryDisplayAttributeName(String primaryDisplayAttributeName) {
249 		if (StringUtils.isBlank(primaryDisplayAttributeName)) {
250 			this.primaryDisplayAttributeName = null;
251 			this.primaryDisplayAttributeSetManually = false;
252 		} else {
253 			this.primaryDisplayAttributeName = primaryDisplayAttributeName;
254 			this.primaryDisplayAttributeSetManually = true;
255 		}
256 	}
257 
258     /**
259     * Orders attributes by defined order.
260     *
261     * <p>
262     *     First looks to see if attributes are inherited, then looks at the declared fields based on the attribute
263     *     type.
264     * </p>
265     *
266     * @param attributes list of data object attributes
267     * @return re-ordered list of data object attributes
268     */
269 	public List<DataObjectAttribute> orderAttributesByDefinedOrder(List<DataObjectAttribute> attributes) {
270 		List<DataObjectAttribute> sorted = new ArrayList<DataObjectAttribute>(attributes.size());
271 		Map<String, DataObjectAttribute> keyedAttributes = new HashMap<String, DataObjectAttribute>(attributes.size());
272 		Map<String, List<DataObjectAttribute>> inheritedAttributes = new HashMap<String, List<DataObjectAttribute>>();
273 
274 		for (DataObjectAttribute attr : attributes) {
275 			if (attr.isInherited()) {
276 				List<DataObjectAttribute> inheritedByProperty = inheritedAttributes.get(attr
277 						.getInheritedFromParentAttributeName());
278 
279 				if (inheritedByProperty == null) {
280 					inheritedByProperty = new ArrayList<DataObjectAttribute>();
281 					inheritedAttributes.put(attr.getInheritedFromParentAttributeName(), inheritedByProperty);
282 				}
283 
284 				inheritedByProperty.add(attr);
285 			} else {
286 				keyedAttributes.put(attr.getName(), attr);
287 			}
288 		}
289 
290 		for (Field f : getType().getDeclaredFields()) {
291 			DataObjectAttribute attr = keyedAttributes.get(f.getName());
292 
293 			if (attr != null) {
294 				sorted.add(attr);
295 				keyedAttributes.remove(f.getName());
296 			}
297 
298 			if (inheritedAttributes.containsKey(f.getName())) {
299 				sorted.addAll(inheritedAttributes.get(f.getName()));
300 			}
301 		}
302 		sorted.addAll(keyedAttributes.values());
303 		return sorted;
304 	}
305 
306 	List<DataObjectAttribute> mergedAttributes = null;
307 
308     /**
309     * {@inheritDoc}
310     */
311 	@Override
312 	public List<DataObjectAttribute> getAttributes() {
313 		// We have a local list and no overrides - return the existing list
314 		if (attributes != null && embedded == null) {
315 			return orderAttributesByDefinedOrder(attributes);
316 		}
317 
318 		if (embedded != null) {
319 			return orderAttributesByDefinedOrder(mergeLists(embedded.getAttributes(), attributes));
320 		}
321 
322 		return Collections.emptyList();
323 	}
324 
325     /**
326     * Sets attributes.
327      *
328      * <p>
329      *     Looks at merge actions when adding, so not all attributes are added.
330      * </p>
331     *
332     * @param attributes list of data object attributes
333     */
334 	public void setAttributes(List<DataObjectAttribute> attributes) {
335 		if (attributes == null) {
336 			attributes = Collections.emptyList();
337 		}
338 
339 		this.attributes = Collections.unmodifiableList(attributes);
340 		mergedAttributes = null;
341 		attributeMap = new HashMap<String, DataObjectAttribute>(attributes.size());
342 		removedAttributeNames = new ArrayList<String>();
343 		for (DataObjectAttribute attr : attributes) {
344 			// TODO: This is not quite correct - we really only want to not add the NO_OVERRIDE items if they are
345 			// overriding something. However, at the point this is running, we don't know whether we will be embedding
346 			// anything...
347 			if (attr.getMergeAction() != MetadataMergeAction.REMOVE
348 					&& attr.getMergeAction() != MetadataMergeAction.NO_OVERRIDE) {
349 				attributeMap.put(attr.getName(), attr);
350 			}
351 
352 			// since the attribute will still exist in the embedded metadata, we need to put a block in on the standard
353 			// cascade
354 			if (attr.getMergeAction() == MetadataMergeAction.REMOVE) {
355 				removedAttributeNames.add(attr.getName());
356 			}
357 		}
358 	}
359 
360     /**
361     * {@inheritDoc}
362     */
363 	@Override
364 	public List<DataObjectCollection> getCollections() {
365 		// We have a local list and no overrides - return the existing list
366 		if (collections != null && embedded == null) {
367 			return collections;
368 		}
369 
370 		if (embedded != null) {
371 			return mergeLists(embedded.getCollections(), collections);
372 		}
373 
374 		return Collections.emptyList();
375 	}
376 
377     /**
378     * Sets collections.
379     *
380     * <p>
381     *     Looks at merge actions when adding, so not all collections are added.
382     * </p>
383     *
384     * @param collections list of data object collections or null
385     */
386 	public void setCollections(List<DataObjectCollection> collections) {
387 		if (collections == null) {
388 			this.collections = null;
389 			return;
390 		}
391 
392 		this.collections = Collections.unmodifiableList(collections);
393 		collectionMap = new HashMap<String, DataObjectCollection>(collections.size());
394 		removedCollectionNames = new ArrayList<String>();
395 		for (DataObjectCollection coll : collections) {
396 			// This is not quite correct - we really only want to not add the NO_OVERRIDE items if they are
397 			// overriding something. However, at the point this is running, we don't know whether we will be embedding
398 			// anything...
399 			if (coll.getMergeAction() != MetadataMergeAction.REMOVE
400 					&& coll.getMergeAction() != MetadataMergeAction.NO_OVERRIDE) {
401 				collectionMap.put(coll.getName(), coll);
402 			}
403 
404 			// since the attribute will still exist in the embedded metadata, we need to put a block in on the standard
405 			// cascade
406 			if (coll.getMergeAction() == MetadataMergeAction.REMOVE) {
407 				removedCollectionNames.add(coll.getName());
408 			}
409 		}
410 	}
411 
412     /**
413     * {@inheritDoc}
414     */
415 	@Override
416 	public List<DataObjectRelationship> getRelationships() {
417 		// We have a local list and no overrides - return the existing list
418 		if (relationships != null && embedded == null) {
419 			return relationships;
420 		}
421 
422 		if (embedded != null) {
423 			return mergeLists(embedded.getRelationships(), relationships);
424 		}
425 
426 		return Collections.emptyList();
427 	}
428 
429     /**
430     * Sets relationships.
431     *
432     * <p>
433     *     Looks at merge actions and whether the relationship is empty when adding, so not all relationships are added.
434     * </p>
435     *
436     * @param relationships list of data object relationships or null
437     */
438 	public void setRelationships(List<DataObjectRelationship> relationships) {
439 		if (relationships == null) {
440 			this.relationships = null;
441 			relationshipMap = null;
442 			lastAttributeToRelationshipMap = null;
443 			attributeToRelationshipMap = null;
444 			return;
445 		}
446 
447 		this.relationships = Collections.unmodifiableList(relationships);
448 		relationshipMap = new HashMap<String, DataObjectRelationship>(relationships.size());
449 		attributeToRelationshipMap = new HashMap<String, List<DataObjectRelationship>>();
450 		lastAttributeToRelationshipMap = new HashMap<String, DataObjectRelationship>(relationships.size());
451 		removedRelationshipNames = new ArrayList<String>();
452 
453 		// Builds maps to link attribute names to their relationships
454 		for (DataObjectRelationship rel : relationships) {
455 			// This is not quite correct - we really only want to not add the NO_OVERRIDE items if they are
456 			// overriding something. However, at the point this is running, we don't know whether we will be embedding
457 			// anything...
458 			if (rel.getMergeAction() != MetadataMergeAction.REMOVE
459 					&& rel.getMergeAction() != MetadataMergeAction.NO_OVERRIDE) {
460 				// related object attribute name
461 				relationshipMap.put(rel.getName(), rel);
462 
463 				// last attribute in list linking the objects
464 				if (!rel.getAttributeRelationships().isEmpty()) {
465 					DataObjectAttributeRelationship relAttr = rel.getAttributeRelationships().get(
466 							rel.getAttributeRelationships().size() - 1);
467 					lastAttributeToRelationshipMap.put(relAttr.getParentAttributeName(), rel);
468 				}
469 
470 				// all relationships relating to an attribute
471 				for (DataObjectAttributeRelationship relAttr : rel.getAttributeRelationships()) {
472 					List<DataObjectRelationship> rels = attributeToRelationshipMap
473 							.get(relAttr.getParentAttributeName());
474 					if (rels == null) {
475 						rels = new ArrayList<DataObjectRelationship>();
476 						attributeToRelationshipMap.put(relAttr.getParentAttributeName(), rels);
477 					}
478 					rels.add(rel);
479 				}
480 			}
481 
482 			// since the attribute will still exist in the embedded metadata, we need to put a block in on the standard
483 			// cascade
484 			if (rel.getMergeAction() == MetadataMergeAction.REMOVE) {
485 				removedRelationshipNames.add(rel.getName());
486 			}
487 		}
488 		relationshipMap = Collections.unmodifiableMap(relationshipMap);
489 		lastAttributeToRelationshipMap = Collections.unmodifiableMap(lastAttributeToRelationshipMap);
490 		attributeToRelationshipMap = Collections.unmodifiableMap(attributeToRelationshipMap);
491 	}
492 
493     /**
494     * {@inheritDoc}
495     */
496 	@Override
497 	public DataObjectAttribute getAttribute(String attributeName) {
498 		if (attributeName == null) {
499 			return null;
500 		}
501 
502 		DataObjectAttribute attribute = null;
503 
504 		// attempt to get it from the local attribute map (if any attributed defined locally)
505 		if (attributes != null) {
506 			attribute = attributeMap.get(attributeName);
507 		}
508 
509 		// if we don't find one, but we have an embedded metadata object, check it
510 		if (attribute == null && embedded != null) {
511 			attribute = embedded.getAttribute(attributeName);
512 			// but, ensure it's not on the removed attribute list
513 			if (attribute != null && removedAttributeNames != null
514 					&& removedAttributeNames.contains(attribute.getName())) {
515 				attribute = null;
516 			}
517 		}
518 
519 		return attribute;
520 	}
521 
522     /**
523     * {@inheritDoc}
524     */
525 	@Override
526 	public DataObjectCollection getCollection(String collectionName) {
527 		if (collectionName == null) {
528 			return null;
529 		}
530 
531 		DataObjectCollection collection = null;
532 
533 		// attempt to get it from the local attribute map (if any attributed defined locally)
534 		if (collections != null) {
535 			collection = collectionMap.get(collectionName);
536 		}
537 
538 		// if we don't find one, but we have an embedded metadata object, check it
539 		if (collection == null && embedded != null) {
540 			collection = embedded.getCollection(collectionName);
541 			// but, ensure it's not on the removed attribute list
542 			if (collection != null && removedCollectionNames != null
543 					&& removedCollectionNames.contains(collection.getName())) {
544 				collection = null;
545 			}
546 		}
547 
548 		return collection;
549 	}
550 
551     /**
552     * {@inheritDoc}
553     */
554 	@Override
555 	public DataObjectRelationship getRelationship(String relationshipName) {
556 		if (relationshipName == null) {
557 			return null;
558 		}
559 
560 		DataObjectRelationship relationship = null;
561 
562 		// attempt to get it from the local attribute map (if any attributed defined locally)
563 		if (relationships != null) {
564 			relationship = relationshipMap.get(relationshipName);
565 		}
566 
567 		// if we don't find one, but we have an embedded metadata object, check it
568 		if (relationship == null && embedded != null) {
569 			relationship = embedded.getRelationship(relationshipName);
570 			// but, ensure it's not on the removed attribute list
571 			if (relationship != null && removedRelationshipNames != null
572 					&& removedRelationshipNames.contains(relationship.getName())) {
573 				relationship = null;
574 			}
575 		}
576 
577 		return relationship;
578 	}
579 
580     /**
581     * {@inheritDoc}
582     */
583 	@Override
584 	public List<DataObjectRelationship> getRelationshipsInvolvingAttribute(String attributeName) {
585 		// somewhat complex, since it returns a list of all possible relationships
586 		if (StringUtils.isBlank(attributeName)) {
587 			return null;
588 		}
589 
590 		Map<Object, DataObjectRelationship> relationships = new HashMap<Object, DataObjectRelationship>();
591 		// Look locally
592 		if (attributeToRelationshipMap != null && attributeToRelationshipMap.containsKey(attributeName)) {
593 			for (DataObjectRelationship rel : attributeToRelationshipMap.get(attributeName)) {
594 				Object mergeKey = rel.getName();
595 
596 				if (rel instanceof MetadataCommonInternal) {
597 					mergeKey = ((MetadataCommonInternal) rel).getUniqueKeyForMerging();
598 				}
599 
600 				relationships.put(mergeKey, rel);
601 			}
602 		}
603 
604 		// now, if we have an embedded object, look for matching ones, but exclude if the relationship is the same
605 		// as that means it was overridden by this bean
606 		if (embedded != null) {
607 			for (DataObjectRelationship rel : embedded.getRelationshipsInvolvingAttribute(attributeName)) {
608 				Object mergeKey = rel.getName();
609 
610 				if (rel instanceof MetadataCommonInternal) {
611 					mergeKey = ((MetadataCommonInternal) rel).getUniqueKeyForMerging();
612 				}
613 
614 				if (!relationships.containsKey(mergeKey)) {
615 					relationships.put(mergeKey, rel);
616 				}
617 			}
618 		}
619 
620 		return new ArrayList<DataObjectRelationship>(relationships.values());
621 	}
622 
623     /**
624     * {@inheritDoc}
625     */
626 	@Override
627 	public DataObjectRelationship getRelationshipByLastAttributeInRelationship(String attributeName) {
628 		// this returns a single record, so we can just use the first matching one we find
629 		if (StringUtils.isBlank(attributeName)) {
630 			return null;
631 		}
632 
633 		DataObjectRelationship relationship = null;
634 
635 		// Look locally
636 		if (lastAttributeToRelationshipMap != null) {
637 			relationship = lastAttributeToRelationshipMap.get(attributeName);
638 		}
639 
640 		// if nothing found local, recurse into the embedded provider
641 		if (relationship == null && embedded != null) {
642 			relationship = embedded.getRelationshipByLastAttributeInRelationship(attributeName);
643 		}
644 
645 		return relationship;
646 	}
647 
648     /**
649     * {@inheritDoc}
650     */
651 	@Override
652 	public DataObjectMetadataInternal getEmbedded() {
653 		return embedded;
654 	}
655 
656     /**
657     * {@inheritDoc}
658     */
659 	@Override
660 	public void setEmbedded(DataObjectMetadataInternal embedded) {
661 		this.embedded = embedded;
662 		setEmbeddedCommonMetadata(embedded);
663 	}
664 
665     /**
666     * Gets the metadata source.
667     *
668     * <p>
669     * Helper property to allow identification of the source of metadata. Value is transient, so it will not survive
670     * serialization.
671     * </p>
672     *
673     * @return metadata source
674     */
675 	public String getProviderName() {
676 		return providerName;
677 	}
678 
679     /**
680     * Sets provider name.
681     *
682     * @param providerName name of provider
683     */
684 	public void setProviderName(String providerName) {
685 		this.providerName = providerName;
686 	}
687 
688     /**
689     * {@inheritDoc}
690     */
691 	@Override
692 	public String toString() {
693 		StringBuilder builder = new StringBuilder();
694 		builder.append("DataObjectMetadata [");
695 		builder.append("type=").append(getType()).append(", ");
696 		builder.append("typeLabel=").append(label).append(", ");
697 		builder.append("backingObjectName=").append(backingObjectName);
698 		if (attributes != null && !attributes.isEmpty()) {
699 			builder.append(", ").append("attributes=").append(attributes);
700 		}
701 		if (primaryKeyAttributeNames != null && !primaryKeyAttributeNames.isEmpty()) {
702 			builder.append(", ").append("primaryKeyAttributeNames=").append(primaryKeyAttributeNames);
703 		}
704 		if (getPrimaryDisplayAttributeName() != null) {
705 			builder.append(", ").append("primaryDisplayAttributeName=").append(getPrimaryDisplayAttributeName());
706 		}
707 		if (businessKeyAttributeNames != null && !businessKeyAttributeNames.isEmpty()) {
708 			builder.append(", ").append("businessKeyAttributeNames=").append(businessKeyAttributeNames);
709 		}
710 		if (collections != null && !collections.isEmpty()) {
711 			builder.append(", ").append("collections=").append(collections);
712 		}
713 		if (relationships != null && !relationships.isEmpty()) {
714 			builder.append(", ").append("relationships=").append(relationships);
715 		}
716 		if (providerName != null) {
717 			builder.append(", ").append("providerName=").append(providerName);
718 		}
719 		if (embedded != null) {
720 			builder.append(", ").append("mergeAction=").append(mergeAction);
721 			builder.append(", ").append("embedded=").append(embedded);
722 		}
723 		builder.append("]");
724 		return builder.toString();
725 	}
726 
727     /**
728     * {@inheritDoc}
729     */
730 	@Override
731 	public boolean isSupportsOptimisticLocking() {
732 		if (supportsOptimisticLocking != null) {
733 			return supportsOptimisticLocking;
734 		}
735 
736 		if (embedded != null) {
737 			return embedded.isSupportsOptimisticLocking();
738 		}
739 
740 		return false;
741 	}
742 
743     /**
744     * Sets whether optimistic locking is supported.
745     *
746     * @param supportsOptimisticLocking whether optimistic locking is supported
747     */
748 	public void setSupportsOptimisticLocking(boolean supportsOptimisticLocking) {
749 		this.supportsOptimisticLocking = supportsOptimisticLocking;
750 	}
751 
752     /**
753     * {@inheritDoc}
754     */
755 	@Override
756     @Beta
757 	public boolean shouldAutoCreateUifViewOfType(UifAutoCreateViewType viewType) {
758 		if (getAutoCreateUifViewTypes() == null) {
759 			return false;
760 		}
761 
762 		return getAutoCreateUifViewTypes().contains(viewType)
763 				|| getAutoCreateUifViewTypes().contains(UifAutoCreateViewType.ALL);
764 	}
765 
766     /**
767     * {@inheritDoc}
768     */
769 	@Override
770     @Beta
771 	public Collection<UifAutoCreateViewType> getAutoCreateUifViewTypes() {
772 		if (autoCreateUifViewTypes != null) {
773 			return autoCreateUifViewTypes;
774 		}
775 
776 		if (embedded != null) {
777 			return embedded.getAutoCreateUifViewTypes();
778 		}
779 
780 		return null;
781 	}
782 
783     /**
784     * BETA: Sets list of UIF view types that will be auto created.
785     *
786     * @param autoCreateUifViewTypes UIF view types
787     */
788     @Beta
789 	public void setAutoCreateUifViewTypes(Collection<UifAutoCreateViewType> autoCreateUifViewTypes) {
790 		this.autoCreateUifViewTypes = autoCreateUifViewTypes;
791 	}
792 
793     /**
794     * Gets sorted attribute list.
795     *
796     * @return ordered attribute list
797     */
798 	public List<String> getOrderedAttributeList() {
799 		return orderedAttributeList;
800 	}
801 
802     /**
803     * Sets sorted attribute list.
804     *
805     * @param orderedAttributeList sorted attributes
806     */
807 	public void setOrderedAttributeList(List<String> orderedAttributeList) {
808 		this.orderedAttributeList = orderedAttributeList;
809 	}
810 
811 }