001package org.andromda.cartridges.spring;
002
003import java.text.SimpleDateFormat;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Date;
007import java.util.Iterator;
008import java.util.LinkedHashMap;
009import java.util.LinkedHashSet;
010import java.util.List;
011import java.util.Map;
012import org.andromda.cartridges.spring.metafacades.SpringService;
013import org.andromda.metafacades.uml.AssociationEndFacade;
014import org.andromda.metafacades.uml.AttributeFacade;
015import org.andromda.metafacades.uml.ClassifierFacade;
016import org.andromda.metafacades.uml.Entity;
017import org.andromda.metafacades.uml.EntityAttribute;
018import org.andromda.metafacades.uml.EnumerationFacade;
019import org.andromda.metafacades.uml.EnumerationLiteralFacade;
020import org.andromda.metafacades.uml.GeneralizableElementFacade;
021import org.andromda.metafacades.uml.ModelElementFacade;
022import org.andromda.metafacades.uml.Role;
023import org.andromda.metafacades.uml.Service;
024import org.andromda.utils.StringUtilsHelper;
025import org.apache.commons.collections.Closure;
026import org.apache.commons.collections.CollectionUtils;
027import org.apache.commons.collections.Predicate;
028import org.apache.commons.lang.StringUtils;
029import org.apache.log4j.Logger;
030
031
032/**
033 * Contains utilities used within the Spring cartridge.
034 *
035 * @author Chad Brandon
036 * @author Joel Kozikowski
037 */
038public class SpringUtils
039{
040    /**
041     * The logger instance.
042     */
043    private static final Logger logger = Logger.getLogger(SpringUtils.class);
044
045    /**
046     * Retrieves all roles from the given <code>services</code> collection.
047     *
048     * @param services the collection services.
049     * @return all roles from the collection.
050     */
051    public Collection<Role> getAllRoles(Collection<Service> services)
052    {
053        final Collection<Role> allRoles = new LinkedHashSet<Role>();
054        CollectionUtils.forAllDo(
055            services,
056            new Closure()
057            {
058                public void execute(Object object)
059                {
060                    if (object != null && Service.class.isAssignableFrom(object.getClass()))
061                    {
062                        allRoles.addAll(((Service)object).getAllRoles());
063                    }
064                }
065            });
066        return allRoles;
067    }
068
069    /**
070     * Indicates if any remote EJBs are present in the collection
071     * of services.
072     *
073     * @param services the collection of services to check.
074     * @return true/false.
075     */
076    public boolean remoteEjbsPresent(final Collection<Service> services)
077    {
078        boolean present = services != null && !services.isEmpty();
079        if (present)
080        {
081            present =
082                CollectionUtils.find(
083                    services,
084                    new Predicate()
085                    {
086                        public boolean evaluate(final Object object)
087                        {
088                            boolean valid = false;
089                            if (object instanceof SpringService)
090                            {
091                                final SpringService service = (SpringService)object;
092                                valid = service.isEjbRemoteView();
093                            }
094                            return valid;
095                        }
096                    }) != null;
097        }
098        return present;
099    }
100
101    /**
102     * Indicates if any local EJBs are present in the collection
103     * of services.
104     *
105     * @param services the collection of services to check.
106     * @return true/false.
107     */
108    public boolean localEjbsPresent(final Collection<Service> services)
109    {
110        boolean present = services != null && !services.isEmpty();
111        if (present)
112        {
113            present =
114                CollectionUtils.find(
115                    services,
116                    new Predicate()
117                    {
118                        public boolean evaluate(final Object object)
119                        {
120                            boolean valid = false;
121                            if (object instanceof SpringService)
122                            {
123                                final SpringService service = (SpringService)object;
124                                valid = service.isEjbLocalView();
125                            }
126                            return valid;
127                        }
128                    }) != null;
129        }
130        return present;
131    }
132
133    /**
134     * Indicates if any Spring remotable services are present.
135     *
136     * @param services the collection of services to check.
137     * @return true/false.
138     */
139    public boolean remotableServicesPresent(final Collection<Service> services)
140    {
141        boolean present = services != null && !services.isEmpty();
142        if (present)
143        {
144            present =
145                CollectionUtils.find(
146                    services,
147                    new Predicate()
148                    {
149                        public boolean evaluate(final Object object)
150                        {
151                            boolean valid = false;
152                            if (object instanceof SpringService)
153                            {
154                                final SpringService service = (SpringService)object;
155                                valid = service.isRemotable();
156                            }
157                            return valid;
158                        }
159                    }) != null;
160        }
161        return present;
162    }
163
164    /**
165     * Indicates if any remotable services using Lingo are present.
166     *
167     * @param services the collection of services to check.
168     * @return true/false.
169     */
170    public boolean lingoRemotableServicesPresent(final Collection<Service> services)
171    {
172        boolean present = services != null && !services.isEmpty();
173        if (present)
174        {
175            present =
176                CollectionUtils.find(
177                        services,
178                        new Predicate()
179                        {
180                            public boolean evaluate(final Object object)
181                            {
182                                boolean valid = false;
183                                if (object instanceof SpringService)
184                                {
185                                    final SpringService service = (SpringService)object;
186                                    valid = service.isRemotingTypeLingo();
187                                }
188                                return valid;
189                            }
190                        }) != null;
191        }
192        return present;
193    }
194
195    /**
196     * Indicates if any private services are present.
197     *
198     * @param services the collection of services to check.
199     * @return true/false.
200     */
201    public boolean privateServicesPresent(final Collection<Service> services)
202    {
203        boolean present = services != null && !services.isEmpty();
204        if (present)
205        {
206            present =
207                CollectionUtils.find(
208                    services,
209                    new Predicate()
210                    {
211                        public boolean evaluate(final Object object)
212                        {
213                            boolean valid = false;
214                            if (object instanceof SpringService)
215                            {
216                                final SpringService service = (SpringService)object;
217                                valid = service.isPrivate();
218                            }
219                            return valid;
220                        }
221                    }) != null;
222        }
223        return present;
224    }
225
226    /**
227     * Indicates if any public (non private) services are present.
228     *
229     * @param services the collection of services to check.
230     * @return true/false.
231     */
232    public boolean publicServicesPresent(final Collection<Service> services)
233    {
234        boolean present = services != null && !services.isEmpty();
235        if (present)
236        {
237            present =
238                CollectionUtils.find(
239                    services,
240                    new Predicate()
241                    {
242                        public boolean evaluate(final Object object)
243                        {
244                            boolean valid = false;
245                            if (object instanceof SpringService)
246                            {
247                                final SpringService service = (SpringService)object;
248                                valid = !service.isPrivate();
249                            }
250                            return valid;
251                        }
252                    }) != null;
253        }
254        return present;
255    }
256
257    /**
258     * Based on the given <code>value</code>, this method will return
259     * a formatted Spring property (including the handling of 'null').
260     *
261     * @param value the value from which to create the spring value.
262     * @return the spring value.
263     */
264    public String getSpringPropertyValue(final String value)
265    {
266        String propertyValue = "";
267        if (value != null)
268        {
269            if ("null".equalsIgnoreCase(value))
270            {
271                propertyValue = "<null/>";
272            }
273            else
274            {
275                propertyValue = "<value>" + value + "</value>";
276            }
277        }
278        return propertyValue;
279    }
280
281    /**
282     * Removes generics from string. Currently used to strip generics
283     * from ejb-jar.xml method parameters.
284     * @param parameter String containing generics
285     * @return String with generics stripped
286     */
287    public String removeGenerics(final String parameter)
288    {
289        int position = parameter.indexOf('<');
290        String result = parameter;
291        if(position != -1)
292        {
293            result = result.substring(0, position);
294        }
295        return result;
296    }
297
298    /**
299     * Are we generating code for a rich client? false.
300     */
301    private boolean richClient = false;
302
303
304    /**
305     * Sets if code is being generated for a rich client.
306     * @param richClientProperty
307     */
308    public void setRichClient(final boolean richClientProperty)
309    {
310        this.richClient = richClientProperty;
311    }
312
313    /**
314     * Returns TRUE if code is being generated for a rich client environment
315     * @return richClient
316     */
317    public boolean isRichClient()
318    {
319        return this.richClient;
320    }
321
322    /**
323     * Returns the class name part of a fully qualified name
324     * @param fullyQualifiedName
325     * @return just the "class name" part of the fully qualified name
326     */
327    public String getClassName(String fullyQualifiedName)
328    {
329       String className = null;
330       if (StringUtils.isNotBlank(fullyQualifiedName))
331       {
332           int lastDot = fullyQualifiedName.lastIndexOf('.');
333           if (lastDot >= 0)
334           {
335               className = fullyQualifiedName.substring(lastDot+1);
336           }
337           else
338           {
339               className = fullyQualifiedName;
340           }
341       }
342       else
343       {
344           className = "";
345       }
346
347       return className;
348    }
349
350
351    /**
352     * Returns the package name part of a fully qualified name
353     * @param fullyQualifiedName
354     * @return just the "package" part of the fully qualified name
355     */
356    public String getPackageName(String fullyQualifiedName)
357    {
358       String packageName = null;
359       if (StringUtils.isNotBlank(fullyQualifiedName))
360       {
361           int lastDot = fullyQualifiedName.lastIndexOf('.');
362           packageName = (lastDot >= 0 ? fullyQualifiedName.substring(0, lastDot) : "");
363       }
364       else
365       {
366           packageName = "";
367       }
368
369       return packageName;
370    }
371
372    /**
373     * Returns an ordered set containing the argument model elements, model elements with a name that is already
374     * used by another model element in the argument collection will not be returned.
375     * The first operation with a name not already encountered will be returned, the order inferred by the
376     * argument's iterator will determine the order of the returned list.
377     *
378     * @param modelElements a collection of model elements, elements that are not model elements will be ignored
379     * @return the argument model elements without, elements with a duplicate name will only be recorded once
380     */
381    public List<ModelElementFacade> filterUniqueByName(Collection<ModelElementFacade> modelElements)
382    {
383        final Map<String, ModelElementFacade> filteredElements = new LinkedHashMap<String, ModelElementFacade>();
384
385        for (final ModelElementFacade modelElement : modelElements)
386        {
387            /*final Object object = elementIterator.next();
388            if (object instanceof ModelElementFacade)
389            {*/
390            if (!filteredElements.containsKey(modelElement.getName()))
391            {
392                filteredElements.put(modelElement.getName(), modelElement);
393            }
394            /*}*/
395        }
396
397        return new ArrayList<ModelElementFacade>(filteredElements.values());
398    }
399
400    /**
401     * Formats the given type to the appropriate Hibernate query parameter value.
402     *
403     * @param type the type of the Hibernate query parameter.
404     * @param value the current value to format.
405     * @return the formatted value.
406     */
407    public String formatHibernateQueryParameterValue(final ClassifierFacade type, String value)
408    {
409        if (type != null)
410        {
411            if (type.isPrimitive())
412            {
413                value = "new " + type.getWrapperName() + '(' + value + ')';
414            }
415        }
416        return value;
417    }
418
419    /**
420     * Takes the given <code>names</code> and concatenates them in camel case
421     * form.
422     *
423     * @param names the names to concatenate.
424     * @return the result of the concatenation
425     */
426    public static String concatNamesCamelCase(final Collection<String> names)
427    {
428        String result = null;
429        if (names != null)
430        {
431            result = StringUtilsHelper.lowerCamelCaseName(StringUtils.join(names.iterator(), " "));
432        }
433        return result;
434    }
435
436    /**
437     * Constructs the fully qualified class name from the packageName and name.
438     * @param packageName the package name to which the class belongs.
439     * @param name the name of the class.
440     * @return the fully qualified name.
441     */
442    public static String getFullyQualifiedClassName(final String packageName, final String name)
443    {
444        final StringBuilder fullName = new StringBuilder(StringUtils.trimToEmpty(packageName));
445        if (fullName.length() > 0)
446        {
447            fullName.append('.');
448        }
449        fullName.append(name);
450        return fullName.toString();
451    }
452
453    /**
454     * Constructs the fully qualified class definition from the facade. Used for
455     * ValueObject, EmbeddedValue
456     * @param facade the class to construct the roo field definition.
457     * @return the Roo class definition.
458     */
459    public static List<String> getRooEnum(final EnumerationFacade facade)
460    {
461        List<String> results = new ArrayList<String>();
462        String result = "enum type --class " + facade.getFullyQualifiedName() + " --permitReservedWords";
463        results.add(result);
464        // Can't do for: because literal may be AttributeFacade or EnumerationLiteralFacade - ClassCastException
465        Iterator literals = facade.getLiterals().iterator();
466        while (literals.hasNext())
467        {
468            result = "enum constant --name " + ((ModelElementFacade)literals.next()).getName();
469            results.add(result);
470        }
471        return results;
472    }
473
474    /**
475     * Constructs the fully qualified class definition from the facade. Used for
476     * ValueObject, EmbeddedValue
477     * @param facade the class to construct the roo field definition.
478     * @return the Roo class definition.
479     */
480    public static List<String> getRooClass(final ClassifierFacade facade)
481    {
482        List<String> results = new ArrayList<String>();
483        String result = null;
484        if (facade.isEmbeddedValue() || facade.hasStereotype("ValueObject"))
485        {
486            if (facade.isEmbeddedValue())
487            {
488                result = "embeddable --class ";
489            }
490            else if (facade.hasStereotype("ValueObject"))
491            {
492                result = "class --class ";
493            }
494            result += facade.getFullyQualifiedName() + " --permitReservedWords";
495            if (facade.isAbstract())
496            {
497                result += " --abstract";
498            }
499            if (facade.getGeneralization() != null)
500            {
501                result += " --extends " + facade.getGeneralization().getFullyQualifiedName();
502            }
503            results.add(result);
504            for (AttributeFacade attr : facade.getAttributes())
505            {
506                results.add(getRooField(attr));
507            }
508            //results.add("");
509        }
510        // Old style Java 1.4 enumeration class
511        /*else if (facade.hasStereotype("Enumeration"))
512        {
513            result = "enum type --class " + facade.getName() + " --permitReservedWords";
514            results.add(result);
515            for (AttributeFacade literal : facade.getAttributes())
516            {
517                result = "enum constant --name " + literal.getName();
518                results.add(result);
519            }
520            results.add("");
521        }*/
522        return results;
523    }
524
525    /**
526     * Constructs the fully qualified class name from the packageName and name.
527     * Removes the words 'Test' and 'TestCase' because Roo cannot create tests for Entities
528     * with those names.
529     * @param entity the entity to construct the roo script definition.
530     * @return the Roo field definition.
531     */
532    public static String getRooEntityName(final Entity entity)
533    {
534        return StringUtils.remove(entity.getFullyQualifiedName(), "Test");
535    }
536
537    /**
538     * Constructs the fully qualified class name from the packageName and name.
539     * @param entity the entity to construct the roo script definition.
540     * @param recordType Either 'dao' or 'repository'
541     * @return the Roo field definition.
542     */
543    public static List<String> getRooEntity(final Entity entity, String recordType)
544    {
545        List<String> results = new ArrayList<String>();
546        Collection<ModelElementFacade> identifiers = entity.getIdentifiers(false);
547        String identifierLine = null;
548        // Keep track of entities already output, so that descendants are created after ancestors.
549        if (entity.isCompositeIdentifier())
550        {
551            String line = "embeddable --class " + StringUtils.remove(entity.getFullyQualifiedIdentifierTypeName(), "Test") + " --serializable";
552            //String line = "embeddable --class " + entity.getFullyQualifiedIdentifierTypeName() + " --serializable";
553            results.add(line);
554            for (AssociationEndFacade associationEnd : entity.getIdentifierAssociationEnds())
555            {
556                //results.add(SpringUtils.getRooField(associationEnd));
557                if (associationEnd.isMany2One())
558                {
559                    line = "field other --fieldName " + associationEnd.getOtherEnd().getName() + " --type " + associationEnd.getOtherEnd().getType().getFullyQualifiedName();
560                    results.add(line);
561                }
562            }
563            for (ModelElementFacade identifier : identifiers)
564            {
565                logger.info("getRooField identifier: " + getRooField(identifier) + " for " + identifier);
566                results.add(SpringUtils.getRooField(identifier));
567            }
568            identifierLine = " --identifierField " + entity.getIdentifierName() + " --identifierType " + StringUtils.remove(entity.getFullyQualifiedIdentifierTypeName(), "Test");
569        }
570        // Hibernate cartridge automatically adds default identifier if none, spring cartridge does not
571        else if (entity.getIdentifiers(false).size()==0)
572        {
573            identifierLine = " --identifierField id --identifierType java.lang.Long --identifierColumn ID";
574        }
575        else if (entity.getIdentifiers(false).size()==1)
576        {
577            ModelElementFacade id = entity.getIdentifiers(false).iterator().next();
578            String identifierType = entity.getFullyQualifiedIdentifierTypeName();
579            // Identifier properties can be either attribute or associationEnd. If end, associated class identifiers are added to this class identifiers.
580            if (id instanceof EntityAttribute)
581            {
582                ClassifierFacade type = ((EntityAttribute)id).getType();
583                if (type.isPrimitive())
584                {
585                    // Primitive type not allowed for identifier in Spring Roo
586                    identifierType = type.getWrapperName();
587                }
588            }
589            // Some test models have 'Test' in entity/attribute names, conflicting with names created for test scaffolding
590            identifierLine = " --identifierField " + entity.getIdentifierName() + " --identifierType " + StringUtils.remove(identifierType, "Test");
591        }
592        else
593        {
594            // Should never get to this place
595        }
596        // Identifiers: identifiers.size()
597        String activeRecord = " --activeRecord true";
598        // false = Use Spring Data JPA, no DAOs. True = Dao helpers
599        if (recordType.equals("active"))
600        {
601            activeRecord = " --activeRecord false";
602        }
603        String mappedSuperclass = "";
604        if (entity.hasStereotype("MappedSuperclass"))
605        {
606            mappedSuperclass = " --mappedSuperclass";
607        }
608        String extension = "";
609        GeneralizableElementFacade general = entity.getGeneralization();
610        if (general != null)
611        {
612            extension = " --extends " + general.getFullyQualifiedName();
613        }
614        String isAbstract = "";
615        if (entity.isAbstract())
616        {
617            isAbstract = " --abstract";
618        }
619        String schema = "";
620        if (StringUtils.isNotBlank(entity.getSchema()))
621        {
622            schema = " --schema " + entity.getSchema();
623        }
624        String version = "";
625        String entityVersion = (String) entity.findTaggedValue("andromda_hibernate_version");
626        for (AttributeFacade attr : entity.getAttributes())
627        {
628            if (attr.hasStereotype("Version"))
629            {
630                version = " --versionField " + attr.getName() + " --versionColumn " + ((EntityAttribute)attr).getColumnName() + " --versionType " + attr.getType().getFullyQualifiedName();
631            }
632        }
633        // TODO Check for global configured property "versionProperty" for adding version property to all entities
634        if (StringUtils.isNotBlank(entityVersion) && StringUtils.isBlank(version))
635        {
636            // Add automatic version identifier to entity definition
637            version = " --versionField version --versionColumn VERSION --versionType java.lang.Integer";
638        }
639        // TODO version*, inheritanceType, persistenceUnit, entityName, sequenceName
640        String line = "entity jpa --class " + StringUtils.remove(entity.getFullyQualifiedName(), "Test") + activeRecord + " --table " + entity.getTableName() + identifierLine + mappedSuperclass + extension + version + isAbstract + schema + " --equals --serializable --testAutomatically --permitReservedWords";
641        results.add(StringUtils.replace(line, "  ", " "));
642        return results;
643    }
644
645    /**
646     * Constructs the fully qualified class name from the packageName and name.
647     * @param facade the property (attribute or associationEnd) to construct the roo field definition.
648     * @return the Roo field definition.
649     */
650    public static String getRooField(final ModelElementFacade facade)
651    {
652        String result = " --fieldName " + facade.getName();
653        if (facade instanceof AssociationEndFacade)
654        {
655            AssociationEndFacade end = (AssociationEndFacade)facade;
656            ClassifierFacade type = end.getOtherEnd().getType();
657            String typeName = " --type " + type.getFullyQualifiedName();
658            if (end.isMany2One())
659            {
660                result = "field reference " + result + typeName;
661            }
662            else if (end.isOne2Many())
663            {
664                result = "field set " + result + typeName;
665            }
666            else
667            {
668                result = "field other " + result + typeName;
669            }
670            //System.out.println(end.getBindedFullyQualifiedName(end) + " " + end.getQualifiedName());
671            String owner = end.getFullyQualifiedName();
672            if (owner.lastIndexOf('.') > 0)
673            {
674                owner = owner.substring(0, owner.lastIndexOf('.'));
675                result += " --class " + owner;
676            }
677            else
678            {
679                logger.error("getRooField invalid owner: " + owner + " for " + facade.getFullyQualifiedName());
680            }
681        }
682        else if (facade instanceof AttributeFacade)
683        {
684            AttributeFacade attribute = (AttributeFacade)facade;
685            ClassifierFacade type = attribute.getType();
686            String typeName = " --type " + type.getFullyQualifiedName();
687            //logger.info("getRooField " + attribute.getFullyQualifiedName() + " type=" + type.getFullyQualifiedName() + " Integer=" + type.isIntegerType());
688            if ((attribute.isMany() || attribute.getName().endsWith("[]")) && !type.isDataType())
689            {
690                // The Many side of M:1 relationship, as an Attribute instead of an AssociationEnd
691                result = "field reference " + result + typeName;
692            }
693            // Version attribute is specified in the entity definition, skip this field definition
694            else if (type.getFullyQualifiedName().endsWith("[]") || attribute.hasStereotype("Version"))
695            {
696                // TODO: Convert DataType * to column Map with association. Punt for now.
697                result = "";
698            }
699            else
700            {
701                String primitive = "";
702                if (type.isPrimitive())
703                {
704                    primitive = " --primitive ";
705                }
706                if (type.isIntegerType() || type.isLongType() || type.isDoubleType() || type.isFloatType())
707                {
708                    result = "field number " + result + primitive + typeName;
709                }
710                else if (type.isBooleanType())
711                {
712                    result = "field boolean " + result + primitive;
713                }
714                else if (type.isStringType() || type.isCharacterType())
715                {
716                    result = "field string " + result;
717                }
718                else if (type.isDateType())
719                {
720                    result = "field date " + result + typeName;
721                }
722                // EmbeddedValue cannot have column, notNull, comment options in Roo
723                else if (type.isEmbeddedValue())
724                {
725                    result = "field embedded " + result + typeName;
726                }
727                else if (type.isEnumeration())
728                {
729                    result = "field enum " + result + typeName;
730                }
731                else if (type.isDateType())
732                {
733                    result = "field reference " + result + typeName;
734                }
735                else if (type.isDateType())
736                {
737                    result = "field set " + result + typeName;
738                }
739                else
740                {
741                    result = "field other " + result + typeName;
742                }
743                if (attribute instanceof EntityAttribute && !type.isEmbeddedValue())
744                {
745                    String column = ((EntityAttribute)attribute).getColumnName();
746                    if (StringUtils.isNotBlank(column))
747                    {
748                        result += " --column " + column;
749                    }
750                }
751                else
752                {
753                    //logger.error("getRooField facade should be EntityAttribute: " + attribute);
754                }
755                int lower = attribute.getLower();
756                if (lower > 0 && !type.isEmbeddedValue())
757                {
758                    result += " --notNull ";
759                }
760                String comment = attribute.getDocumentation("", 9999, false);
761                if (StringUtils.isNotBlank(comment) && !type.isEmbeddedValue())
762                {
763                    result += " --comment \"" + comment + "\"";
764                }
765            }
766        }
767        else
768        {
769            throw new RuntimeException("getRooField facade must be Attribute or AssociationEnd: " + facade);
770        }
771        if (StringUtils.isNotBlank(result))
772        {
773            result = StringUtils.replace(result + " --permitReservedWords", "  ", " ");
774        }
775        return result;
776    }
777    private static final SimpleDateFormat DF = new SimpleDateFormat("MM/dd/yyyy HH:mm:ssZ");
778    /**
779     * Returns the current Date in the specified format. $conversionUtils does not seem to work in vsl.
780     *
781     * @param format The format for the output date
782     * @return the current date in the specified format.
783     */
784    public static String getDate(String format)
785    {
786        /*if (DF == null || !format.equals(DF.toLocalizedPattern()))
787        {
788            DF = new SimpleDateFormat(format);
789        }*/
790        return DF.format(new Date());
791    }
792
793    /**
794     * Returns the current Date in the specified format.
795     *
796     * @return the current date with the default format .
797     */
798    public static String getDate()
799    {
800        return DF.format(new Date());
801    }
802}