org.gvnix.web.screen.roo.addon.WebScreenOperationsImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.gvnix.web.screen.roo.addon.WebScreenOperationsImpl.java

Source

/*
 * gvNIX. Spring Roo based RAD tool for Conselleria d'Infraestructures i
 * Transport - Generalitat Valenciana Copyright (C) 2010, 2011 CIT - Generalitat
 * Valenciana
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.gvnix.web.screen.roo.addon;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.xml.transform.Transformer;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.gvnix.support.MessageBundleUtils;
import org.gvnix.support.OperationUtils;
import org.gvnix.web.i18n.roo.addon.ValencianCatalanLanguage;
import org.springframework.roo.addon.jpa.activerecord.JpaActiveRecordMetadata;
import org.springframework.roo.addon.propfiles.PropFileOperations;
import org.springframework.roo.addon.web.mvc.controller.scaffold.RooWebScaffold;
import org.springframework.roo.addon.web.mvc.jsp.i18n.I18n;
import org.springframework.roo.addon.web.mvc.jsp.i18n.I18nSupport;
import org.springframework.roo.addon.web.mvc.jsp.i18n.languages.SpanishLanguage;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.TypeManagementService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.ArrayAttributeValue;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.classpath.operations.AbstractOperations;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.process.manager.MutableFile;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.util.DomUtils;
import org.springframework.roo.support.util.XmlElementBuilder;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import org.osgi.service.component.ComponentContext;

/**
 * Implementation of web MVC screen patterns operations.
 * 
 * @author Jose Manuel Viv (jmvivo at disid dot com) at <a
 *         href="http://www.disid.com">DiSiD Technologies S.L.</a> made for <a
 *         href="http://www.cit.gva.es">Conselleria d'Infraestructures i
 *         Transport</a>
 * @author scar Rovira (orovira at disid dot com) at <a
 *         href="http://www.disid.com">DiSiD Technologies S.L.</a> made for <a
 *         href="http://www.cit.gva.es">Conselleria d'Infraestructures i
 *         Transport</a>
 * @since 0.8
 */
@Component
@Service
public class WebScreenOperationsImpl extends AbstractOperations implements WebScreenOperations {
    private static final String VALUE = "value";
    private static final String FORM_BACKING_OBJ = "formBackingObject";
    private static final String ARRAY_ELEM = "__ARRAY_ELEMENT__";
    private static final String VAR = "var";

    private static Logger logger = Logger.getLogger(WebScreenOperationsImpl.class.getName());

    /** Name of {@link GvNIXPattern} attribute value */
    public static final JavaSymbolName PAT_ANN_ATTR_VAL_NAME = new JavaSymbolName(VALUE);

    /** {@link GvNIXPattern} JavaType */
    public static final JavaType PATTERN_ANNOTATION = new JavaType(GvNIXPattern.class.getName());

    /** Name of {@link GvNIXRelationPattern} attribute value */
    public static final JavaSymbolName REL_PAT_ANN_ATTR_VAL_NAME = new JavaSymbolName(VALUE);

    /** {@link GvNIXRelationPattern} JavaType */
    public static final JavaType RELATION_PATTERN_ANNOTATION = new JavaType(GvNIXRelationsPattern.class.getName());

    /** {@link GvNIXPattern} JavaType */
    public static final JavaType RELATED_PATTERN_ANNOTATION = new JavaType(GvNIXRelatedPattern.class.getName());

    /** Name of {@link GvNIXRelatedPattern} attribute value */
    public static final JavaSymbolName RELTD_PAT_ANN_ATTR_VAL_NAME = new JavaSymbolName(VALUE);

    /** Name of {@link RooWebScaffold} attribute formBackingObject */
    public static final JavaSymbolName SCAFF_ANN_ATTR_FORMBOBJ = new JavaSymbolName(FORM_BACKING_OBJ);

    /** {@link RooWebScaffold} JavaType */
    public static final JavaType ROOWEBSCAFFOLD_ANNOTATION = new JavaType(RooWebScaffold.class.getName());

    /** {@link GvNIXEntityBatch} JavaType */
    public static final JavaType ENTITYBATCH_ANNOTATION = new JavaType(GvNIXEntityBatch.class.getName());

    /** {@link OneToMany} JavaType */
    public static final JavaType ONETOMANY_ANNOTATION = new JavaType("javax.persistence.OneToMany");

    /** {@link OneToMany} JavaType */
    public static final JavaType MANYTOMANY_ANNOTATION = new JavaType("javax.persistence.ManyToMany");

    private ComponentContext cContext;

    protected void activate(final ComponentContext componentContext) {
        cContext = componentContext;
        context = cContext.getBundleContext();
    }

    /**
     * MetadataService offers access to Roo's metadata model, use it to retrieve
     * any available metadata by its MID
     */
    @Reference
    private MetadataService metadataService;

    @Reference
    private TypeLocationService typeLocationService;

    @Reference
    private PatternService patternService;

    @Reference
    private WebScreenConfigService configService;

    @Reference
    private ProjectOperations projectOperations;

    @Reference
    PropFileOperations propFileOperations;

    @Reference
    private I18nSupport i18nSupport;

    @Reference
    private TypeManagementService typeManagementService;

    /** {@inheritDoc} */
    public boolean isPatternCommandAvailable() {

        return configService.isSpringMvcProject();
    }

    /** {@inheritDoc} */
    public boolean addPattern(JavaType controllerClass, JavaSymbolName name, WebPatternType pattern) {

        Validate.notNull(controllerClass, "controller class is required");
        Validate.notNull(name, "pattern name is required");
        Validate.notNull(pattern, "pattern type is required");

        // Get mutableTypeDetails from controllerClass. Also checks javaType is
        // a controller
        ClassOrInterfaceTypeDetails controllerDetails = patternService.getControllerTypeDetails(controllerClass);

        // Check if controller entity is a active-record entity (supported)
        Validate.isTrue(isControllerEntityActiveRecord(controllerDetails),
                "This operation only supports controllers of entities with @RooJpaActiveRecord annotation");

        // Check if there are pattern names used more than once in project
        Validate.isTrue(
                !patternService.existsMasterPatternDuplicated()
                        && !patternService.existsMasterPatternDefined(name.getSymbolName()),
                "There is a pattern name used more than once in the project");

        // All checks passed OK

        // Test if the annotation already exists on the target type
        AnnotationMetadata annotationMetadata = MemberFindingUtils
                .getAnnotationOfType(controllerDetails.getAnnotations(), PATTERN_ANNOTATION);

        // Get pattern attributes of the controller
        List<StringAttributeValue> patternList = patternService.getControllerMasterPattern(controllerClass);

        // Build string parameter for the pattern
        String patternParameter = name.toString().concat("=").concat(pattern.toString());

        // Adds new pattern
        patternList.add(new StringAttributeValue(new JavaSymbolName(ARRAY_ELEM), patternParameter));

        // Prepare annotation builder
        AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder(PATTERN_ANNOTATION);
        annotationBuilder
                .addAttribute(new ArrayAttributeValue<StringAttributeValue>(PAT_ANN_ATTR_VAL_NAME, patternList));

        // Add or update annotation to target type
        ClassOrInterfaceTypeDetailsBuilder mutableTypeDetailsBuilder = new ClassOrInterfaceTypeDetailsBuilder(
                controllerDetails);
        if (annotationMetadata != null) {

            mutableTypeDetailsBuilder.updateTypeAnnotation(annotationBuilder.build(),
                    new HashSet<JavaSymbolName>());

        } else {

            mutableTypeDetailsBuilder.addAnnotation(annotationBuilder.build());
        }
        typeManagementService.createOrUpdateTypeOnDisk(mutableTypeDetailsBuilder.build());

        // Tabular style patterns requires batch operations
        if (pattern.equals(WebPatternType.tabular) || pattern.equals(WebPatternType.tabular_edit_register)) {

            // TODO If tabular_edit_register, only delete entity in batch
            // required
            annotateTypeWithGvNIXEntityBatch(getFormBakingObject(controllerDetails));
        }

        return true;
    }

    /** {@inheritDoc} */
    public boolean isRelationPatternCommandAvailable() {
        return configService.arePattrenArtifactsInstalled();
    }

    /** {@inheritDoc} */
    public boolean addRelationPattern(JavaType controllerClass, JavaSymbolName name, JavaSymbolName field,
            WebPatternType type) {

        Validate.notNull(controllerClass, "controller is required");
        Validate.notNull(name, "name is required");
        Validate.notNull(field, "field is required");
        Validate.notNull(type, "type is required");

        // Get mutableTypeDetails from controllerClass. Also checks javaType is
        // a controller
        ClassOrInterfaceTypeDetails mutableTypeDetails = patternService.getControllerTypeDetails(controllerClass);

        // Check if controller entity is a active-record entity (supported)
        Validate.isTrue(isControllerEntityActiveRecord(mutableTypeDetails),
                "This operation only supports controllers of entities with @RooJpaActiveRecord annotation");

        // Check if pattern name is already used as value of @GvNIXPattern
        Validate.isTrue(patternService.existsControllerMasterPattern(controllerClass, name),
                "Pattern name '".concat(name.getSymbolName()).concat("' not found in values of ")
                        .concat(PATTERN_ANNOTATION.getSimpleTypeName()).concat(" annotation in controller ")
                        .concat(controllerClass.getFullyQualifiedTypeName()));

        // Check if user is setting Detail register
        // if so, this is not supported yet, so abort
        List<StringAttributeValue> patternValues = patternService.getControllerMasterPattern(controllerClass);
        if (!patternValues.isEmpty() && type == WebPatternType.register) {

            logger.warning("For pattern name '".concat(name.getSymbolName()).concat(
                    "' you are setting detail as register. Currently gvNIX doesn't support 'Detail register' pattern"));
            return false;
        }

        // Get @RooController annotation to get formbackingObject definition
        AnnotationMetadata controllerAnnotationMetadata = MemberFindingUtils
                .getAnnotationOfType(mutableTypeDetails.getAnnotations(), ROOWEBSCAFFOLD_ANNOTATION);

        // Check if filed exists in formBackingObject entity and is OneToMany or
        // ManyToMany
        JavaType formBackingObject = (JavaType) controllerAnnotationMetadata.getAttribute(SCAFF_ANN_ATTR_FORMBOBJ)
                .getValue();
        Validate.notNull(patternService.getEntityToManyField(formBackingObject, field.getSymbolName()),
                "Field '".concat(field.getSymbolName()).concat("' not found or is not *ToMany in '")
                        .concat(formBackingObject.getFullyQualifiedTypeName()).concat("' entity."));

        // All checks passed OK.

        // List of pattern to use in annotation
        List<StringAttributeValue> patternList = new ArrayList<StringAttributeValue>();

        // Prepare pattern prefix and previous value
        String patternPrefix = name.getSymbolName().concat(":");
        String patternFieldValue = field.getSymbolName().concat("=").concat(type.name());
        String finalValue = patternPrefix.concat(" ").concat(patternFieldValue);
        int previousValueIndex = -1;

        // Test if the annotation already exists on the target type
        AnnotationMetadata annotationMetadata = MemberFindingUtils
                .getAnnotationOfType(mutableTypeDetails.getAnnotations(), RELATION_PATTERN_ANNOTATION);

        // Check if @GvNIXRelationPattern
        boolean isAlreadyAnnotated = false;
        if (annotationMetadata != null) {
            // @GvNIXRelationPattern already exists

            // Loads previously registered values into patterList
            // Also identifies previous value of target pattern
            AnnotationAttributeValue<?> previousAnnotationValues = annotationMetadata
                    .getAttribute(REL_PAT_ANN_ATTR_VAL_NAME);

            if (previousAnnotationValues != null) {

                @SuppressWarnings("unchecked")
                List<StringAttributeValue> previousValues = (List<StringAttributeValue>) previousAnnotationValues
                        .getValue();

                if (previousValues != null && !previousValues.isEmpty()) {
                    String strValue;
                    int tmpIndex = 0;
                    for (StringAttributeValue value : previousValues) {
                        strValue = value.getValue();
                        if (strValue.trim().startsWith(patternPrefix)) {
                            // Found previous

                            // Check for duplicates pattern definition
                            Validate.isTrue(previousValueIndex < 0,
                                    "Duplicate definition for pattern '".concat(name.getSymbolName())
                                            .concat("' in ").concat(RELATION_PATTERN_ANNOTATION.getSimpleTypeName())
                                            .concat(" annotation in ")
                                            .concat(controllerClass.getFullyQualifiedTypeName()));

                            // Check if already exist field declaration for
                            // pattern name
                            boolean existsField = existsFieldPatternDeclaration(strValue, field);
                            Validate.isTrue(!existsField,
                                    "Field '".concat(field.getSymbolName())
                                            .concat("' is already defined for pattern '")
                                            .concat(name.getSymbolName()).concat("' in controller ")
                                            .concat(controllerClass.getFullyQualifiedTypeName()));
                            // Prepare final value
                            finalValue = value.getValue().concat(", ").concat(patternFieldValue);
                            // skip from pattern list but store position
                            // to prevent unnecessary changes in file
                            previousValueIndex = tmpIndex;

                        } else {
                            patternList.add(value);
                        }
                        tmpIndex++;
                    }
                }
            }
            isAlreadyAnnotated = true;
        }

        // Adds final pattern value
        if (previousValueIndex >= 0) {
            // Restore previous value position if any
            patternList.add(previousValueIndex,
                    new StringAttributeValue(new JavaSymbolName(ARRAY_ELEM), finalValue));
        } else {
            patternList.add(new StringAttributeValue(new JavaSymbolName(ARRAY_ELEM), finalValue));
        }

        // Prepare annotation builder
        AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder(RELATION_PATTERN_ANNOTATION);
        AnnotationAttributeValue<?> annotationValues = new ArrayAttributeValue<StringAttributeValue>(
                REL_PAT_ANN_ATTR_VAL_NAME, patternList);
        annotationBuilder.addAttribute(annotationValues);

        // Add or update annotation to target type
        ClassOrInterfaceTypeDetailsBuilder mutableTypeDetailsBuilder = new ClassOrInterfaceTypeDetailsBuilder(
                mutableTypeDetails);
        if (isAlreadyAnnotated) {
            mutableTypeDetailsBuilder.updateTypeAnnotation(annotationBuilder.build(),
                    new HashSet<JavaSymbolName>());
        } else {
            mutableTypeDetailsBuilder.addAnnotation(annotationBuilder.build());
        }
        typeManagementService.createOrUpdateTypeOnDisk(mutableTypeDetailsBuilder.build());

        annotateFormBackingObjectRelationsControllers(mutableTypeDetails, annotationValues);

        return true;
    }

    /**
     * For a given controller, this method inspect the OneToMany and ManyToMany
     * fields in its formBackingObjet and, based on GvNIXRelationsPattern
     * annotationValues, annotates the controllers exposing these entities with
     * the needed GvNIXPattern annotation
     * 
     * @param controllerDetails
     * @param relationsPatternValues
     */
    private void annotateFormBackingObjectRelationsControllers(ClassOrInterfaceTypeDetails controllerDetails,
            AnnotationAttributeValue<?> relationsPatternValues) {

        JavaType formBakingObjectType = getFormBakingObject(controllerDetails);

        // Retrieve metadata for the Java source type the annotation is being
        // added to
        String formBackingTypeId = JpaActiveRecordMetadata.createIdentifier(formBakingObjectType,
                LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""));
        JpaActiveRecordMetadata formBackingTypeMetadata = (JpaActiveRecordMetadata) metadataService
                .evictAndGet(formBackingTypeId);
        if (formBackingTypeMetadata == null) {
            throw new IllegalArgumentException(
                    "Cannot locate Metadata for '" + formBakingObjectType.getFullyQualifiedTypeName() + "'");
        }

        List<FieldMetadata> toManyFields = patternService.getEntityToManyFields(formBakingObjectType);
        Map<String, String> fieldsPatternIdAndType = patternService
                .getRelationsPatternFieldAndRelatedPattern(relationsPatternValues);
        if (!fieldsPatternIdAndType.keySet().isEmpty()) {
            for (FieldMetadata field : toManyFields) {
                if (fieldsPatternIdAndType.keySet().contains(field.getFieldName().getSymbolName())
                        && field.getFieldType().isCommonCollectionType()) {
                    AnnotationMetadata relatedPattern = getGvNIXRelatedPattern(
                            fieldsPatternIdAndType.get(field.getFieldName().getSymbolName()));
                    if (relatedPattern != null) {
                        if (!annotateEntityController(field.getFieldType().getParameters().get(0),
                                relatedPattern)) {
                            // Related entity has not a controller: Alert
                            // message and rollback
                            throw new IllegalArgumentException(
                                    "Cannot add a detail pattern into a field whose entity type has not a controller."
                                            + " Please, run command 'web mvc scaffold' for field entity type first.");
                        }
                    }
                }
            }
        }

    }

    /**
     * Given a Entity this method look for the WebScaffold exposing it and adds
     * {@link GvNIXRelatedPattern} in parameter annotation
     * <p>
     * The entity controller can't be annotated if entity has not a controller.
     * Then this method return false.
     * </p>
     * 
     * @param entity
     * @param annotation
     * @return Entity controller annotated ?
     */
    private boolean annotateEntityController(JavaType entity, AnnotationMetadata annotation) {

        // If entity has not a controller, 'success' will be false
        boolean success = false;

        AnnotationMetadata annotationMetadata = null;
        for (ClassOrInterfaceTypeDetails cid : typeLocationService
                .findClassesOrInterfaceDetailsWithAnnotation(ROOWEBSCAFFOLD_ANNOTATION)) {
            annotationMetadata = MemberFindingUtils.getAnnotationOfType(cid.getAnnotations(),
                    ROOWEBSCAFFOLD_ANNOTATION);
            if (annotationMetadata != null) {
                AnnotationAttributeValue<?> annotationValues = annotationMetadata
                        .getAttribute(new JavaSymbolName(FORM_BACKING_OBJ));
                if (annotationValues != null) {
                    if (annotationValues.getName().compareTo(new JavaSymbolName(FORM_BACKING_OBJ)) == 0
                            && ((JavaType) annotationValues.getValue()).getFullyQualifiedTypeName()
                                    .equalsIgnoreCase(entity.getFullyQualifiedTypeName())) {
                        addOrUpdateGvNIXRelatedPatternToController(cid.getName(), annotation);
                        success = true;
                    }
                }
            }
        }
        return success;
    }

    /**
     * Annotates with GvNIXRelatedPattern (or updates the annotation value) the
     * given controller
     * 
     * @param controllerClass
     * @param annotation Instace of GvNIXPattern annotation metadata
     */
    private void addOrUpdateGvNIXRelatedPatternToController(JavaType controllerClass,
            AnnotationMetadata annotation) {

        ClassOrInterfaceTypeDetails controllerDetails = patternService.getControllerTypeDetails(controllerClass);

        // Test if has the @RooWebScaffold
        Validate.notNull(
                MemberFindingUtils.getAnnotationOfType(controllerDetails.getAnnotations(),
                        ROOWEBSCAFFOLD_ANNOTATION),
                controllerClass.getSimpleTypeName().concat(" has not @RooWebScaffold annotation"));

        // All checks passed OK.
        // We don't need to do Setup because it must be done by addPattern call

        // Test if the annotation already exists on the target type
        AnnotationMetadata annotationMetadata = MemberFindingUtils
                .getAnnotationOfType(controllerDetails.getAnnotations(), RELATED_PATTERN_ANNOTATION);

        // List of pattern to use
        List<StringAttributeValue> patternList = new ArrayList<StringAttributeValue>();

        boolean isAlreadyAnnotated = false;
        if (annotationMetadata != null) {
            // @GvNIXRelatedPattern alredy exists

            // Loads previously registered pattern into patterList
            // Also checks if name is used previously
            AnnotationAttributeValue<?> previousAnnotationValues = annotationMetadata
                    .getAttribute(RELTD_PAT_ANN_ATTR_VAL_NAME);

            if (previousAnnotationValues != null) {

                @SuppressWarnings("unchecked")
                List<StringAttributeValue> previousValues = (List<StringAttributeValue>) previousAnnotationValues
                        .getValue();
                patternList.addAll(previousValues);
            }
            isAlreadyAnnotated = true;
        }

        // We're sure that annotation has set a value since we created this
        // instance
        @SuppressWarnings("unchecked")
        List<StringAttributeValue> newValues = (List<StringAttributeValue>) annotation
                .getAttribute(RELTD_PAT_ANN_ATTR_VAL_NAME).getValue();
        // Check if the same annotation value is already defined avoiding define
        // it twice
        for (StringAttributeValue stringAttributeValue : newValues) {
            if (!patternList.contains(stringAttributeValue)) {
                patternList.add(stringAttributeValue);
            }
        }

        AnnotationAttributeValue<?> gvNIXRelatedPatternValue = new ArrayAttributeValue<StringAttributeValue>(
                RELTD_PAT_ANN_ATTR_VAL_NAME, patternList);

        // TODO: next check must be over if pattern id is already defined in
        // project
        // Assert.isTrue(
        // arePatternsDefinedOnceInController(gvNIXRelatedPatternValue),
        // "Controller ".concat(
        // controllerClass.getFullyQualifiedTypeName()).concat(
        // " has the same pattern defined more than once"));

        // Prepare annotation builder
        AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder(RELATED_PATTERN_ANNOTATION);
        annotationBuilder.addAttribute(gvNIXRelatedPatternValue);

        // Add or update annotation to target type
        ClassOrInterfaceTypeDetailsBuilder mutableTypeDetailsBuilder = new ClassOrInterfaceTypeDetailsBuilder(
                controllerDetails);
        if (isAlreadyAnnotated) {
            mutableTypeDetailsBuilder.updateTypeAnnotation(annotationBuilder.build(),
                    new HashSet<JavaSymbolName>());
        } else {
            mutableTypeDetailsBuilder.addAnnotation(annotationBuilder.build());
        }
        typeManagementService.createOrUpdateTypeOnDisk(mutableTypeDetailsBuilder.build());

        if (patternService.existsRelatedPatternType(WebPatternType.tabular.name(), patternList) || patternService
                .existsRelatedPatternType(WebPatternType.tabular_edit_register.name(), patternList)) {
            annotateTypeWithGvNIXEntityBatch(getFormBakingObject(controllerDetails));
        }
    }

    /**
     * Annotates with GvNIXEntityBatch given JavaType
     * 
     * @param type
     */
    private void annotateTypeWithGvNIXEntityBatch(JavaType type) {

        ClassOrInterfaceTypeDetails typeMutableDetails = typeLocationService.getTypeDetails(type);
        AnnotationMetadata annotationMetadata = MemberFindingUtils
                .getAnnotationOfType(typeMutableDetails.getAnnotations(), ENTITYBATCH_ANNOTATION);

        // Annotate type with GvNIXEntityBatch just if is not
        // annotated already. We don't need to update attributes
        if (annotationMetadata == null) {
            // Prepare annotation builder
            AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder(ENTITYBATCH_ANNOTATION);
            ClassOrInterfaceTypeDetailsBuilder mutableTypeDetailsBuilder = new ClassOrInterfaceTypeDetailsBuilder(
                    typeMutableDetails);
            mutableTypeDetailsBuilder.addAnnotation(annotationBuilder.build());
            typeManagementService.createOrUpdateTypeOnDisk(mutableTypeDetailsBuilder.build());
        }
    }

    /**
     * For given attrValue, returns an instance of GvNIXRelatedPattern
     * annotation <br/>
     * GvNIXRelatedPattern({"pattern_id1=table"})
     * 
     * @param attrValue
     * @return
     */
    private AnnotationMetadata getGvNIXRelatedPattern(String attrValue) {
        List<AnnotationAttributeValue<?>> attributes = new ArrayList<AnnotationAttributeValue<?>>();

        List<StringAttributeValue> patternList = new ArrayList<StringAttributeValue>(1);

        patternList.add(new StringAttributeValue(new JavaSymbolName(ARRAY_ELEM), attrValue));

        attributes.add(new ArrayAttributeValue<StringAttributeValue>(RELTD_PAT_ANN_ATTR_VAL_NAME, patternList));

        AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder(RELATED_PATTERN_ANNOTATION);
        annotationBuilder.setAttributes(attributes);

        return annotationBuilder.build();
    }

    /**
     * Returns formBackingObject JavaType from the {@link RooWebScaffold}
     * attribute
     * 
     * @param controllerDetails
     * @return
     */
    private JavaType getFormBakingObject(ClassOrInterfaceTypeDetails controllerDetails) {
        AnnotationMetadata rooWSacffAnnMdata = MemberFindingUtils
                .getAnnotationOfType(controllerDetails.getAnnotations(), ROOWEBSCAFFOLD_ANNOTATION);

        AnnotationAttributeValue<?> formbakingObjectAttValue = rooWSacffAnnMdata
                .getAttribute(new JavaSymbolName(FORM_BACKING_OBJ));

        JavaType formBakingObjectType = (JavaType) formbakingObjectAttValue.getValue();
        Validate.notNull(formBakingObjectType, "formBakingObject attribute of RooWebScaffold in "
                + controllerDetails.getName().getSimpleTypeName() + " must be set");

        return formBakingObjectType;
    }

    private boolean existsFieldPatternDeclaration(String definition, JavaSymbolName field) {
        char[] fieldName = field.getSymbolName().toCharArray();
        StringBuilder sb = new StringBuilder((fieldName.length * 3) + 15);
        sb.append("[,: ]");
        for (char ch : fieldName) {
            sb.append('[');
            sb.append(ch);
            sb.append(']');
        }
        sb.append("[ ]*[=]");
        Pattern pattern = Pattern.compile(sb.toString());
        return pattern.matcher(definition).find();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.gvnix.web.screen.roo.addon.WebScreenOperations#setup()
     */
    @Override
    public void setup() {
        configService.setup();
        installPatternArtifacts(false);
        modifyLoadScriptsTagx();
        addMessageBoxInLayout();
    }

    /**
     * Updates load-scripts.tagx adding in the right position some elements:
     * <ul>
     * <li><code>spring:url</code> elements for JS and CSS</li>
     * <li><code>link</code> element for CSS</li>
     * <li><code>script</code> element for JS</li>
     * </ul>
     */
    private void modifyLoadScriptsTagx() {
        PathResolver pathResolver = projectOperations.getPathResolver();
        String loadScriptsTagx = pathResolver.getIdentifier(LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""),
                "WEB-INF/tags/util/load-scripts.tagx");

        if (!fileManager.exists(loadScriptsTagx)) {
            // load-scripts.tagx doesn't exist, so nothing to do
            return;
        }

        InputStream loadScriptsIs = fileManager.getInputStream(loadScriptsTagx);

        Document loadScriptsXml;
        try {
            loadScriptsXml = XmlUtils.getDocumentBuilder().parse(loadScriptsIs);
        } catch (Exception ex) {
            throw new IllegalStateException("Could not open load-scripts.tagx file", ex);
        }

        Element lsRoot = loadScriptsXml.getDocumentElement();

        Node nextSibiling;

        // spring:url elements
        Element testElement = XmlUtils.findFirstElement("/root/url[@var='pattern_css_url']", lsRoot);
        if (testElement == null) {
            Element urlPatternCss = new XmlElementBuilder("spring:url", loadScriptsXml)
                    .addAttribute(VALUE, "/resources/styles/pattern.css").addAttribute(VAR, "pattern_css_url")
                    .build();
            Element urlQlJs = new XmlElementBuilder("spring:url", loadScriptsXml)
                    .addAttribute(VALUE, "/resources/scripts/quicklinks.js").addAttribute(VAR, "qljs_url").build();
            // Add i18n messages for quicklinks.js
            List<Element> qlJsI18n = new ArrayList<Element>();
            qlJsI18n.add(new XmlElementBuilder("spring:message", loadScriptsXml)
                    .addAttribute("code", "message_selectrowtodelete_alert")
                    .addAttribute(VAR, "msg_selectrowtodelete_alert").addAttribute("htmlEscape", "false").build());
            qlJsI18n.add(new XmlElementBuilder("spring:message", loadScriptsXml)
                    .addAttribute("code", "message_selectrowtoupdate_alert")
                    .addAttribute(VAR, "msg_selectrowtoupdate_alert").addAttribute("htmlEscape", "false").build());
            qlJsI18n.add(new XmlElementBuilder("spring:message", loadScriptsXml)
                    .addAttribute("code", "message_updateonlyonerow_alert")
                    .addAttribute(VAR, "msg_updateonlyonerow_alert").addAttribute("htmlEscape", "false").build());
            StringBuilder qlJsI18nScriptText = new StringBuilder("<!--\n");
            qlJsI18nScriptText.append("var GVNIX_MSG_SELECT_ROW_TO_DELETE=\"${msg_selectrowtodelete_alert}\";\n");
            qlJsI18nScriptText.append("var GVNIX_MSG_SELECT_ROW_TO_UPDATE=\"${msg_selectrowtoupdate_alert}\";\n");
            qlJsI18nScriptText.append("var GVNIX_MSG_UPDATE_ONLY_ONE_ROW=\"${msg_updateonlyonerow_alert}\";\n");
            qlJsI18nScriptText.append("-->\n");
            Element qlJsI18nScript = new XmlElementBuilder("script", loadScriptsXml)
                    .setText(qlJsI18nScriptText.toString()).build();
            List<Element> springUrlElements = XmlUtils.findElements("/root/url", lsRoot);
            // Element lastSpringUrl = null;
            if (!springUrlElements.isEmpty()) {
                Element lastSpringUrl = springUrlElements.get(springUrlElements.size() - 1);
                if (lastSpringUrl != null) {
                    nextSibiling = lastSpringUrl.getNextSibling().getNextSibling();
                    lsRoot.insertBefore(urlPatternCss, nextSibiling);
                    lsRoot.insertBefore(urlQlJs, nextSibiling);
                    lsRoot.insertBefore(qlJsI18nScript, nextSibiling);
                    for (Element item : qlJsI18n) {
                        lsRoot.insertBefore(item, qlJsI18nScript);
                    }
                }
            } else {
                // Add at the end of the document
                lsRoot.appendChild(urlPatternCss);
                lsRoot.appendChild(urlQlJs);
                for (Element item : qlJsI18n) {
                    lsRoot.appendChild(item);
                }
                lsRoot.appendChild(qlJsI18nScript);
            }
        }

        // pattern.css stylesheet element
        testElement = XmlUtils.findFirstElement("/root/link[@href='${pattern_css_url}']", lsRoot);
        if (testElement == null) {
            Element linkPatternCss = new XmlElementBuilder("link", loadScriptsXml).addAttribute("rel", "stylesheet")
                    .addAttribute("type", "text/css").addAttribute("media", "screen")
                    .addAttribute("href", "${pattern_css_url}").build();
            linkPatternCss.appendChild(loadScriptsXml.createComment(" required for FF3 and Opera "));
            Node linkTrundraCssNode = XmlUtils.findFirstElement("/root/link[@href='${tundra_url}']", lsRoot);
            if (linkTrundraCssNode != null) {
                nextSibiling = linkTrundraCssNode.getNextSibling().getNextSibling();
                lsRoot.insertBefore(linkPatternCss, nextSibiling);
            } else {
                // Add ass last link element
                // Element lastLink = null;
                List<Element> linkElements = XmlUtils.findElements("/root/link", lsRoot);
                if (!linkElements.isEmpty()) {
                    Element lastLink = linkElements.get(linkElements.size() - 1);
                    if (lastLink != null) {
                        nextSibiling = lastLink.getNextSibling().getNextSibling();
                        lsRoot.insertBefore(linkPatternCss, nextSibiling);
                    }
                } else {
                    // Add at the end of document
                    lsRoot.appendChild(linkPatternCss);
                }
            }
        }

        // quicklinks.js script element
        testElement = XmlUtils.findFirstElement("/root/script[@src='${qljs_url}']", lsRoot);
        if (testElement == null) {
            Element scriptQlJs = new XmlElementBuilder("script", loadScriptsXml).addAttribute("src", "${qljs_url}")
                    .addAttribute("type", "text/javascript").build();
            scriptQlJs.appendChild(loadScriptsXml.createComment(" required for FF3 and Opera "));
            List<Element> scrtiptElements = XmlUtils.findElements("/root/script", lsRoot);
            // Element lastScript = null;
            if (!scrtiptElements.isEmpty()) {
                Element lastScript = scrtiptElements.get(scrtiptElements.size() - 1);
                if (lastScript != null) {
                    nextSibiling = lastScript.getNextSibling().getNextSibling();
                    lsRoot.insertBefore(scriptQlJs, nextSibiling);
                }
            } else {
                // Add at the end of document
                lsRoot.appendChild(scriptQlJs);
            }
        }

        writeToDiskIfNecessary(loadScriptsTagx, loadScriptsXml.getDocumentElement());

    }

    /**
     * Decides if write to disk is needed (ie updated or created)<br/>
     * Used for TAGx files
     * 
     * @param filePath
     * @param body
     * @return
     */
    private boolean writeToDiskIfNecessary(String filePath, Element body) {
        // Build a string representation of the JSP
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Transformer transformer = XmlUtils.createIndentingTransformer();
        XmlUtils.writeXml(transformer, byteArrayOutputStream, body.getOwnerDocument());
        String viewContent = byteArrayOutputStream.toString();

        // If mutableFile becomes non-null, it means we need to use it to write
        // out the contents of jspContent to the file
        MutableFile mutableFile = null;
        if (fileManager.exists(filePath)) {
            // First verify if the file has even changed
            File newFile = new File(filePath);
            String existing = null;
            try {
                existing = IOUtils.toString(new FileReader(newFile));
            } catch (IOException ignoreAndJustOverwriteIt) {
                LOGGER.finest("Problems writing ".concat(newFile.getAbsolutePath()));
            }

            if (!viewContent.equals(existing)) {
                mutableFile = fileManager.updateFile(filePath);
            }
        } else {
            mutableFile = fileManager.createFile(filePath);
            Validate.notNull(mutableFile, "Could not create '" + filePath + "'");
        }

        if (mutableFile != null) {
            try {
                // We need to write the file out (it's a new file, or the
                // existing file has different contents)
                InputStream inputStream = null;
                OutputStreamWriter outputStream = null;
                try {
                    inputStream = IOUtils.toInputStream(viewContent);
                    outputStream = new OutputStreamWriter(mutableFile.getOutputStream());
                    IOUtils.copy(inputStream, outputStream);
                } finally {
                    IOUtils.closeQuietly(inputStream);
                    IOUtils.closeQuietly(outputStream);
                }
                // Return and indicate we wrote out the file
                return true;
            } catch (IOException ioe) {
                throw new IllegalStateException("Could not output '" + mutableFile.getCanonicalPath() + "'", ioe);
            }
        }

        // A file existed, but it contained the same content, so we return false
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.gvnix.web.screen.roo.addon.WebScreenOperations#updatePattern()
     */
    public void updatePattern() {
        installPatternArtifacts(true);
    }

    /**
     * Installs or updates pattern Artifacts.
     * <ul>
     * <li>images</li>
     * <li>scripts JS</li>
     * <li>CSS</li>
     * <li>TAGx</li>
     * <li>i18n language properties</li>
     * <ul>
     * 
     * @param forceUpdate
     */
    private void installPatternArtifacts(boolean forceUpdate) {
        PathResolver pathResolver = projectOperations.getPathResolver();
        // install pattern images
        // Don't use copyDirectoryContents method with binary files and
        // 'replace' = true: binary files will be corrupted
        if (forceUpdate) {
            OperationUtils.updateDirectoryContents(
                    "images/pattern/*.*", pathResolver
                            .getIdentifier(LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/images/pattern"),
                    fileManager, cContext, getClass());
        } else {
            copyDirectoryContents("images/pattern/*.*", pathResolver
                    .getIdentifier(LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/images/pattern"), false);
        }
        // install js
        copyDirectoryContents("scripts/*.js",
                pathResolver.getIdentifier(LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/scripts"),
                forceUpdate);
        // install css
        copyDirectoryContents("styles/*.css",
                pathResolver.getIdentifier(LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/styles"),
                forceUpdate);

        // copy util to tags/util
        copyDirectoryContents("tags/util/*.tagx",
                pathResolver.getIdentifier(LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/tags/util"),
                forceUpdate);
        // copy dialog/message to tags/dialog/message
        copyDirectoryContents("tags/dialog/message/*.tagx", pathResolver.getIdentifier(
                LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/tags/dialog/message"), forceUpdate);
        // copy pattern to tags/pattern
        copyDirectoryContents("tags/pattern/*.tagx", pathResolver.getIdentifier(
                LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/tags/pattern"), forceUpdate);
        copyDirectoryContents("tags/pattern/form/*.tagx", pathResolver.getIdentifier(
                LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""), "/WEB-INF/tags/pattern/form"), forceUpdate);
        copyDirectoryContents("tags/pattern/form/fields/*.tagx",
                pathResolver.getIdentifier(LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""),
                        "/WEB-INF/tags/pattern/form/fields"),
                forceUpdate);

        addI18nProperties();
    }

    /**
     * Takes properties files (messages_xx.properties) and adds their content to
     * i18n message bundle file in current project
     */
    private void addI18nProperties() {
        // Check if Valencian_Catalan language is supported and add properties
        // if so
        Set<I18n> supportedLanguages = i18nSupport.getSupportedLanguages();
        for (I18n i18n : supportedLanguages) {
            if (i18n.getLocale().equals(new Locale("ca"))) {
                MessageBundleUtils.installI18nMessages(new ValencianCatalanLanguage(), projectOperations,
                        fileManager);
                MessageBundleUtils.addPropertiesToMessageBundle("ca", getClass(), propFileOperations,
                        projectOperations, fileManager);
                break;
            }
        }
        // Add properties to Spanish messageBundle
        MessageBundleUtils.installI18nMessages(new SpanishLanguage(), projectOperations, fileManager);
        MessageBundleUtils.addPropertiesToMessageBundle("es", getClass(), propFileOperations, projectOperations,
                fileManager);

        // Add properties to default messageBundle
        MessageBundleUtils.addPropertiesToMessageBundle("en", getClass(), propFileOperations, projectOperations,
                fileManager);
    }

    /**
     * Adds the element dialog:message-box in the right place in default.jspx
     * layout
     */
    private void addMessageBoxInLayout() {
        PathResolver pathResolver = projectOperations.getPathResolver();
        String defaultJspx = pathResolver.getIdentifier(LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""),
                "WEB-INF/layouts/default.jspx");

        // TODO: Check if it's necessary to add message-box in home-default.jspx
        // layout (when exists)

        if (!fileManager.exists(defaultJspx)) {
            // layouts/default.jspx doesn't exist, so nothing to do
            return;
        }

        InputStream defulatJspxIs = fileManager.getInputStream(defaultJspx);

        Document defaultJspxXml;
        try {
            defaultJspxXml = XmlUtils.getDocumentBuilder().parse(defulatJspxIs);
        } catch (Exception ex) {
            throw new IllegalStateException("Could not open default.jspx file", ex);
        }

        Element lsHtml = defaultJspxXml.getDocumentElement();

        String dialogNsUri = lsHtml.getAttribute("xmlns:dialog");

        if (!dialogNsUri.isEmpty() && dialogNsUri.equals("urn:jsptagdir:/WEB-INF/tags/dialog/modal")) {
            // User has applied modal dialog support, so, don't change anything;
            return;
        }

        // Set dialog tag lib as attribute in html element
        lsHtml.setAttribute("xmlns:dialog", "urn:jsptagdir:/WEB-INF/tags/dialog/message");

        Element messageBoxElement = DomUtils.findFirstElementByName("dialog:message-box", lsHtml);
        if (messageBoxElement == null) {
            Element divMain = XmlUtils.findFirstElement("/html/body/div/div[@id='main']", lsHtml);
            Element insertAttributeBodyElement = XmlUtils
                    .findFirstElement("/html/body/div/div/insertAttribute[@name='body']", lsHtml);
            Element messageBox = new XmlElementBuilder("dialog:message-box", defaultJspxXml).build();
            divMain.insertBefore(messageBox, insertAttributeBodyElement);
        }

        writeToDiskIfNecessary(defaultJspx, defaultJspxXml.getDocumentElement());

    }

    /*
     * (non-Javadoc)
     *
     * @see org.gvnix.web.screen.roo.addon.WebScreenOperations#
     * isControllerEntityActiveRecord(org.springframework.roo.model.JavaType)
     */
    @Override
    public boolean isControllerEntityActiveRecord(JavaType controller) {
        ClassOrInterfaceTypeDetails mutableTypeDetails = patternService.getControllerTypeDetails(controller);
        return isControllerEntityActiveRecord(mutableTypeDetails);
    }

    private boolean isControllerEntityActiveRecord(ClassOrInterfaceTypeDetails controller) {
        JavaType entityType = getFormBakingObject(controller);

        // Retrieve metadata for the Java source type the annotation is being
        // added to
        String entityTypeId = JpaActiveRecordMetadata.createIdentifier(entityType,
                LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""));
        JpaActiveRecordMetadata activeRecordMetadata = (JpaActiveRecordMetadata) metadataService.get(entityTypeId);

        return activeRecordMetadata != null;
    }
}