SpringUtils.java

package org.andromda.cartridges.spring;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import org.andromda.cartridges.spring.metafacades.SpringService;
import org.andromda.metafacades.uml.AssociationEndFacade;
import org.andromda.metafacades.uml.AttributeFacade;
import org.andromda.metafacades.uml.ClassifierFacade;
import org.andromda.metafacades.uml.Entity;
import org.andromda.metafacades.uml.EntityAttribute;
import org.andromda.metafacades.uml.EnumerationFacade;
import org.andromda.metafacades.uml.EnumerationLiteralFacade;
import org.andromda.metafacades.uml.GeneralizableElementFacade;
import org.andromda.metafacades.uml.ModelElementFacade;
import org.andromda.metafacades.uml.Role;
import org.andromda.metafacades.uml.Service;
import org.andromda.utils.StringUtilsHelper;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;


/**
 * Contains utilities used within the Spring cartridge.
 *
 * @author Chad Brandon
 * @author Joel Kozikowski
 */
public class SpringUtils
{
    /**
     * The logger instance.
     */
    private static final Logger logger = Logger.getLogger(SpringUtils.class);

    /**
     * Retrieves all roles from the given <code>services</code> collection.
     *
     * @param services the collection services.
     * @return all roles from the collection.
     */
    public Collection<Role> getAllRoles(Collection<Service> services)
    {
        final Collection<Role> allRoles = new LinkedHashSet<Role>();
        CollectionUtils.forAllDo(
            services,
            new Closure()
            {
                public void execute(Object object)
                {
                    if (object != null && Service.class.isAssignableFrom(object.getClass()))
                    {
                        allRoles.addAll(((Service)object).getAllRoles());
                    }
                }
            });
        return allRoles;
    }

    /**
     * Indicates if any remote EJBs are present in the collection
     * of services.
     *
     * @param services the collection of services to check.
     * @return true/false.
     */
    public boolean remoteEjbsPresent(final Collection<Service> services)
    {
        boolean present = services != null && !services.isEmpty();
        if (present)
        {
            present =
                CollectionUtils.find(
                    services,
                    new Predicate()
                    {
                        public boolean evaluate(final Object object)
                        {
                            boolean valid = false;
                            if (object instanceof SpringService)
                            {
                                final SpringService service = (SpringService)object;
                                valid = service.isEjbRemoteView();
                            }
                            return valid;
                        }
                    }) != null;
        }
        return present;
    }

    /**
     * Indicates if any local EJBs are present in the collection
     * of services.
     *
     * @param services the collection of services to check.
     * @return true/false.
     */
    public boolean localEjbsPresent(final Collection<Service> services)
    {
        boolean present = services != null && !services.isEmpty();
        if (present)
        {
            present =
                CollectionUtils.find(
                    services,
                    new Predicate()
                    {
                        public boolean evaluate(final Object object)
                        {
                            boolean valid = false;
                            if (object instanceof SpringService)
                            {
                                final SpringService service = (SpringService)object;
                                valid = service.isEjbLocalView();
                            }
                            return valid;
                        }
                    }) != null;
        }
        return present;
    }

    /**
     * Indicates if any Spring remotable services are present.
     *
     * @param services the collection of services to check.
     * @return true/false.
     */
    public boolean remotableServicesPresent(final Collection<Service> services)
    {
        boolean present = services != null && !services.isEmpty();
        if (present)
        {
            present =
                CollectionUtils.find(
                    services,
                    new Predicate()
                    {
                        public boolean evaluate(final Object object)
                        {
                            boolean valid = false;
                            if (object instanceof SpringService)
                            {
                                final SpringService service = (SpringService)object;
                                valid = service.isRemotable();
                            }
                            return valid;
                        }
                    }) != null;
        }
        return present;
    }

    /**
     * Indicates if any remotable services using Lingo are present.
     *
     * @param services the collection of services to check.
     * @return true/false.
     */
    public boolean lingoRemotableServicesPresent(final Collection<Service> services)
    {
        boolean present = services != null && !services.isEmpty();
        if (present)
        {
            present =
                CollectionUtils.find(
                        services,
                        new Predicate()
                        {
                            public boolean evaluate(final Object object)
                            {
                                boolean valid = false;
                                if (object instanceof SpringService)
                                {
                                    final SpringService service = (SpringService)object;
                                    valid = service.isRemotingTypeLingo();
                                }
                                return valid;
                            }
                        }) != null;
        }
        return present;
    }

    /**
     * Indicates if any private services are present.
     *
     * @param services the collection of services to check.
     * @return true/false.
     */
    public boolean privateServicesPresent(final Collection<Service> services)
    {
        boolean present = services != null && !services.isEmpty();
        if (present)
        {
            present =
                CollectionUtils.find(
                    services,
                    new Predicate()
                    {
                        public boolean evaluate(final Object object)
                        {
                            boolean valid = false;
                            if (object instanceof SpringService)
                            {
                                final SpringService service = (SpringService)object;
                                valid = service.isPrivate();
                            }
                            return valid;
                        }
                    }) != null;
        }
        return present;
    }

    /**
     * Indicates if any public (non private) services are present.
     *
     * @param services the collection of services to check.
     * @return true/false.
     */
    public boolean publicServicesPresent(final Collection<Service> services)
    {
        boolean present = services != null && !services.isEmpty();
        if (present)
        {
            present =
                CollectionUtils.find(
                    services,
                    new Predicate()
                    {
                        public boolean evaluate(final Object object)
                        {
                            boolean valid = false;
                            if (object instanceof SpringService)
                            {
                                final SpringService service = (SpringService)object;
                                valid = !service.isPrivate();
                            }
                            return valid;
                        }
                    }) != null;
        }
        return present;
    }

    /**
     * Based on the given <code>value</code>, this method will return
     * a formatted Spring property (including the handling of 'null').
     *
     * @param value the value from which to create the spring value.
     * @return the spring value.
     */
    public String getSpringPropertyValue(final String value)
    {
        String propertyValue = "";
        if (value != null)
        {
            if ("null".equalsIgnoreCase(value))
            {
                propertyValue = "<null/>";
            }
            else
            {
                propertyValue = "<value>" + value + "</value>";
            }
        }
        return propertyValue;
    }

    /**
     * Removes generics from string. Currently used to strip generics
     * from ejb-jar.xml method parameters.
     * @param parameter String containing generics
     * @return String with generics stripped
     */
    public String removeGenerics(final String parameter)
    {
        int position = parameter.indexOf('<');
        String result = parameter;
        if(position != -1)
        {
            result = result.substring(0, position);
        }
        return result;
    }

    /**
     * Are we generating code for a rich client? false.
     */
    private boolean richClient = false;


    /**
     * Sets if code is being generated for a rich client.
     * @param richClientProperty
     */
    public void setRichClient(final boolean richClientProperty)
    {
        this.richClient = richClientProperty;
    }

    /**
     * Returns TRUE if code is being generated for a rich client environment
     * @return richClient
     */
    public boolean isRichClient()
    {
        return this.richClient;
    }

    /**
     * Returns the class name part of a fully qualified name
     * @param fullyQualifiedName
     * @return just the "class name" part of the fully qualified name
     */
    public String getClassName(String fullyQualifiedName)
    {
       String className = null;
       if (StringUtils.isNotBlank(fullyQualifiedName))
       {
           int lastDot = fullyQualifiedName.lastIndexOf('.');
           if (lastDot >= 0)
           {
               className = fullyQualifiedName.substring(lastDot+1);
           }
           else
           {
               className = fullyQualifiedName;
           }
       }
       else
       {
           className = "";
       }

       return className;
    }


    /**
     * Returns the package name part of a fully qualified name
     * @param fullyQualifiedName
     * @return just the "package" part of the fully qualified name
     */
    public String getPackageName(String fullyQualifiedName)
    {
       String packageName = null;
       if (StringUtils.isNotBlank(fullyQualifiedName))
       {
           int lastDot = fullyQualifiedName.lastIndexOf('.');
           packageName = (lastDot >= 0 ? fullyQualifiedName.substring(0, lastDot) : "");
       }
       else
       {
           packageName = "";
       }

       return packageName;
    }

    /**
     * Returns an ordered set containing the argument model elements, model elements with a name that is already
     * used by another model element in the argument collection will not be returned.
     * The first operation with a name not already encountered will be returned, the order inferred by the
     * argument's iterator will determine the order of the returned list.
     *
     * @param modelElements a collection of model elements, elements that are not model elements will be ignored
     * @return the argument model elements without, elements with a duplicate name will only be recorded once
     */
    public List<ModelElementFacade> filterUniqueByName(Collection<ModelElementFacade> modelElements)
    {
        final Map<String, ModelElementFacade> filteredElements = new LinkedHashMap<String, ModelElementFacade>();

        for (final ModelElementFacade modelElement : modelElements)
        {
            /*final Object object = elementIterator.next();
            if (object instanceof ModelElementFacade)
            {*/
            if (!filteredElements.containsKey(modelElement.getName()))
            {
                filteredElements.put(modelElement.getName(), modelElement);
            }
            /*}*/
        }

        return new ArrayList<ModelElementFacade>(filteredElements.values());
    }

    /**
     * Formats the given type to the appropriate Hibernate query parameter value.
     *
     * @param type the type of the Hibernate query parameter.
     * @param value the current value to format.
     * @return the formatted value.
     */
    public String formatHibernateQueryParameterValue(final ClassifierFacade type, String value)
    {
        if (type != null)
        {
            if (type.isPrimitive())
            {
                value = "new " + type.getWrapperName() + '(' + value + ')';
            }
        }
        return value;
    }

    /**
     * Takes the given <code>names</code> and concatenates them in camel case
     * form.
     *
     * @param names the names to concatenate.
     * @return the result of the concatenation
     */
    public static String concatNamesCamelCase(final Collection<String> names)
    {
        String result = null;
        if (names != null)
        {
            result = StringUtilsHelper.lowerCamelCaseName(StringUtils.join(names.iterator(), " "));
        }
        return result;
    }

    /**
     * Constructs the fully qualified class name from the packageName and name.
     * @param packageName the package name to which the class belongs.
     * @param name the name of the class.
     * @return the fully qualified name.
     */
    public static String getFullyQualifiedClassName(final String packageName, final String name)
    {
        final StringBuilder fullName = new StringBuilder(StringUtils.trimToEmpty(packageName));
        if (fullName.length() > 0)
        {
            fullName.append('.');
        }
        fullName.append(name);
        return fullName.toString();
    }

    /**
     * Constructs the fully qualified class definition from the facade. Used for
     * ValueObject, EmbeddedValue
     * @param facade the class to construct the roo field definition.
     * @return the Roo class definition.
     */
    public static List<String> getRooEnum(final EnumerationFacade facade)
    {
        List<String> results = new ArrayList<String>();
        String result = "enum type --class " + facade.getFullyQualifiedName() + " --permitReservedWords";
        results.add(result);
        // Can't do for: because literal may be AttributeFacade or EnumerationLiteralFacade - ClassCastException
        Iterator literals = facade.getLiterals().iterator();
        while (literals.hasNext())
        {
            result = "enum constant --name " + ((ModelElementFacade)literals.next()).getName();
            results.add(result);
        }
        return results;
    }

    /**
     * Constructs the fully qualified class definition from the facade. Used for
     * ValueObject, EmbeddedValue
     * @param facade the class to construct the roo field definition.
     * @return the Roo class definition.
     */
    public static List<String> getRooClass(final ClassifierFacade facade)
    {
        List<String> results = new ArrayList<String>();
        String result = null;
        if (facade.isEmbeddedValue() || facade.hasStereotype("ValueObject"))
        {
            if (facade.isEmbeddedValue())
            {
                result = "embeddable --class ";
            }
            else if (facade.hasStereotype("ValueObject"))
            {
                result = "class --class ";
            }
            result += facade.getFullyQualifiedName() + " --permitReservedWords";
            if (facade.isAbstract())
            {
                result += " --abstract";
            }
            if (facade.getGeneralization() != null)
            {
                result += " --extends " + facade.getGeneralization().getFullyQualifiedName();
            }
            results.add(result);
            for (AttributeFacade attr : facade.getAttributes())
            {
                results.add(getRooField(attr));
            }
            //results.add("");
        }
        // Old style Java 1.4 enumeration class
        /*else if (facade.hasStereotype("Enumeration"))
        {
            result = "enum type --class " + facade.getName() + " --permitReservedWords";
            results.add(result);
            for (AttributeFacade literal : facade.getAttributes())
            {
                result = "enum constant --name " + literal.getName();
                results.add(result);
            }
            results.add("");
        }*/
        return results;
    }

    /**
     * Constructs the fully qualified class name from the packageName and name.
     * Removes the words 'Test' and 'TestCase' because Roo cannot create tests for Entities
     * with those names.
     * @param entity the entity to construct the roo script definition.
     * @return the Roo field definition.
     */
    public static String getRooEntityName(final Entity entity)
    {
        return StringUtils.remove(entity.getFullyQualifiedName(), "Test");
    }

    /**
     * Constructs the fully qualified class name from the packageName and name.
     * @param entity the entity to construct the roo script definition.
     * @param recordType Either 'dao' or 'repository'
     * @return the Roo field definition.
     */
    public static List<String> getRooEntity(final Entity entity, String recordType)
    {
        List<String> results = new ArrayList<String>();
        Collection<ModelElementFacade> identifiers = entity.getIdentifiers(false);
        String identifierLine = null;
        // Keep track of entities already output, so that descendants are created after ancestors.
        if (entity.isCompositeIdentifier())
        {
            String line = "embeddable --class " + StringUtils.remove(entity.getFullyQualifiedIdentifierTypeName(), "Test") + " --serializable";
            //String line = "embeddable --class " + entity.getFullyQualifiedIdentifierTypeName() + " --serializable";
            results.add(line);
            for (AssociationEndFacade associationEnd : entity.getIdentifierAssociationEnds())
            {
                //results.add(SpringUtils.getRooField(associationEnd));
                if (associationEnd.isMany2One())
                {
                    line = "field other --fieldName " + associationEnd.getOtherEnd().getName() + " --type " + associationEnd.getOtherEnd().getType().getFullyQualifiedName();
                    results.add(line);
                }
            }
            for (ModelElementFacade identifier : identifiers)
            {
                logger.info("getRooField identifier: " + getRooField(identifier) + " for " + identifier);
                results.add(SpringUtils.getRooField(identifier));
            }
            identifierLine = " --identifierField " + entity.getIdentifierName() + " --identifierType " + StringUtils.remove(entity.getFullyQualifiedIdentifierTypeName(), "Test");
        }
        // Hibernate cartridge automatically adds default identifier if none, spring cartridge does not
        else if (entity.getIdentifiers(false).size()==0)
        {
            identifierLine = " --identifierField id --identifierType java.lang.Long --identifierColumn ID";
        }
        else if (entity.getIdentifiers(false).size()==1)
        {
            ModelElementFacade id = entity.getIdentifiers(false).iterator().next();
            String identifierType = entity.getFullyQualifiedIdentifierTypeName();
            // Identifier properties can be either attribute or associationEnd. If end, associated class identifiers are added to this class identifiers.
            if (id instanceof EntityAttribute)
            {
                ClassifierFacade type = ((EntityAttribute)id).getType();
                if (type.isPrimitive())
                {
                    // Primitive type not allowed for identifier in Spring Roo
                    identifierType = type.getWrapperName();
                }
            }
            // Some test models have 'Test' in entity/attribute names, conflicting with names created for test scaffolding
            identifierLine = " --identifierField " + entity.getIdentifierName() + " --identifierType " + StringUtils.remove(identifierType, "Test");
        }
        else
        {
            // Should never get to this place
        }
        // Identifiers: identifiers.size()
        String activeRecord = " --activeRecord true";
        // false = Use Spring Data JPA, no DAOs. True = Dao helpers
        if (recordType.equals("active"))
        {
            activeRecord = " --activeRecord false";
        }
        String mappedSuperclass = "";
        if (entity.hasStereotype("MappedSuperclass"))
        {
            mappedSuperclass = " --mappedSuperclass";
        }
        String extension = "";
        GeneralizableElementFacade general = entity.getGeneralization();
        if (general != null)
        {
            extension = " --extends " + general.getFullyQualifiedName();
        }
        String isAbstract = "";
        if (entity.isAbstract())
        {
            isAbstract = " --abstract";
        }
        String schema = "";
        if (StringUtils.isNotBlank(entity.getSchema()))
        {
            schema = " --schema " + entity.getSchema();
        }
        String version = "";
        String entityVersion = (String) entity.findTaggedValue("andromda_hibernate_version");
        for (AttributeFacade attr : entity.getAttributes())
        {
            if (attr.hasStereotype("Version"))
            {
                version = " --versionField " + attr.getName() + " --versionColumn " + ((EntityAttribute)attr).getColumnName() + " --versionType " + attr.getType().getFullyQualifiedName();
            }
        }
        // TODO Check for global configured property "versionProperty" for adding version property to all entities
        if (StringUtils.isNotBlank(entityVersion) && StringUtils.isBlank(version))
        {
            // Add automatic version identifier to entity definition
            version = " --versionField version --versionColumn VERSION --versionType java.lang.Integer";
        }
        // TODO version*, inheritanceType, persistenceUnit, entityName, sequenceName
        String line = "entity jpa --class " + StringUtils.remove(entity.getFullyQualifiedName(), "Test") + activeRecord + " --table " + entity.getTableName() + identifierLine + mappedSuperclass + extension + version + isAbstract + schema + " --equals --serializable --testAutomatically --permitReservedWords";
        results.add(StringUtils.replace(line, "  ", " "));
        return results;
    }

    /**
     * Constructs the fully qualified class name from the packageName and name.
     * @param facade the property (attribute or associationEnd) to construct the roo field definition.
     * @return the Roo field definition.
     */
    public static String getRooField(final ModelElementFacade facade)
    {
        String result = " --fieldName " + facade.getName();
        if (facade instanceof AssociationEndFacade)
        {
            AssociationEndFacade end = (AssociationEndFacade)facade;
            ClassifierFacade type = end.getOtherEnd().getType();
            String typeName = " --type " + type.getFullyQualifiedName();
            if (end.isMany2One())
            {
                result = "field reference " + result + typeName;
            }
            else if (end.isOne2Many())
            {
                result = "field set " + result + typeName;
            }
            else
            {
                result = "field other " + result + typeName;
            }
            //System.out.println(end.getBindedFullyQualifiedName(end) + " " + end.getQualifiedName());
            String owner = end.getFullyQualifiedName();
            if (owner.lastIndexOf('.') > 0)
            {
                owner = owner.substring(0, owner.lastIndexOf('.'));
                result += " --class " + owner;
            }
            else
            {
                logger.error("getRooField invalid owner: " + owner + " for " + facade.getFullyQualifiedName());
            }
        }
        else if (facade instanceof AttributeFacade)
        {
            AttributeFacade attribute = (AttributeFacade)facade;
            ClassifierFacade type = attribute.getType();
            String typeName = " --type " + type.getFullyQualifiedName();
            //logger.info("getRooField " + attribute.getFullyQualifiedName() + " type=" + type.getFullyQualifiedName() + " Integer=" + type.isIntegerType());
            if ((attribute.isMany() || attribute.getName().endsWith("[]")) && !type.isDataType())
            {
                // The Many side of M:1 relationship, as an Attribute instead of an AssociationEnd
                result = "field reference " + result + typeName;
            }
            // Version attribute is specified in the entity definition, skip this field definition
            else if (type.getFullyQualifiedName().endsWith("[]") || attribute.hasStereotype("Version"))
            {
                // TODO: Convert DataType * to column Map with association. Punt for now.
                result = "";
            }
            else
            {
                String primitive = "";
                if (type.isPrimitive())
                {
                    primitive = " --primitive ";
                }
                if (type.isIntegerType() || type.isLongType() || type.isDoubleType() || type.isFloatType())
                {
                    result = "field number " + result + primitive + typeName;
                }
                else if (type.isBooleanType())
                {
                    result = "field boolean " + result + primitive;
                }
                else if (type.isStringType() || type.isCharacterType())
                {
                    result = "field string " + result;
                }
                else if (type.isDateType())
                {
                    result = "field date " + result + typeName;
                }
                // EmbeddedValue cannot have column, notNull, comment options in Roo
                else if (type.isEmbeddedValue())
                {
                    result = "field embedded " + result + typeName;
                }
                else if (type.isEnumeration())
                {
                    result = "field enum " + result + typeName;
                }
                else if (type.isDateType())
                {
                    result = "field reference " + result + typeName;
                }
                else if (type.isDateType())
                {
                    result = "field set " + result + typeName;
                }
                else
                {
                    result = "field other " + result + typeName;
                }
                if (attribute instanceof EntityAttribute && !type.isEmbeddedValue())
                {
                    String column = ((EntityAttribute)attribute).getColumnName();
                    if (StringUtils.isNotBlank(column))
                    {
                        result += " --column " + column;
                    }
                }
                else
                {
                    //logger.error("getRooField facade should be EntityAttribute: " + attribute);
                }
                int lower = attribute.getLower();
                if (lower > 0 && !type.isEmbeddedValue())
                {
                    result += " --notNull ";
                }
                String comment = attribute.getDocumentation("", 9999, false);
                if (StringUtils.isNotBlank(comment) && !type.isEmbeddedValue())
                {
                    result += " --comment \"" + comment + "\"";
                }
            }
        }
        else
        {
            throw new RuntimeException("getRooField facade must be Attribute or AssociationEnd: " + facade);
        }
        if (StringUtils.isNotBlank(result))
        {
            result = StringUtils.replace(result + " --permitReservedWords", "  ", " ");
        }
        return result;
    }
    private static final SimpleDateFormat DF = new SimpleDateFormat("MM/dd/yyyy HH:mm:ssZ");
    /**
     * Returns the current Date in the specified format. $conversionUtils does not seem to work in vsl.
     *
     * @param format The format for the output date
     * @return the current date in the specified format.
     */
    public static String getDate(String format)
    {
        /*if (DF == null || !format.equals(DF.toLocalizedPattern()))
        {
            DF = new SimpleDateFormat(format);
        }*/
        return DF.format(new Date());
    }

    /**
     * Returns the current Date in the specified format.
     *
     * @return the current date with the default format .
     */
    public static String getDate()
    {
        return DF.format(new Date());
    }
}