001package org.andromda.cartridges.ejb3.metafacades;
002
003import java.text.MessageFormat;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Hashtable;
007import java.util.Map;
008import org.andromda.cartridges.ejb3.EJB3Globals;
009import org.andromda.cartridges.ejb3.EJB3Profile;
010import org.andromda.metafacades.uml.AssociationEndFacade;
011import org.andromda.metafacades.uml.AttributeFacade;
012import org.andromda.metafacades.uml.ClassifierFacade;
013import org.andromda.metafacades.uml.Entity;
014import org.andromda.metafacades.uml.EntityMetafacadeUtils;
015import org.andromda.metafacades.uml.MetafacadeUtils;
016import org.andromda.metafacades.uml.ModelElementFacade;
017import org.andromda.metafacades.uml.TaggedValueFacade;
018import org.andromda.metafacades.uml.TypeMappings;
019import org.andromda.metafacades.uml.UMLMetafacadeProperties;
020import org.andromda.metafacades.uml.UMLMetafacadeUtils;
021import org.andromda.metafacades.uml.UMLProfile;
022import org.apache.commons.lang.BooleanUtils;
023import org.apache.commons.lang.ObjectUtils;
024import org.apache.commons.lang.StringUtils;
025
026/**
027 * <p/>
028 * Represents an EJB association end. </p>
029 * MetafacadeLogic implementation for org.andromda.cartridges.ejb3.metafacades.EJB3AssociationEndFacade.
030 *
031 * @see EJB3AssociationEndFacade
032 */
033public class EJB3AssociationEndFacadeLogicImpl
034    extends EJB3AssociationEndFacadeLogic
035{
036    private static final long serialVersionUID = 34L;
037    /**
038     * The default composite association cascade property
039     */
040    private static final String ENTITY_DEFAULT_COMPOSITE_CASCADE = "entityCompositeCascade";
041
042    /**
043     * The default aggregation association cascade property
044     */
045    private static final String ENTITY_DEFAULT_AGGREGATION_CASCADE = "entityAggregationCascade";
046
047    /**
048     * The namespace property storing default collection type for associations
049     */
050    private static final String ASSOCIATION_COLLECTION_TYPE = "associationCollectionType";
051
052    /**
053     * A flag indicating whether or not specific (java.util.Set, java.util.List,
054     * etc) collection interfaces should be used in assocation mutators and
055     * accessors or whether the generic java.util.Collection interface should be
056     * used.
057     */
058    private static final String SPECIFIC_COLLECTION_INTERFACES = "specificCollectionInterfaces";
059
060    /**
061     * The property that defines the default collection interface, this is the
062     * interface used if the property defined by
063     * {@link #SPECIFIC_COLLECTION_INTERFACES} is false.
064     */
065    private static final String DEFAULT_COLLECTION_INTERFACE = "defaultCollectionInterface";
066
067    /**
068     * Stores the default collection index name.
069     */
070    private static final String COLLECTION_INDEX_NAME = "associationEndCollectionIndexName";
071
072    /**
073     * Stores the default collection index type.
074     */
075    private static final String COLLECTION_INDEX_TYPE = "associationEndCollectionIndexType";
076
077    /**
078     * Represents the EJB3 <code>ALL</code> cascade option and fully qualified representation.
079     */
080    private static final String ENTITY_CASCADE_ALL = "ALL";
081    private static final String ENTITY_CASCADE_ALL_FQN = "javax.persistence.CascadeType.ALL";
082
083    /**
084     * Represents the EJB3 <code>PERSIST</code> cascade option.
085     */
086    private static final String ENTITY_CASCADE_PERSIST = "PERSIST";
087    private static final String ENTITY_CASCADE_PERSIST_FQN = "javax.persistence.CascadeType.PERSIST";
088
089    /**
090     * Represents the EJB3 <code>MERGE</code> cascade option.
091     */
092    private static final String ENTITY_CASCADE_MERGE = "MERGE";
093    private static final String ENTITY_CASCADE_MERGE_FQN = "javax.persistence.CascadeType.MERGE";
094
095    /**
096     * Represents the EJB3 <code>REMOVE</code> cascade option.
097     */
098    private static final String ENTITY_CASCADE_REMOVE = "REMOVE";
099    private static final String ENTITY_CASCADE_REMOVE_FQN = "javax.persistence.CascadeType.REMOVE";
100
101    /**
102     * Represents the EJB3 <code>REFRESH</code> cascade option.
103     */
104    private static final String ENTITY_CASCADE_REFRESH = "REFRESH";
105    private static final String ENTITY_CASCADE_REFRESH_FQN = "javax.persistence.CascadeType.REFRESH";
106
107    /**
108     * Represents the value used to represents NO cascade option.
109     */
110    private static final String ENTITY_CASCADE_NONE = "NONE";
111
112    /**
113     * Stores the cascade map of fully qualified cascade types
114     */
115    private static final Map<String, String> cascadeTable = new Hashtable<String, String>();
116
117    static
118    {
119        cascadeTable.put(ENTITY_CASCADE_ALL, ENTITY_CASCADE_ALL_FQN);
120        cascadeTable.put(ENTITY_CASCADE_PERSIST, ENTITY_CASCADE_PERSIST_FQN);
121        cascadeTable.put(ENTITY_CASCADE_MERGE, ENTITY_CASCADE_MERGE_FQN);
122        cascadeTable.put(ENTITY_CASCADE_REMOVE, ENTITY_CASCADE_REMOVE_FQN);
123        cascadeTable.put(ENTITY_CASCADE_REFRESH, ENTITY_CASCADE_REFRESH_FQN);
124    }
125
126    /**
127     * Value for set
128     */
129    private static final String COLLECTION_TYPE_SET = "set";
130
131    /**
132     * Value for map
133     */
134    private static final String COLLECTION_TYPE_MAP = "map";
135
136    /**
137     * Value for list
138     */
139    private static final String COLLECTION_TYPE_LIST = "list";
140
141    /**
142     * Value for collections
143     */
144    private static final String COLLECTION_TYPE_COLLECTION = "bag";
145
146    /**
147     * Stores the valid collection types
148     */
149    private static final Collection<String> collectionTypes = new ArrayList<String>();
150
151    static
152    {
153        collectionTypes.add(COLLECTION_TYPE_SET);
154        collectionTypes.add(COLLECTION_TYPE_MAP);
155        collectionTypes.add(COLLECTION_TYPE_LIST);
156        collectionTypes.add(COLLECTION_TYPE_COLLECTION);
157    }
158
159    /**
160     * Stores the property indicating whether or not composition should define
161     * the eager loading strategy and aggregation define lazy loading strategy.
162     */
163    private static final String COMPOSITION_DEFINES_EAGER_LOADING = "compositionDefinesEagerLoading";
164
165    /**
166     * The property that stores whether relationship collection caching is enabled.
167     */
168    private static final String HIBERNATE_ASSOCIATION_ENABLE_CACHE = "hibernateEnableAssociationsCache";
169
170    /**
171     * Stores the default cache strategy for relationship Collections.
172     */
173    private static final String HIBERNATE_ASSOCIATION_CACHE = "hibernateAssociationCache";
174
175    /**
176     * The 'list' type implementation to use.
177     */
178    private static final String LIST_TYPE_IMPLEMENTATION = "listTypeImplementation";
179
180    /**
181     * The 'set' type implementation to use.
182     */
183    private static final String SET_TYPE_IMPLEMENTATION = "setTypeImplementation";
184
185    /**
186     * The 'map' type implementation to use.
187     */
188    private static final String MAP_TYPE_IMPLEMENTATION = "mapTypeImplementation";
189
190    // ---------------- constructor -------------------------------
191
192    /**
193     * @param metaObject
194     * @param context
195     */
196    public EJB3AssociationEndFacadeLogicImpl(final Object metaObject, final String context)
197    {
198        super (metaObject, context);
199    }
200
201    // --------------- methods ---------------------
202
203    /**
204     * @see AssociationEndFacade#getGetterSetterTypeName()
205     */
206    @Override public String getGetterSetterTypeName()
207    {
208        String getterSetterTypeName = null;
209
210        if (this.isMany())
211        {
212            final boolean specificInterfaces =
213                Boolean.valueOf(
214                    ObjectUtils.toString(this.getConfiguredProperty(SPECIFIC_COLLECTION_INTERFACES))).booleanValue();
215
216            final TypeMappings mappings = this.getLanguageMappings();
217            if (mappings != null)
218            {
219                if (this.isMap())
220                {
221                    getterSetterTypeName = mappings.getTo(UMLProfile.MAP_TYPE_NAME);
222                }
223                else if (specificInterfaces)
224                {
225                    if (this.isSet())
226                    {
227                        getterSetterTypeName = mappings.getTo(UMLProfile.SET_TYPE_NAME);
228                    }
229                    else if (this.isList())
230                    {
231                        getterSetterTypeName = mappings.getTo(UMLProfile.LIST_TYPE_NAME);
232                    }
233                    else if (this.isCollection())
234                    {
235                        getterSetterTypeName = mappings.getTo(UMLProfile.COLLECTION_TYPE_NAME);
236                    }
237                }
238                else
239                {
240                    getterSetterTypeName = this.getDefaultCollectionInterface();
241                }
242            }
243            else
244            {
245                getterSetterTypeName = this.getDefaultCollectionInterface();
246            }
247        }
248        else
249        {
250            final ClassifierFacade type = this.getType();
251            if (type instanceof EJB3EntityFacade)
252            {
253                final String typeName = ((EJB3EntityFacade)type).getFullyQualifiedEntityName();
254                if (StringUtils.isNotBlank(typeName))
255                {
256                    getterSetterTypeName = typeName;
257                }
258            }
259        }
260
261        if (getterSetterTypeName == null)
262        {
263            getterSetterTypeName = super.getGetterSetterTypeName();
264        }
265        else if (this.isMany())
266        {
267            /**
268             * Set this association end's type as a template parameter if required
269             */
270            if ("true".equals(this.getConfiguredProperty(UMLMetafacadeProperties.ENABLE_TEMPLATING)))
271            {
272                getterSetterTypeName += '<'
273                    + (this.isMap() ? this.getCollectionIndexType() + ", " : "")
274                    + this.getType().getFullyQualifiedName() + '>';
275            }
276        }
277        return getterSetterTypeName;
278    }
279
280    /**
281     * Overridden to provide handling of inheritance.
282     *
283     * @see AssociationEndFacade#isRequired()
284     */
285    @Override
286    public boolean isRequired()
287    {
288        // TODO: Fix UML Association isRequired to use getOtherEnd
289        boolean required = super.isRequired();
290        Object type = this.getOtherEnd().getType();
291
292        if ((type != null) && EJB3EntityFacade.class.isAssignableFrom(type.getClass()))
293        {
294            EJB3EntityFacade entity = (EJB3EntityFacade)type;
295
296            /**
297             * Exclude ONLY if single table inheritance exists
298             */
299            if (entity.isRequiresGeneralizationMapping() && entity.isInheritanceSingleTable()
300                    && !entity.isEmbeddableSuperclassGeneralizationExists())
301            {
302                required = false;
303            }
304        }
305
306        return required;
307    }
308
309    /**
310     * @return fetch type
311     * @see EJB3AssociationEndFacade#getFetchType()
312     */
313    @Override
314    protected String handleGetFetchType()
315    {
316        String fetchType = (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_PERSISTENCE_FETCH_TYPE);
317
318        if (StringUtils.isBlank(fetchType))
319        {
320            // check whether or not composition defines eager loading is turned on
321            final boolean compositionDefinesEagerLoading =
322                Boolean.valueOf(String.valueOf(this.getConfiguredProperty(COMPOSITION_DEFINES_EAGER_LOADING)))
323                       .booleanValue();
324
325            if (compositionDefinesEagerLoading)
326            {
327                if (this.getOtherEnd().isComposition())
328                {
329                    fetchType = EJB3Globals.FETCH_TYPE_EAGER;
330                }
331                else if (this.getOtherEnd().isAggregation())
332                {
333                    fetchType = EJB3Globals.FETCH_TYPE_LAZY;
334                }
335            }
336        }
337
338        /**
339         * Go for defaults if blank
340         */
341        if (StringUtils.isBlank(fetchType))
342        {
343            if (this.getOtherEnd().isOne2Many() || this.getOtherEnd().isMany2Many())
344            {
345                fetchType = EJB3Globals.FETCH_TYPE_LAZY;
346            }
347            else
348            {
349                fetchType = EJB3Globals.FETCH_TYPE_EAGER;
350            }
351        }
352        return fetchType;
353    }
354
355    /**
356     * @see EJB3AssociationEndFacadeLogic#handleIsEager()
357     */
358    @Override
359    protected boolean handleIsEager()
360    {
361        boolean isEager = false;
362        if (StringUtils.isNotBlank(this.getFetchType()))
363        {
364            if (EJB3Globals.FETCH_TYPE_EAGER.equalsIgnoreCase(this.getFetchType()))
365            {
366                isEager = true;
367            }
368        }
369        return isEager;
370    }
371
372    /**
373     * @see EJB3AssociationEndFacadeLogic#handleIsLazy()
374     */
375    @Override
376    protected boolean handleIsLazy()
377    {
378        boolean isLazy = false;
379        if (StringUtils.isNotBlank(this.getFetchType()))
380        {
381            if (EJB3Globals.FETCH_TYPE_LAZY.equalsIgnoreCase(this.getFetchType()))
382            {
383                isLazy = true;
384            }
385        }
386        return isLazy;
387    }
388
389    /**
390     * @see EJB3AssociationEndFacadeLogic#handleIsOwning()
391     */
392    @Override
393    protected boolean handleIsOwning()
394    {
395        return !UMLMetafacadeUtils.isOwningEnd(this);
396        /*boolean owning = false;
397        if (this.isAggregation() || this.isComposition() || !this.getOtherEnd().isNavigable()
398            || BooleanUtils.toBoolean(
399                ObjectUtils.toString(this.findTaggedValue(
400                        EJB3Profile.TAGGEDVALUE_PERSISTENCE_ASSOCIATION_END_PRIMARY))))
401        {
402            owning = true;
403        }
404        // See if this end or the other end is tagged as the association owner
405        else if (BooleanUtils.toBoolean(
406            ObjectUtils.toString(this.getOtherEnd().findTaggedValue(
407                    EJB3Profile.TAGGEDVALUE_PERSISTENCE_ASSOCIATION_END_PRIMARY))))
408        {
409            owning = false;
410        }
411        // If other end not tagged, the many side of 1:M owns the bidirectional relationship
412        else if (this.isMany() && !this.getOtherEnd().isMany())
413        {
414            owning = true;
415        }
416        // Other side: aggregation/composition owns the bidirectional relationship
417        else if (this.getOtherEnd().isAggregation() || this.getOtherEnd().isComposition())
418        {
419            owning = false;
420        }
421        // Other side: the many side of 1:M owns the bidirectional relationship if no composition/aggregation
422        else if (!this.isMany() && this.getOtherEnd().isMany())
423        {
424            owning = false;
425        }
426        // If bidirectional 1:1 or M:M, choose the side with the longest type name because it typically indicates a composition relationship
427        //else if (this.getOtherEnd().getType().getName().length()
428        //        > this.getType().getName().length())
429        else if (this.getName().length()
430            > this.getOtherEnd().getName().length())
431        {
432            owning = true;
433        }
434        // If length is the same, alphabetically earliest is the owner
435        else if (this.getName().compareTo(
436                this.getOtherEnd().getName()) < 0)
437        {
438            owning = true;
439        }
440        //System.out.println("handleIsOwning=" + owning + " for " + this.getName() + " OName=" + this.getOtherEnd().getName() + " Aggregation=" + this.isAggregation() + " Aggregation=" + this.isAggregation() + " Composition=" + this.isComposition() + " !Navigable=" + !this.isNavigable() + " Many=" + this.isMany() + " OMany=" + this.getOtherEnd().isMany() + " Upper=" + this.getUpper() + " OUpper=" + this.getOtherEnd().getUpper() + " OAggregation=" + this.getOtherEnd().isAggregation() + " OComposition=" + this.getOtherEnd().isComposition() + " ONavigable=" + this.getOtherEnd().isNavigable() + this);
441        return owning;*/
442    }
443
444    /**
445     * @see EJB3AssociationEndFacadeLogic#handleIsOptional()
446     */
447    @Override
448    protected boolean handleIsOptional()
449    {
450        boolean optional = true;
451        String optionalString = (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_PERSISTENCE_OPTIONAL);
452
453        if (StringUtils.isBlank(optionalString))
454        {
455            optional = !this.isRequired();
456        }
457        else
458        {
459            optional = Boolean.valueOf(optionalString).booleanValue();
460        }
461        return optional;
462    }
463
464    /**
465     * @see EJB3AssociationEndFacadeLogic#handleGetOrderByClause()
466     */
467    @Override
468    protected String handleGetOrderByClause()
469    {
470        return (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_PERSISTENCE_ORDERBY);
471    }
472
473    /**
474     * @see EJB3AssociationEndFacadeLogic#handleGetColumnDefinition()
475     */
476    @Override
477    protected String handleGetColumnDefinition()
478    {
479        return (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_PERSISTENCE_COLUMN_DEFINITION);
480    }
481
482    /**
483     * Returns true if the tagged name exists for this association end.
484     *
485     * @param name The tagged name to lookup.
486     * @return boolean True if the tagged name exists.  False otherwise.
487     *
488     * @see EJB3AssociationEndFacadeLogic#handleHasTaggedValue(String)
489     */
490    @Override
491    protected boolean handleHasTaggedValue(String name)
492    {
493        boolean exists = false;
494        if (StringUtils.isNotBlank(name))
495        {
496            // trim to remove leading/trailing spaces
497            name = StringUtils.trimToEmpty(name);
498
499            // loop over tagged values and match the argument tagged value name
500            for (TaggedValueFacade taggedValue : this.getTaggedValues())
501            {
502                // return with true on the first match found
503                //if (name.equals(taggedValue.getName()))
504                String tagName = taggedValue.getName();
505                if (name.equals(tagName) || MetafacadeUtils.getEmfTaggedValue(name).equals(tagName)
506                    || MetafacadeUtils.getUml14TaggedValue(name).equals(tagName))
507                {
508                    exists = true;
509                    break;
510                }
511            }
512        }
513        return exists;
514    }
515
516    /**
517     * Resolves a comma separated list of cascade types from andromda.xml
518
519     * @param cascadesString
520     * @return fully qualified cascade type sequence
521     */
522    private String getFullyQualifiedCascadeTypeList(final String cascadesString)
523    {
524        StringBuilder buf = null;
525        if (StringUtils.isNotBlank(cascadesString))
526        {
527            String[] ct = cascadesString.split(",");
528            for (int i = 0; i < ct.length; i++)
529            {
530                final String value = ct[i].trim();
531                if (StringUtils.isNotBlank(value))
532                {
533                    if (buf == null)
534                    {
535                        buf = new StringBuilder();
536                    }
537                    else
538                    {
539                        buf.append(", ");
540                    }
541
542                    buf.append(cascadeTable.get(value));
543                }
544            }
545        }
546        return buf == null ? null : buf.toString();
547    }
548
549    /**
550     * @see EJB3AssociationEndFacadeLogic#handleGetCascadeType()
551     */
552    @Override
553    protected String handleGetCascadeType()
554    {
555        String cascade = null;
556        final Collection<Object> taggedValues = this.findTaggedValues(EJB3Profile.TAGGEDVALUE_PERSISTENCE_CASCADE_TYPE);
557        if (taggedValues != null && !taggedValues.isEmpty())
558        {
559            StringBuilder buf = null;
560            for (Object value : taggedValues)
561            {
562                if (buf == null)
563                {
564                    buf = new StringBuilder();
565                }
566                else
567                {
568                    buf.append(", ");
569                }
570                if (StringUtils.isNotBlank((String)value))
571                {
572                    buf.append(cascadeTable.get(value));
573                }
574            }
575            if (buf != null)
576            {
577                cascade = buf.toString();
578            }
579        }
580        else if ((this.getOtherEnd() != null) &&
581                 (this.getOtherEnd().isAggregation() || this.getOtherEnd().isComposition()))
582        {
583            cascade = cascadeTable.get(ENTITY_CASCADE_REMOVE);
584            if (this.getOtherEnd().isComposition())
585            {
586                if (StringUtils.isBlank(this.getCompositionCascadeType()))
587                {
588                    if (this.getType() instanceof EJB3EntityFacade)
589                    {
590                        EJB3EntityFacade entity = (EJB3EntityFacade)this.getType();
591                        cascade = (ENTITY_CASCADE_NONE.equalsIgnoreCase(entity.getDefaultCascadeType()) ?
592                                null : this.getFullyQualifiedCascadeTypeList(entity.getDefaultCascadeType()));
593                    }
594                }
595                else
596                {
597                    cascade = (ENTITY_CASCADE_NONE.equalsIgnoreCase(this.getCompositionCascadeType()) ?
598                            null : this.getFullyQualifiedCascadeTypeList(this.getCompositionCascadeType()));
599                }
600            }
601            else if (this.getOtherEnd().isAggregation())
602            {
603                if (StringUtils.isBlank(this.getAggregationCascadeType()))
604                {
605                    if (this.getType() instanceof EJB3EntityFacade)
606                    {
607                        EJB3EntityFacade entity = (EJB3EntityFacade)this.getType();
608                        cascade = (ENTITY_CASCADE_NONE.equalsIgnoreCase(entity.getDefaultCascadeType()) ?
609                                null : this.getFullyQualifiedCascadeTypeList(entity.getDefaultCascadeType()));
610                    }
611                }
612                else
613                {
614                    cascade = (ENTITY_CASCADE_NONE.equalsIgnoreCase(this.getAggregationCascadeType()) ?
615                            null : this.getFullyQualifiedCascadeTypeList(this.getAggregationCascadeType()));
616                }
617            }
618        }
619        else if (this.isComposition())
620        {
621            /*
622             * On the composite side of the relationship, always enforce no cascade delete
623             * property indicating no cascadable propagation - overriding a default cascade
624             * value
625             */
626            // TODO cascade can only be null at this point - anything else to change?
627            //cascade = null;
628        }
629        else if (this.isAggregation())
630        {
631            /*
632             * On the aggregation side of the relationship, always enforce no cascade delete
633             * property indicating no cascadable propagation - overriding a default cascade
634             * value
635             */
636            // TODO cascade can only be null at this point - anything else to change?
637            //cascade = null;
638        }
639        return cascade;
640    }
641
642    /**
643     * @see EJB3AssociationEndFacadeLogic#handleGetCompositionCascadeType()
644     */
645    @Override
646    protected String handleGetCompositionCascadeType()
647    {
648        return StringUtils.trimToEmpty(
649                ObjectUtils.toString(this.getConfiguredProperty(ENTITY_DEFAULT_COMPOSITE_CASCADE)));
650    }
651
652    /**
653     * @see EJB3AssociationEndFacadeLogic#handleGetAggregationCascadeType()
654     */
655    @Override
656    protected String handleGetAggregationCascadeType()
657    {
658        return StringUtils.trimToEmpty(
659                ObjectUtils.toString(this.getConfiguredProperty(ENTITY_DEFAULT_AGGREGATION_CASCADE)));
660    }
661
662    /**
663     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionType()
664     */
665    @Override
666    protected String handleGetCollectionType()
667    {
668        String collectionType = this.getSpecificCollectionType();
669        if (!collectionTypes.contains(collectionType))
670        {
671            if (this.isOrdered())
672            {
673                collectionType = COLLECTION_TYPE_LIST;
674            }
675            else
676            {
677                collectionType =
678                    (String)this.getConfiguredProperty(ASSOCIATION_COLLECTION_TYPE);
679            }
680        }
681        return collectionType;
682    }
683
684    /**
685     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionTypeImplemenationClass()
686     */
687    @Override
688    protected String handleGetCollectionTypeImplemenationClass()
689    {
690        String collectionTypeImplementationClass = null;
691        if (this.isMany())
692        {
693            if (this.isSet())
694            {
695                collectionTypeImplementationClass = String.valueOf(
696                        this.getConfiguredProperty(SET_TYPE_IMPLEMENTATION));
697            }
698            else if (this.isMap())
699            {
700                collectionTypeImplementationClass = String.valueOf(
701                        this.getConfiguredProperty(MAP_TYPE_IMPLEMENTATION));
702            }
703            else if (this.isList() || this.isCollection())
704            {
705                collectionTypeImplementationClass = String.valueOf(
706                        this.getConfiguredProperty(LIST_TYPE_IMPLEMENTATION));
707            }
708        }
709        return collectionTypeImplementationClass;
710    }
711
712    /**
713     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionTypeImplementation()
714     */
715    @Override
716    protected String handleGetCollectionTypeImplementation()
717    {
718        return this.getCollectionTypeImplementation(null);
719    }
720
721    /**
722     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionTypeImplementation(String)
723     */
724    @Override
725    protected String handleGetCollectionTypeImplementation(final String arg)
726    {
727        StringBuilder implementation = new StringBuilder();
728        if (this.isMany())
729        {
730            implementation.append("new ");
731            implementation.append(this.getCollectionTypeImplemenationClass());
732
733            // set this association end's type as a template parameter if required
734            if (Boolean.valueOf(String.valueOf(this.getConfiguredProperty(UMLMetafacadeProperties.ENABLE_TEMPLATING)))
735                       .booleanValue())
736            {
737                implementation.append("<");
738                if (this.isMap())
739                {
740                    implementation.append(this.getCollectionIndexType());
741                    implementation.append(", ");
742                }
743                implementation.append(this.getType().getFullyQualifiedName());
744                implementation.append(">");
745            }
746            implementation.append("(");
747            if (StringUtils.isNotBlank(arg))
748            {
749                implementation.append(arg);
750            }
751            implementation.append(")");
752        }
753
754        return implementation.toString();
755    }
756
757    /**
758     * Gets the collection type defined on this association end.
759     *
760     * @return the specific collection type.
761     */
762    private String getSpecificCollectionType()
763    {
764        return ObjectUtils.toString(
765            this.findTaggedValue(EJB3Profile.TAGGEDVALUE_ASSOCIATION_COLLECTION_TYPE));
766    }
767
768    /**
769     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionIndexType()
770     */
771    @Override
772    protected String handleGetCollectionIndexType()
773    {
774        Object value = this.findTaggedValue(EJB3Profile.TAGGEDVALUE_ASSOCIATION_INDEX_TYPE);
775        if (value == null)
776        {
777            Object name = this.findTaggedValue(EJB3Profile.TAGGEDVALUE_ASSOCIATION_INDEX);
778            if (name == null)
779            {
780                ClassifierFacade facade = this.getOtherEnd().getType();
781                if (facade instanceof EJB3EntityFacade)
782                {
783                    // Find the identifier
784                    ModelElementFacade identifier = ((EJB3EntityFacade)facade).getIdentifier();
785                    if (identifier instanceof AttributeFacade)
786                    {
787                        value = ((AttributeFacade)identifier).getType().getFullyQualifiedName();
788                        return value.toString();
789                    }
790                    else if (identifier instanceof AssociationEndFacade)
791                    {
792                        value = ((AssociationEndFacade)identifier).getType().getFullyQualifiedName();
793                        return value.toString();
794                    }
795                }
796            }
797            // Find the attribute corresponding to name
798            Collection<AttributeFacade> attributes = ((EJB3EntityFacade)this.getOtherEnd().getType()).getAttributes();
799            for (AttributeFacade attrib : attributes)
800            {
801                EJB3EntityAttributeFacade attribute = (EJB3EntityAttributeFacade)attrib;
802                if (attribute.getName().equals(name))
803                {
804                    value = attribute.getType().getFullyQualifiedName();
805                    return value.toString();
806                }
807            }
808
809            // value can only be null at this point
810            value = this.getConfiguredProperty(COLLECTION_INDEX_TYPE);
811            if (StringUtils.isBlank(ObjectUtils.toString(value)))
812            {
813                value = null;
814            }
815         }
816
817        if (value != null)
818        {
819            if (value instanceof String)
820            {
821                value = this.getRootPackage().findModelElement((String)value);
822            }
823            if (value instanceof EJB3TypeFacade)
824            {
825                value = ((EJB3TypeFacade)value).getFullyQualifiedEJB3Type();
826            }
827        }
828        return (value != null) ? ObjectUtils.toString(value) : null;
829    }
830
831    /**
832     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionIndexName()
833     */
834    @Override
835    protected String handleGetCollectionIndexName()
836    {
837        Object value = this.findTaggedValue(EJB3Profile.TAGGEDVALUE_ASSOCIATION_INDEX);
838        if ((value == null) && this.isConfiguredProperty(COLLECTION_INDEX_NAME))
839        {
840            value = this.getConfiguredProperty(COLLECTION_INDEX_NAME);
841            if (StringUtils.isBlank(ObjectUtils.toString(value)))
842            {
843                value = null;
844            }
845        }
846
847        if (value != null)
848        {
849            return ObjectUtils.toString(value);
850        }
851        final String otherEntityName = ((EJB3EntityFacade)this.getOtherEnd().getType()).getEntityName();
852        final Object separator = this.getConfiguredProperty(UMLMetafacadeProperties.SQL_NAME_SEPARATOR);
853        return EntityMetafacadeUtils.toSqlName(
854            otherEntityName,
855            separator) + separator + EntityMetafacadeUtils.toSqlName(
856            this.getName(),
857            separator) + separator + "IDX";
858    }
859
860    /**
861     * @see EJB3AssociationEndFacadeLogic#handleIsMap()
862     */
863    @Override
864    protected boolean handleIsMap()
865    {
866        boolean isMap = COLLECTION_TYPE_MAP.equalsIgnoreCase(this.getCollectionType());
867        if (isMap && StringUtils.isBlank(this.getSpecificCollectionType()))
868        {
869            isMap = !this.isOrdered();
870        }
871        return isMap;
872    }
873
874    /**
875     * @see EJB3AssociationEndFacadeLogic#handleIsList()
876     */
877    @Override
878    protected boolean handleIsList()
879    {
880        boolean isList = COLLECTION_TYPE_LIST.equalsIgnoreCase(this.getCollectionType());
881        if (!isList && StringUtils.isBlank(this.getSpecificCollectionType()))
882        {
883            isList = this.isOrdered();
884        }
885        return isList;
886    }
887
888    /**
889     * @see EJB3AssociationEndFacadeLogic#handleIsSet()
890     */
891    @Override
892    protected boolean handleIsSet()
893    {
894        boolean isSet = COLLECTION_TYPE_SET.equalsIgnoreCase(this.getCollectionType());
895        if (isSet && StringUtils.isBlank(this.getSpecificCollectionType()))
896        {
897            isSet = !this.isOrdered();
898        }
899        return isSet;
900    }
901
902    /**
903     * @see EJB3AssociationEndFacadeLogic#handleIsCollection()
904     */
905    @Override
906    protected boolean handleIsCollection()
907    {
908        boolean isCollection = COLLECTION_TYPE_COLLECTION.equalsIgnoreCase(this.getCollectionType());
909        if (!isCollection && StringUtils.isBlank(this.getSpecificCollectionType()))
910        {
911            isCollection = this.isOrdered();
912        }
913        return isCollection;
914    }
915
916    /**
917     * @see EJB3AssociationEndFacadeLogic#handleGetLabelName()
918     */
919    @Override
920    protected String handleGetLabelName()
921    {
922        String labelNamePattern = (this.isMany() ?
923                (String)this.getConfiguredProperty(EJB3Globals.LABEL_COLLECTION_NAME_PATTERN) :
924                    (String)this.getConfiguredProperty(EJB3Globals.LABEL_SINGLE_NAME_PATTERN));
925
926        return MessageFormat.format(
927                labelNamePattern,
928                StringUtils.trimToEmpty(this.getName()));
929    }
930
931    /**
932     * @see EJB3AssociationEndFacadeLogic#handleGetGetterLabelName()
933     */
934    @Override
935    protected String handleGetGetterLabelName()
936    {
937        return UMLMetafacadeUtils.getGetterPrefix(this.getType()) + StringUtils.capitalize(this.getLabelName());
938    }
939
940    /**
941     * @see EJB3AssociationEndFacadeLogic#handleGetSetterLabelName()
942     */
943    @Override
944    protected String handleGetSetterLabelName()
945    {
946        return "set" + StringUtils.capitalize(this.getLabelName());
947    }
948
949    /**
950     * @see EJB3AssociationEndFacadeLogic#handleGetCacheType()
951     */
952    @Override
953    protected String handleGetCacheType()
954    {
955        String cacheType = (String)super.findTaggedValue(EJB3Profile.TAGGEDVALUE_HIBERNATE_ASSOCIATION_CACHE);
956        if (StringUtils.isBlank(cacheType))
957        {
958            cacheType = String.valueOf(this.getConfiguredProperty(HIBERNATE_ASSOCIATION_CACHE));
959        }
960        return StringUtils.trimToEmpty(cacheType);
961    }
962
963    /**
964     * @see EJB3AssociationEndFacadeLogic#handleIsAssociationCacheEnabled()
965     */
966    @Override
967    protected boolean handleIsAssociationCacheEnabled()
968    {
969        return BooleanUtils.toBoolean(String.valueOf(this.getConfiguredProperty(HIBERNATE_ASSOCIATION_ENABLE_CACHE)));
970    }
971
972    /**
973     * @see EJB3AssociationEndFacadeLogic#handleIsForeignKeyConstraintDefined()
974     */
975    @Override
976    protected boolean handleIsForeignKeyConstraintDefined()
977    {
978        boolean fkConstraintDefined = false;
979        if (super.findTaggedValue(UMLProfile.TAGGEDVALUE_PERSISTENCE_FOREIGN_KEY_CONSTRAINT_NAME) != null)
980        {
981            fkConstraintDefined = true;
982        }
983        return fkConstraintDefined;
984    }
985
986    /**
987     * @see EJB3AssociationEndFacadeLogic#handleGetForeignKeyConstraintName(String)
988     */
989    @Override
990    protected String handleGetForeignKeyConstraintName(final String suffix)
991    {
992        String constraintName;
993
994        final Object taggedValueObject = super.findTaggedValue(
995                UMLProfile.TAGGEDVALUE_PERSISTENCE_FOREIGN_KEY_CONSTRAINT_NAME);
996
997        /**
998         * Construct our own foreign key constraint name here
999         */
1000        StringBuilder buffer = new StringBuilder();
1001
1002        if (taggedValueObject == null)
1003        {
1004            final ClassifierFacade type = this.getOtherEnd().getType();
1005            if (type instanceof Entity)
1006            {
1007                //Entity entity = (Entity)type;
1008                //Instead of using the entity name, use the association end name to avoid duplication of
1009                //FK constraint names which causes failures during table creation for some DBs (MySQL)
1010                buffer.append(
1011                        EntityMetafacadeUtils.toSqlName(
1012                                this.getOtherEnd().getName(),
1013                                this.getConfiguredProperty(UMLMetafacadeProperties.SQL_NAME_SEPARATOR)));
1014            }
1015            else
1016            {
1017                // should not happen
1018                buffer.append(type.getName().toUpperCase());
1019            }
1020
1021            buffer.append(this.getConfiguredProperty(UMLMetafacadeProperties.SQL_NAME_SEPARATOR));
1022
1023            /**
1024             * Add the suffix - which is the name of the identifier pk column if not blank
1025             * otherwise use the column name of the relationship
1026             */
1027            if (StringUtils.isNotBlank(suffix))
1028            {
1029                buffer.append(suffix);
1030            }
1031            else
1032            {
1033                buffer.append(this.getColumnName());
1034            }
1035            constraintName = buffer.toString();
1036
1037            final String constraintSuffix =
1038                ObjectUtils.toString(this.getConfiguredProperty(UMLMetafacadeProperties.CONSTRAINT_SUFFIX)).trim();
1039
1040            /**
1041             * we take into consideration the maximum length allowed
1042             */
1043            final String maxLengthString = (String)super.getConfiguredProperty(UMLMetafacadeProperties.MAX_SQL_NAME_LENGTH);
1044            final short maxLength = (short)(Short.valueOf(maxLengthString).shortValue() - constraintSuffix.length());
1045            final String method = (String)super.getConfiguredProperty(UMLMetafacadeProperties.SHORTEN_SQL_NAMES_METHOD);
1046            buffer = new StringBuilder(
1047                    EntityMetafacadeUtils.ensureMaximumNameLength(constraintName, Short.valueOf(maxLength),method));
1048            buffer.append(constraintSuffix);
1049        }
1050        else
1051        {
1052            // use the tagged value
1053            buffer.append(taggedValueObject.toString());
1054        }
1055
1056        return buffer.toString();
1057    }
1058
1059    /**
1060     * @see EJB3AssociationEndFacadeLogic#handleGetForeignKeyName(String)
1061     */
1062    @Override
1063    protected String handleGetForeignKeyName(String suffix)
1064    {
1065        String columnName = null;
1066        // prevent ClassCastException if the association isn't an Entity
1067        if (this.getType() instanceof Entity)
1068        {
1069            if (StringUtils.isNotBlank(suffix))
1070            {
1071                suffix = new String(
1072                        this.getConfiguredProperty(UMLMetafacadeProperties.SQL_NAME_SEPARATOR) +
1073                        suffix +
1074                        this.getForeignKeySuffix());
1075            }
1076            else
1077            {
1078                suffix = this.getForeignKeySuffix();
1079            }
1080
1081            final String columnNamePrefix =
1082                this.isConfiguredProperty(UMLMetafacadeProperties.COLUMN_NAME_PREFIX)
1083                ? ObjectUtils.toString(this.getConfiguredProperty(UMLMetafacadeProperties.COLUMN_NAME_PREFIX)) : null;
1084            columnName =
1085                EntityMetafacadeUtils.getSqlNameFromTaggedValue(
1086                    columnNamePrefix,
1087                    this,
1088                    UMLProfile.TAGGEDVALUE_PERSISTENCE_COLUMN,
1089                    ((Entity)this.getType()).getMaxSqlNameLength(),
1090                    suffix,
1091                    this.getConfiguredProperty(UMLMetafacadeProperties.SQL_NAME_SEPARATOR),
1092                    this.getConfiguredProperty(UMLMetafacadeProperties.SHORTEN_SQL_NAMES_METHOD));
1093        }
1094        return columnName;
1095    }
1096
1097    /**
1098     * @see EJB3AssociationEndFacadeLogic#handleGetDefaultCollectionInterface()
1099     */
1100    @Override
1101    protected String handleGetDefaultCollectionInterface()
1102    {
1103        return ObjectUtils.toString(this.getConfiguredProperty(DEFAULT_COLLECTION_INTERFACE));
1104    }
1105
1106    /**
1107     * @see EJB3AssociationEndFacadeLogic#handleIsCollectionInterfaceSortedSet()
1108     */
1109    @Override
1110    protected boolean handleIsCollectionInterfaceSortedSet()
1111    {
1112        boolean isInterfaceSortedSet = false;
1113        if (this.getGetterSetterTypeName().startsWith(EJB3Globals.COLLECTION_INTERFACE_SORTED_SET))
1114        {
1115            isInterfaceSortedSet = true;
1116        }
1117        return isInterfaceSortedSet;
1118    }
1119
1120    /**
1121     * @see EJB3AssociationEndFacadeLogic#handleGetHibernateCascadeType()
1122     */
1123    @Override
1124    protected String handleGetHibernateCascadeType()
1125    {
1126        return (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_HIBERNATE_CASCADE);
1127    }
1128
1129    /**
1130     * @see EJB3AssociationEndFacadeLogic#handleIsHibernateCascadeExists()
1131     */
1132    @Override
1133    protected boolean handleIsHibernateCascadeExists()
1134    {
1135        return StringUtils.isNotBlank(this.getHibernateCascadeType()) ? true : false;
1136    }
1137
1138    /**
1139     * @see EJB3AssociationEndFacadeLogic#handleIsColumnNullable()
1140     */
1141    @Override
1142    protected boolean handleIsColumnNullable()
1143    {
1144        boolean nullable = true;
1145        String nullableString = (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_PERSISTENCE_COLUMN_NULLABLE);
1146
1147        if (StringUtils.isBlank(nullableString))
1148        {
1149            nullable = (this.isIdentifier() || this.isUnique()) ? false : !this.isRequired();
1150        }
1151        else
1152        {
1153            nullable = Boolean.valueOf(nullableString).booleanValue();
1154        }
1155        return nullable;
1156    }
1157}