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.lang.reflect.Field;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import org.apache.commons.lang.StringUtils;
027import org.kuali.rice.krad.data.metadata.DataObjectAttribute;
028import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
029import org.kuali.rice.krad.data.metadata.DataObjectCollection;
030import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
031import org.kuali.rice.krad.data.metadata.MetadataMergeAction;
032import org.kuali.rice.krad.data.provider.annotation.UifAutoCreateViewType;
033
034import com.google.common.annotations.Beta;
035
036/**
037 * Base implementation class for the metadata related to the data object as a whole.
038 *
039 * <p>
040 * Contains lists of all child elements.
041 * </p>
042 * 
043 * @author Kuali Rice Team (rice.collab@kuali.org)
044 */
045public class DataObjectMetadataImpl extends MetadataCommonBase implements DataObjectMetadataInternal {
046        private static final long serialVersionUID = 7722982931510558892L;
047
048        protected DataObjectMetadataInternal embedded;
049        /**
050         * Property used to help with debugging.
051         * 
052         * It is used in the toString() method so you can determine from which provider this metadata was extracted.
053         */
054        protected String providerName;
055
056        protected Class<?> type;
057
058        protected List<DataObjectAttribute> attributes;
059        protected Map<String, DataObjectAttribute> attributeMap;
060        protected List<String> removedAttributeNames;
061        protected List<String> orderedAttributeList = new ArrayList<String>();
062
063        protected List<DataObjectCollection> collections;
064        protected Map<String, DataObjectCollection> collectionMap;
065        protected List<String> removedCollectionNames;
066
067        protected List<DataObjectRelationship> relationships;
068        protected Map<String, DataObjectRelationship> relationshipMap;
069        protected List<String> removedRelationshipNames;
070
071        protected Map<String, List<DataObjectRelationship>> attributeToRelationshipMap;
072        protected Map<String, DataObjectRelationship> lastAttributeToRelationshipMap;
073
074        protected List<String> primaryKeyAttributeNames;
075        protected List<String> businessKeyAttributeNames;
076        protected String primaryDisplayAttributeName;
077        protected boolean primaryDisplayAttributeSetManually;
078
079        protected Boolean supportsOptimisticLocking;
080
081        protected Collection<UifAutoCreateViewType> autoCreateUifViewTypes;
082
083        public DataObjectMetadataImpl() {
084        }
085
086    /**
087    * {@inheritDoc}
088    */
089        @Override
090        public Object getUniqueKeyForMerging() {
091                return type;
092        }
093
094    /**
095    * {@inheritDoc}
096    */
097        @Override
098        public Class<?> getType() {
099                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}