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