001package org.andromda.cartridges.ejb3.metafacades;
002
003import java.util.ArrayList;
004import java.util.Collection;
005import java.util.Iterator;
006import java.util.List;
007import org.andromda.cartridges.ejb3.EJB3Globals;
008import org.andromda.cartridges.ejb3.EJB3Profile;
009import org.andromda.core.common.ExceptionUtils;
010import org.andromda.metafacades.uml.AttributeFacade;
011import org.andromda.metafacades.uml.ClassifierFacade;
012import org.andromda.metafacades.uml.EntityMetafacadeUtils;
013import org.andromda.metafacades.uml.ModelElementFacade;
014import org.andromda.metafacades.uml.OperationFacade;
015import org.andromda.metafacades.uml.UMLProfile;
016import org.apache.commons.collections.CollectionUtils;
017import org.apache.commons.collections.Predicate;
018import org.apache.commons.lang.StringUtils;
019
020/**
021 * Contains utilities for use with EJB metafacades.
022 *
023 * @author Chad Brandon
024 * @author Vance Karimi
025 */
026class EJB3MetafacadeUtils
027{
028    /**
029     * Gets all create methods for the given <code>classifier</code>.
030     *
031     * @param classifier The classifier from which to retries the create methods
032     * @param follow if true, all super type create methods are also retrieved
033     * @return collection of create methods found.
034     */
035    static Collection<OperationFacade> getCreateMethods(
036            ClassifierFacade classifier,
037            boolean follow)
038    {
039        ExceptionUtils.checkNull("classifer", classifier);
040        Collection<OperationFacade> retval = new ArrayList<OperationFacade>();
041        ClassifierFacade entity = classifier;
042        do
043        {
044            for (final OperationFacade op : entity.getOperations())
045            {
046                if (op.hasStereotype(EJB3Profile.STEREOTYPE_CREATE_METHOD))
047                {
048                    retval.add(op);
049                }
050            }
051            if (follow)
052            {
053                entity = (ClassifierFacade)entity.getGeneralization();
054            }
055        }
056        while (follow && entity != null);
057        return retval;
058    }
059
060    /**
061     * Gets the interface name for the passed in <code>classifier</code>. Returns 'LocalHome' if the mode element has
062     * the entity stereotype, returns 'Home' otherwise.
063     * @param classifier
064     * @return the interface name.
065     */
066    static String getHomeInterfaceName(ClassifierFacade classifier)
067    {
068        ExceptionUtils.checkNull("classifer", classifier);
069        String homeInterfaceName;
070        if (classifier.hasStereotype(UMLProfile.STEREOTYPE_ENTITY))
071        {
072            homeInterfaceName = classifier.getName() + "LocalHome";
073        }
074        else
075        {
076            homeInterfaceName = classifier.getName() + "Home";
077        }
078        return homeInterfaceName;
079    }
080
081    /**
082     * Gets the view type for the passed in <code>classifier</code>. If no
083     * view type can be retrieved from the <code>classifier</code>, then the
084     * <code>defaultViewType</code> is returned.
085     *
086     * If session ejb pojo then checks the ejb tagged value and if there is
087     * no value defined, returns 'remote'.
088     *
089     * @param classifier The classifier to lookup the view type tagged value
090     * @param defaultViewType The default view type if one is not found
091     * @return String the view type name.
092     */
093    static String getViewType(
094            ClassifierFacade classifier,
095            String defaultViewType)
096    {
097        ExceptionUtils.checkNull("classifer", classifier);
098        String viewType = (String)classifier.findTaggedValue(EJB3Profile.TAGGEDVALUE_EJB_VIEWTYPE);
099        if (StringUtils.isBlank(viewType))
100        {
101            if (classifier.hasStereotype(EJB3Profile.STEREOTYPE_SERVICE))
102            {
103                // if the view type wasn't found, search all super classes
104                if (StringUtils.isBlank(viewType))
105                {
106                    viewType = (String)CollectionUtils.find(
107                            classifier.getAllGeneralizations(),
108                            new Predicate()
109                            {
110                                public boolean evaluate(Object object)
111                                {
112                                    return ((ModelElementFacade)object).findTaggedValue(
113                                            EJB3Profile.TAGGEDVALUE_EJB_VIEWTYPE) != null;
114                                }
115                            });
116                }
117                if (StringUtils.isBlank(viewType))
118                {
119                    viewType = (StringUtils.isNotBlank(defaultViewType) ?
120                            defaultViewType : EJB3Globals.VIEW_TYPE_REMOTE);
121                }
122            }
123        }
124        return viewType.toLowerCase();
125    }
126
127    /**
128     * Gets all the inherited instance attributes, excluding the instance attributes directory from this
129     * <code>classifier</code>.
130     *
131     * @param classifier the ClassifierFacade from which to retrieve the inherited attributes.
132     * @return a list of ordered attributes.
133     */
134    static List<AttributeFacade> getInheritedInstanceAttributes(ClassifierFacade classifier)
135    {
136        ExceptionUtils.checkNull("classifer", classifier);
137        ClassifierFacade current = (ClassifierFacade)classifier.getGeneralization();
138        if (current == null)
139        {
140            return new ArrayList<AttributeFacade>();
141        }
142        List<AttributeFacade> retval = getInheritedInstanceAttributes(current);
143
144        if (current.getInstanceAttributes() != null)
145        {
146            retval.addAll(current.getInstanceAttributes());
147        }
148        return retval;
149    }
150
151    /**
152     * Gets all instance attributes including those instance attributes belonging to the
153     * <code>classifier</code> and any inherited ones.
154     *
155     * @param classifier the ClassifierFacade from which to retrieve the instance attributes.
156     * @return the list of all instance attributes.
157     */
158    static List<AttributeFacade> getAllInstanceAttributes(ClassifierFacade classifier)
159    {
160        ExceptionUtils.checkNull("classifer", classifier);
161        List<AttributeFacade> retval = getInheritedInstanceAttributes(classifier);
162        retval.addAll(classifier.getInstanceAttributes());
163        return retval;
164    }
165
166    /**
167     * Gets all environment entries for the specified <code>classifier</code>. If <code>follow</code> is true, then a
168     * search up the inheritance hierarchy will be performed and all super type environment entries will also be
169     * retrieved.
170     *
171     * @param classifier the classifier from which to retrieve the env-entries
172     * @param follow     true/false on whether or not to 'follow' the inheritance hierarchy when retrieving the
173     *                   env-entries.
174     * @return the collection of environment entries
175     */
176    static Collection<AttributeFacade> getEnvironmentEntries(
177            ClassifierFacade classifier,
178            boolean follow)
179    {
180        ExceptionUtils.checkNull("classifer", classifier);
181
182        Collection<AttributeFacade> attributes = classifier.getStaticAttributes();
183
184        if (follow)
185        {
186            for (classifier = (ClassifierFacade)classifier.getGeneralization();
187                 classifier != null; classifier = (ClassifierFacade)classifier.getGeneralization())
188            {
189                attributes.addAll(classifier.getStaticAttributes());
190            }
191        }
192
193        CollectionUtils.filter(attributes, new Predicate()
194        {
195            public boolean evaluate(Object object)
196            {
197                return ((AttributeFacade)object).hasStereotype(EJB3Profile.STEREOTYPE_ENV_ENTRY);
198            }
199        });
200
201        return attributes;
202    }
203
204    /**
205     * Returns the transaction type for the specified <code>classifier</code>.
206     *
207     * @param classifier the classifier from which to retrieve the transaction type.
208     * @param defaultTransactionType the default transaction type if no tagged value is specified.
209     * @return the transaction type as a String.
210     */
211    static String getTransactionType(ClassifierFacade classifier, String defaultTransactionType)
212    {
213        ExceptionUtils.checkNull("classifer", classifier);
214
215        String transactionType = (String)classifier.findTaggedValue(EJB3Profile.TAGGEDVALUE_EJB_TRANSACTION_TYPE);
216        if (StringUtils.isNotBlank(transactionType))
217        {
218            transactionType = convertTransactionType(transactionType);
219        }
220        else
221        {
222            transactionType = defaultTransactionType;
223        }
224        return transactionType;
225    }
226
227    /**
228     * Convert the transaction type from lower casing to upper casing.
229     * This maintains reusable tagged value enumeration from EJB
230     * implementation.
231     *
232     * @param transType
233     * @return transactionType
234     */
235    static String convertTransactionType(String transType)
236    {
237        ExceptionUtils.checkNull("transType", transType);
238
239        String type = null;
240        if (StringUtils.equalsIgnoreCase(transType, EJB3Globals.TRANSACTION_TYPE_MANDATORY))
241        {
242            type = "MANDATORY";
243        }
244        else if (StringUtils.equalsIgnoreCase(transType, EJB3Globals.TRANSACTION_TYPE_NEVER))
245        {
246            type = "NEVER";
247        }
248        else if (StringUtils.equalsIgnoreCase(transType, EJB3Globals.TRANSACTION_TYPE_NOT_SUPPORTED))
249        {
250            type = "NOT_SUPPORTED";
251        }
252        else if (StringUtils.equalsIgnoreCase(transType, EJB3Globals.TRANSACTION_TYPE_REQUIRED))
253        {
254            type = "REQUIRED";
255        }
256        else if (StringUtils.equalsIgnoreCase(transType, EJB3Globals.TRANSACTION_TYPE_REQUIRES_NEW))
257        {
258            type = "REQUIRES_NEW";
259        }
260        else if (StringUtils.equalsIgnoreCase(transType, EJB3Globals.TRANSACTION_TYPE_SUPPORTS))
261        {
262            type = "SUPPORTS";
263        }
264        return type;
265    }
266
267    /**
268     * Gets all constants for the specified <code>classifier</code>.
269     * If <code>follow</code> is true, then a search up
270     * the inheritance hierarchy will be performed and all super
271     * type constants will also be retrieved.
272     *
273     * @param classifier the classifier from which to retrieve the constants
274     * @param follow     true/false on whether or not to 'follow' the inheritance hierarchy when retrieving the
275     *                   constants.
276     * @return the collection of environment entries
277     */
278    static Collection<AttributeFacade> getConstants(
279            ClassifierFacade classifier,
280            boolean follow)
281    {
282        ExceptionUtils.checkNull("classifer", classifier);
283
284        Collection<AttributeFacade> attributes = classifier.getStaticAttributes();
285
286        if (follow)
287        {
288            for (classifier = (ClassifierFacade)classifier.getGeneralization();
289                 classifier != null; classifier = (ClassifierFacade)classifier.getGeneralization())
290            {
291                attributes.addAll(classifier.getStaticAttributes());
292            }
293        }
294
295        CollectionUtils.filter(attributes, new Predicate()
296        {
297            public boolean evaluate(Object object)
298            {
299                return !((AttributeFacade)object).hasStereotype(EJB3Profile.STEREOTYPE_ENV_ENTRY);
300            }
301        });
302
303        return attributes;
304    }
305
306    /**
307     * Returns true/false based on whether or not synthetic or auto generated create methods should be allowed.
308     *
309     * @param classifier The entity or session bean.
310     * @return true/false
311     */
312    static boolean allowSyntheticCreateMethod(ClassifierFacade classifier)
313    {
314        ExceptionUtils.checkNull("classifer", classifier);
315        return !classifier.isAbstract() && classifier.findTaggedValue(
316                EJB3Profile.TAGGEDVALUE_EJB_NO_SYNTHETIC_CREATE_METHOD) == null;
317    }
318
319    /**
320     * Creates a fully qualified name from the given <code>packageName</code>,
321     * <code>name</code>, and <code>suffix</code>.
322     *
323     * @param packageName the name of the model element package.
324     * @param name the name of the model element.
325     * @param suffix the suffix to append.
326     * @return the new fully qualified name.
327     */
328    static String getFullyQualifiedName(
329        String packageName,
330        String name,
331        String suffix)
332    {
333        StringBuilder fullyQualifiedName = new StringBuilder(StringUtils.trimToEmpty(packageName));
334        if (StringUtils.isNotBlank(packageName))
335        {
336            fullyQualifiedName.append('.');
337        }
338        fullyQualifiedName.append(StringUtils.trimToEmpty(name));
339        fullyQualifiedName.append(StringUtils.trimToEmpty(suffix));
340        return fullyQualifiedName.toString();
341    }
342
343    /**
344     * Returns true if the Seam stereotype is modeled on the class.
345     *
346     * @param classifier The classifier to lookup if the stereotype is modeled
347     * @return True is stereotype exists, false otherwise
348     */
349    static boolean isSeamComponent(ClassifierFacade classifier)
350    {
351        boolean isSeamComponent = false;
352        if (classifier.hasStereotype(EJB3Profile.STEREOTYPE_SEAM_COMPONENT))
353        {
354            isSeamComponent = true;
355        }
356        return isSeamComponent;
357    }
358
359    /**
360     * Gets the Seam component scope type for Entity and Session beans.
361     * If no scope has been specified:
362     *     If the Classifier is a stateless session bean, then returns STATELESS
363     *     If the Classifier is an entity or stateful session bean, then returns CONVERSATION
364     *
365     * @param classifier The classifier to lookup the scope type tagged value
366     * @param stateless Whether the classifier is a stateless session bean
367     * @return The scope type as a String
368     */
369    static String getSeamComponentScopeType(ClassifierFacade classifier, boolean stateless)
370    {
371        ExceptionUtils.checkNull("classifer", classifier);
372        String scopeType = (String)classifier.findTaggedValue(EJB3Profile.TAGGEDVALUE_SEAM_SCOPE_TYPE);
373        if (StringUtils.isBlank(scopeType))
374        {
375            if (stateless)
376            {
377                scopeType = EJB3Globals.SEAM_COMPONENT_SCOPE_STATELESS;
378            }
379            else
380            {
381                scopeType = EJB3Globals.SEAM_COMPONENT_SCOPE_CONVERSATION;
382            }
383        }
384        return scopeType;
385    }
386
387    /**
388     * Returns the Seam component name.  Can override using tagged value, otherwise just the
389     * class name.
390     *
391     * @param classifier The classifier to get the tagged value or the name from.
392     * @return The Seam component name
393     */
394    static String getSeamComponentName(ClassifierFacade classifier)
395    {
396        ExceptionUtils.checkNull("classifer", classifier);
397        String componentName = (String)classifier.findTaggedValue(EJB3Profile.TAGGEDVALUE_SEAM_COMPONENT_NAME);
398        if (StringUtils.isBlank(componentName))
399        {
400            componentName = StringUtils.uncapitalize(classifier.getName());
401        }
402        return componentName;
403    }
404
405    /**
406     * Builds an annotation parameter line
407     * @param parameters The parameters
408     * @return The parameter line
409     */
410    static String buildAnnotationParameters(Collection<String> parameters)
411    {
412        StringBuilder buf = new StringBuilder();
413        if(!parameters.isEmpty())
414        {
415            buf.append("(");
416            Iterator it = parameters.iterator();
417            while(it.hasNext())
418            {
419                String option = (String) it.next();
420                buf.append(option);
421                if(it.hasNext())
422                {
423                    buf.append(", ");
424                }
425            }
426            buf.append(")");
427            return buf.toString();
428        }
429        else
430        {
431            return null;
432        }
433    }
434
435    /**
436     * Builds a multi valued parameter string
437     * @param name The name of the parameter
438     * @param values The values for the parameters
439     * @return The parameter string
440     */
441    static String buildAnnotationMultivalueParameter(String name, Collection<String> values)
442    {
443        return buildAnnotationMultivalueParameter(name, values, true);
444    }
445
446    /**
447     * Builds a multi valued parameter string
448     * @param name The name of the parameter
449     * @param values The values for the parameters
450     * @param areStrings If the values are strings then surround with " sign
451     * @return The parameter string
452     */
453    static String buildAnnotationMultivalueParameter(String name, Collection<String> values, boolean areStrings)
454    {
455        return buildAnnotationMultivalueParameter(name, values, areStrings, null);
456    }
457
458    /**
459     * Builds a multi option string
460     * Builds a multi valued parameter string
461     * @param name The name of the parameter
462     * @param values The values for the parameters
463     * @param areStrings If the values are strings then surround with " sign
464     * @param suffix Any suffix to add to the values
465     * @return The parameter string
466     */
467    static String buildAnnotationMultivalueParameter(String name, Collection<String> values, boolean areStrings, String suffix)
468    {
469        if(values.isEmpty())
470        {
471            return null;
472        }
473        else
474        {
475            StringBuilder buf = new StringBuilder();
476            buf.append(name + " = {");
477
478            Iterator it = values.iterator();
479            while(it.hasNext())
480            {
481                String parameter = (String) it.next();
482                if(areStrings)
483                {
484                    buf.append("\"");
485                }
486                buf.append(parameter);
487                if((suffix != null) && !parameter.endsWith(suffix))
488                {
489                    buf.append(suffix);
490                }
491                if(areStrings)
492                {
493                    buf.append("\"");
494                }
495                if(it.hasNext())
496                {
497                    buf.append(", ");
498                }
499            }
500            buf.append("}");
501            return buf.toString();
502        }
503    }
504
505    /**
506     * Gets the SQL name. (i.e. column name, table name, etc.). If it can't find
507     * the corresponding tagged value with the specified <code>name</code>,
508     * then it uses the element name by default and just returns that.
509     *
510     * @param prefix the optional prefix to add to the sql name (i.e. table name
511     *        prefix, etc.).
512     * @param element from which to retrieve the SQL name.
513     * @param name the name of the tagged value.
514     * @param nameMaxLength if this is not null, then the name returned will be
515     *        trimmed to this length (if it happens to be longer).
516     * @param suffix the optional suffix to add to the sql name (i.e. foreign
517     *        key suffix, etc.)
518     * @param separator character used to separate words
519     * @param shortenSqlNameMethod The method used to shorten the name, i.e. removeVowels
520     * @return the SQL name as a String.
521     */
522    public static String getSqlNameFromTaggedValue(
523        String prefix,
524        final EJB3AssociationFacade element,
525        String name,
526        final Short nameMaxLength,
527        String suffix,
528        final Object separator,
529        final Object shortenSqlNameMethod)
530    {
531        if (element != null)
532        {
533            Object value = element.findTaggedValue(name);
534            StringBuilder buffer = new StringBuilder(StringUtils.trimToEmpty((String)value));
535            if (StringUtils.isBlank(buffer.toString()))
536            {
537                // if we can't find the tagValue then use the
538                // element name for the name
539                buffer = new StringBuilder(
540                        EntityMetafacadeUtils.toSqlName(
541                            element.getName(),
542                            separator));
543
544                suffix = StringUtils.trimToEmpty(suffix);
545                prefix = StringUtils.trimToEmpty(prefix);
546                if (nameMaxLength != null)
547                {
548                    final short maxLength = (short)(nameMaxLength.shortValue() - suffix.length() - prefix.length());
549                    buffer =
550                        new StringBuilder(
551                            EntityMetafacadeUtils.ensureMaximumNameLength(
552                                buffer.toString(),
553                                new Short(maxLength),
554                                (String)shortenSqlNameMethod));
555                }
556                if (StringUtils.isNotBlank(prefix))
557                {
558                    buffer.insert(
559                        0,
560                        prefix);
561                }
562                if (StringUtils.isNotBlank(suffix))
563                {
564                    buffer.append(suffix);
565                }
566            }
567            name = buffer.toString();
568        }
569        return name;
570    }
571}