/*
 * gvNIX is an open source tool for rapid application development (RAD).
 * Copyright (C) 2010 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.support;

import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.web.mvc.controller.addon.scaffold.WebScaffoldMetadata;
import org.springframework.roo.addon.web.mvc.controller.annotations.scaffold.RooWebScaffold;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.process.manager.FileManager;
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.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * Utils for web projects.
 * 
 * @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a
 *         href="http://www.dgti.gva.es">General Directorate for Information
 *         Technologies (DGTI)</a>
 */

@Component
@Service
public class WebProjectUtilsImpl implements WebProjectUtils {

    static final String WEB_INF_VIEWS = "WEB-INF/views/";

    static final String JSPX_EXT = ".jspx";

    static final String VALUE = "value";

    private static final Logger LOGGER = Logger
            .getLogger(WebProjectUtilsImpl.class.getName());

    private OperationUtils operationUtils;

    // ------------ OSGi component attributes ----------------
    private BundleContext context;

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

    /**
     * Check if current project is a Spring MVC one
     * <p/>
     * Search webmvc-config.xml file exists.
     * 
     * @param metadataService Metadata Service component
     * @param fileManager File manager component
     * @param projectOperations Project operations component
     * @return Is a Spring MVC project ?
     */
    public boolean isSpringMvcProject(MetadataService metadataService,
            FileManager fileManager, ProjectOperations projectOperations) {

        PathResolver pathResolver = getOperationUtils().getPathResolver(
                metadataService, projectOperations);
        String webXmlPath = pathResolver.getIdentifier(
                LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""),
                "WEB-INF/spring/webmvc-config.xml");

        return fileManager.exists(webXmlPath);
    }

    /**
     * Check if current project is a web project
     * <p/>
     * Search web.xml file exists.
     * 
     * @param metadataService Metadata Service component
     * @param fileManager File manager component
     * @param projectOperations Project operations component
     * @return Is a web project ?
     */

    public boolean isWebProject(MetadataService metadataService,
            FileManager fileManager, ProjectOperations projectOperations) {

        PathResolver pathResolver = getOperationUtils().getPathResolver(
                metadataService, projectOperations);
        String webXmlPath = pathResolver.getIdentifier(
                LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""),
                "WEB-INF/web.xml");

        return fileManager.exists(webXmlPath);
    }

    /**
     * Installs the Dialog Java class
     * 
     * @param packageFullName fullyQualifiedName of destination package for
     *        Dialog Bean. ie. <code>com.disid.myapp.web.dialog</code>
     * @param pathResolver
     * @param fileManager
     */

    public void installWebDialogClass(String packageFullName,
            PathResolver pathResolver, FileManager fileManager) {

        String classFullName = pathResolver.getIdentifier(
                LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""),
                packageFullName.concat(".Dialog").replace(".", File.separator)
                        .concat(".java"));

        getOperationUtils().installJavaClassFromTemplate(packageFullName,
                classFullName, "Dialog.java-template", pathResolver,
                fileManager);
    }

    /**
     * Create all namespaces occurrences in given {@relativePath} file with
     * namespaces occurrences contained in {@code newUriMap}.
     * 
     * @param controllerPath {@link RooWebScaffold#path()} value
     * @param jspxName view name, for example: "show", "list", "update"
     * @param uriMap where key attribute name (ex "xmlns:page") and the value
     *        the new uri (ex: "urn:jsptagdir:/WEB-INF/tags/datatables")
     * @param projectOperations
     * @param fileManager
     * @param metadataService
     */

    public void addTagxUriInJspx(JavaType controller, String jspxName,
            Map<String, String> uriMap, ProjectOperations projectOperations,
            FileManager fileManager, MetadataService metadataService) {

        WebScaffoldMetadata webScaffoldMetadata = (WebScaffoldMetadata) metadataService
                .get(WebScaffoldMetadata.createIdentifier(controller,
                        getJavaPath(projectOperations)));

        Validate.notNull(webScaffoldMetadata,
                "Can't get RooWebScaffold metada for type: %s",
                controller.getFullyQualifiedTypeName());

        addTagxUriInJspx(webScaffoldMetadata.getAnnotationValues().getPath(),
                jspxName, uriMap, projectOperations, fileManager);
    }

    /**
     * Create all namespaces occurrences in given {@relativePath} file with
     * namespaces occurrences contained in {@code newUriMap}.
     * 
     * @param controllerPath {@link RooWebScaffold#path()} value
     * @param jspxName view name, for example: "show", "list", "update"
     * @param newUriMap Keys are namespace names (ex: "xmlns:page") and values
     *        are the new namespace URI (ex:
     *        "urn:jsptagdir:/WEB-INF/tags/datatables")
     * @param projectOperations
     * @param fileManager
     */

    public void addTagxUriInJspx(String controllerPath, String jspxName,
            Map<String, String> newUriMap, ProjectOperations projectOperations,
            FileManager fileManager) {
        addTagxUriInJspx(WEB_INF_VIEWS.concat(controllerPath).concat("/")
                .concat(jspxName).concat(JSPX_EXT), (Map<String, String>) null,
                newUriMap, projectOperations, fileManager);
    }

    /**
     * Create all namespaces occurrences in given {@relativePath} file with
     * namespaces occurrences contained in {@code newUriMap}.
     * 
     * @param controllerPath {@link RooWebScaffold#path()} value
     * @param jspxName view name, for example: "show", "list", "update"
     * @param oldUriMap (optional) Keys are namespace names (ex: "xmlns:page")
     *        and values are the old namespace URI (ex:
     *        "urn:jsptagdir:/WEB-INF/tags/form") that must match with the
     *        namespace URI in the XML
     * @param newUriMap Keys are namespace names (ex: "xmlns:page") and values
     *        are the new namespace URI (ex:
     *        "urn:jsptagdir:/WEB-INF/tags/datatables")
     * @param projectOperations
     * @param fileManager
     */

    public void addTagxUriInJspx(String controllerPath, String jspxName,
            Map<String, String> oldUriMap, Map<String, String> newUriMap,
            ProjectOperations projectOperations, FileManager fileManager) {
        addTagxUriInJspx(WEB_INF_VIEWS.concat(controllerPath).concat("/")
                .concat(jspxName).concat(JSPX_EXT), oldUriMap, newUriMap,
                projectOperations, fileManager);
    }

    /**
     * Create namespaces in given {@relativePath} file with namespaces
     * occurrences contained in {@code newUriMap}.
     * 
     * @param relativePath XML file to update. The path must be relative to
     *        {@code src/main/webapp} (cannot be null, but may be empty if
     *        referring to the path itself)
     * @param oldUriMap (optional) Keys are namespace names (ex: "xmlns:page")
     *        and values are the old namespace URI (ex:
     *        "urn:jsptagdir:/WEB-INF/tags/form") that must match with the
     *        namespace URI in the XML
     * @param newUriMap Keys are namespace names (ex: "xmlns:page") and values
     *        are the new namespace URI (ex:
     *        "urn:jsptagdir:/WEB-INF/tags/datatables")
     * @param projectOperations
     * @param fileManager
     */

    public void addTagxUriInJspx(String relativePath,
            Map<String, String> oldUriMap, Map<String, String> newUriMap,
            ProjectOperations projectOperations, FileManager fileManager) {

        // Get jspx file path
        PathResolver pathResolver = projectOperations.getPathResolver();
        String docJspx = pathResolver.getIdentifier(
                getWebappPath(projectOperations), relativePath);

        // Parse XML document
        Document docJspXml = loadXmlDocument(docJspx, fileManager);
        if (docJspXml == null) {
            // file not found: do nothing
            return;
        }

        // Get main div
        Element docRoot = docJspXml.getDocumentElement();
        Element divMain = XmlUtils.findFirstElement("/div", docRoot);
        boolean modified = false;

        // Create namespace URIs
        for (Entry<String, String> newUriEntry : newUriMap.entrySet()) {
            String nsName = newUriEntry.getKey();
            String nsUri = newUriEntry.getValue();

            divMain.setAttribute(nsName, nsUri);
            modified = true;
        }

        // If modified, update the jspx file
        if (modified) {
            DomUtils.removeTextNodes(docJspXml);
            fileManager.createOrUpdateTextFileIfRequired(docJspx,
                    XmlUtils.nodeToString(docJspXml), true);
        }
    }

    /**
     * Replaces all namespaces occurrences in given {@relativePath} file with
     * namespaces occurrences contained in {@code newUriMap}.
     * <p/>
     * A namespace in given {@relativePath} file will be replaced by a namespace
     * in {@code newUriMap} if namespace name is in {@relativePath} file
     * <p/>
     * Only tagx namespaces found in uriMap will be updated, uriMap keys that
     * don't match any tagx namespace will be ignored.
     * 
     * @param controllerPath {@link RooWebScaffold#path()} value
     * @param jspxName view name, for example: "show", "list", "update"
     * @param uriMap where key attribute name (ex "xmlns:page") and the value
     *        the new uri (ex: "urn:jsptagdir:/WEB-INF/tags/datatables")
     * @param projectOperations
     * @param fileManager
     * @param metadataService
     */

    public void updateTagxUriInJspx(JavaType controller, String jspxName,
            Map<String, String> uriMap, ProjectOperations projectOperations,
            FileManager fileManager, MetadataService metadataService) {

        WebScaffoldMetadata webScaffoldMetadata = (WebScaffoldMetadata) metadataService
                .get(WebScaffoldMetadata.createIdentifier(controller,
                        getJavaPath(projectOperations)));

        Validate.notNull(webScaffoldMetadata,
                "Can't get RooWebScaffold metada for type: %s",
                controller.getFullyQualifiedTypeName());

        updateTagxUriInJspx(
                webScaffoldMetadata.getAnnotationValues().getPath(), jspxName,
                uriMap, projectOperations, fileManager);
    }

    /**
     * Replaces all namespaces occurrences in given {@relativePath} file with
     * namespaces occurrences contained in {@code newUriMap}.
     * <p/>
     * A namespace in given {@relativePath} file will be replaced by a namespace
     * in {@code newUriMap} if namespace name is in {@relativePath} file
     * <p/>
     * Only tagx namespaces found in uriMap will be updated, uriMap keys that
     * don't match any tagx namespace will be ignored.
     * 
     * @param controllerPath {@link RooWebScaffold#path()} value
     * @param jspxName view name, for example: "show", "list", "update"
     * @param newUriMap Keys are namespace names (ex: "xmlns:page") and values
     *        are the new namespace URI (ex:
     *        "urn:jsptagdir:/WEB-INF/tags/datatables")
     * @param projectOperations
     * @param fileManager
     */

    public void updateTagxUriInJspx(String controllerPath, String jspxName,
            Map<String, String> newUriMap, ProjectOperations projectOperations,
            FileManager fileManager) {
        updateTagxUriInJspx(WEB_INF_VIEWS.concat(controllerPath).concat("/")
                .concat(jspxName).concat(JSPX_EXT), (Map<String, String>) null,
                newUriMap, projectOperations, fileManager);
    }

    /**
     * Replaces all namespaces occurrences in given {@relativePath} file with
     * namespaces occurrences contained in {@code newUriMap}.
     * <p/>
     * A namespace in given {@relativePath} file will be replaced by a namespace
     * in {@code newUriMap} if namespace name is in {@relativePath} file
     * <p/>
     * Only tagx namespaces found in uriMap will be updated, uriMap keys that
     * don't match any tagx namespace will be ignored.
     * 
     * @param controllerPath {@link RooWebScaffold#path()} value
     * @param jspxName view name, for example: "show", "list", "update"
     * @param oldUriMap (optional) Keys are namespace names (ex: "xmlns:page")
     *        and values are the old namespace URI (ex:
     *        "urn:jsptagdir:/WEB-INF/tags/form") that must match with the
     *        namespace URI in the XML
     * @param newUriMap Keys are namespace names (ex: "xmlns:page") and values
     *        are the new namespace URI (ex:
     *        "urn:jsptagdir:/WEB-INF/tags/datatables")
     * @param projectOperations
     * @param fileManager
     */

    public void updateTagxUriInJspx(String controllerPath, String jspxName,
            Map<String, String> oldUriMap, Map<String, String> newUriMap,
            ProjectOperations projectOperations, FileManager fileManager) {
        updateTagxUriInJspx(WEB_INF_VIEWS.concat(controllerPath).concat("/")
                .concat(jspxName).concat(JSPX_EXT), oldUriMap, newUriMap,
                projectOperations, fileManager);
    }

    /**
     * Replaces all namespaces occurrences contained in {@code oldUriMap} in
     * given {@relativePath} file with namespaces occurrences contained in
     * {@code newUriMap}.
     * <p/>
     * A namespace in given {@relativePath} file will be replaced by a namespace
     * in {@code newUriMap} when one of the conditions below is true:
     * <p/>
     * <strong>A.</strong> Namespace name is in {@code oldUriMap}, as is in
     * {@code newUriMap} and as is in {@relativePath} file and old namespace
     * (value in {@code oldUriMap}) match with namespace in jspx.
     * <p/>
     * <strong>B.</strong> Namespace name is not in {@code oldUriMap} and
     * namespace name is in {@relativePath} file
     * 
     * @param relativePath XML file to update. The path must be relative to
     *        {@code src/main/webapp} (cannot be null, but may be empty if
     *        referring to the path itself)
     * @param oldUriMap (optional) Keys are namespace names (ex: "xmlns:page")
     *        and values are the old namespace URI (ex:
     *        "urn:jsptagdir:/WEB-INF/tags/form") that must match with the
     *        namespace URI in the XML
     * @param newUriMap Keys are namespace names (ex: "xmlns:page") and values
     *        are the new namespace URI (ex:
     *        "urn:jsptagdir:/WEB-INF/tags/datatables")
     * @param projectOperations
     * @param fileManager
     */

    public void updateTagxUriInJspx(String relativePath,
            Map<String, String> oldUriMap, Map<String, String> newUriMap,
            ProjectOperations projectOperations, FileManager fileManager) {

        // If null, create default oldUriMap causing jspx will be updated with
        // all URIs in newUriMap
        if (oldUriMap == null) {
            oldUriMap = new HashMap<String, String>();
        }

        // Get jspx file path
        PathResolver pathResolver = projectOperations.getPathResolver();
        String docJspx = pathResolver.getIdentifier(
                getWebappPath(projectOperations), relativePath);

        // Parse XML document
        Document docJspXml = loadXmlDocument(docJspx, fileManager);
        if (docJspXml == null) {
            // file not found: do nothing
            return;
        }

        // Get main div
        Element docRoot = docJspXml.getDocumentElement();
        Element divMain = XmlUtils.findFirstElement("/div", docRoot);
        boolean modified = false;

        // Update namespace URIs
        for (Entry<String, String> newUriEntry : newUriMap.entrySet()) {
            String nsName = newUriEntry.getKey();
            String nsUri = newUriEntry.getValue();

            // Namespace name is in oldUriMap, as is in and as is in given file
            // and old namespace (value in oldUriMap) match with namespace in
            // jspx
            if (oldUriMap.containsKey(nsName) && divMain.hasAttribute(nsName)) {
                String oldNsUri = oldUriMap.get(nsName);
                String currentUri = divMain.getAttribute(nsName);

                if (StringUtils.isEmpty(oldNsUri)
                        || oldNsUri.equalsIgnoreCase(currentUri)) {
                    // Compares new value with current before change
                    if (!StringUtils.equalsIgnoreCase(currentUri, nsUri)) {
                        divMain.setAttribute(nsName, nsUri);
                        modified = true;
                    }
                }
            }

            // Namespace name is not in oldUriMap and namespace name is in
            // given file
            if (!oldUriMap.containsKey(nsName) && divMain.hasAttribute(nsName)) {
                // Compares new value with current before change
                if (!StringUtils.equalsIgnoreCase(divMain.getAttribute(nsName),
                        nsUri)) {
                    divMain.setAttribute(nsName, nsUri);
                    modified = true;
                }
            }
        }

        // If modified, update the jspx file
        if (modified) {
            DomUtils.removeTextNodes(docJspXml);
            fileManager.createOrUpdateTextFileIfRequired(docJspx,
                    XmlUtils.nodeToString(docJspXml), true);
        }
    }

    /**
     * Load a XML {@link Document} from its file identifier
     * 
     * @param docFileIdentifier
     * @return document or null if file not found
     */

    public Document loadXmlDocument(String docFileIdentifier,
            FileManager fileManager) {

        Validate.notNull(fileManager, "FileManager cannot be null");
        if (!fileManager.exists(docFileIdentifier)) {
            // document doesn't exist, so nothing to do
            return null;
        }

        // Parse document
        Document docXml;
        InputStream docIs = null;
        try {
            docIs = fileManager.getInputStream(docFileIdentifier);

            try {
                docXml = XmlUtils.getDocumentBuilder().parse(docIs);
            }
            catch (Exception ex) {
                throw new IllegalStateException(
                        "Could not open ".concat(docFileIdentifier), ex);
            }
        }
        finally {
            IOUtils.closeQuietly(docIs);
        }
        return docXml;
    }

    /**
     * Gets the {@code src/main/webapp} logicalPath
     * 
     * @param projectOperations
     * @return
     */

    public LogicalPath getWebappPath(ProjectOperations projectOperations) {
        return LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP,
                projectOperations.getFocusedModuleName());
    }

    /**
     * Gets the src/main/java logicalPath
     * 
     * @param projectOperations
     * @return
     */

    public LogicalPath getJavaPath(ProjectOperations projectOperations) {
        return LogicalPath.getInstance(Path.SRC_MAIN_JAVA,
                projectOperations.getFocusedModuleName());
    }

    /**
     * Append a css definition in loadScript.tagx
     * <p/>
     * This first append a "spring:url" (if not exists) and then add the "link"
     * tag (if not exists)
     * 
     * @param docTagx loadScript.tagx document
     * @param root root node
     * @param varName name of variable to hold css url
     * @param location css location
     * @return document has changed
     */

    public boolean addCssToTag(Document docTagx, Element root, String varName,
            String location) {
        boolean modified = false;

        // add url resolution
        modified = addUrlToTag(docTagx, root, varName, location);

        // Add link
        Element cssElement = XmlUtils.findFirstElement(
                String.format("link[@href='${%s}']", varName), root);
        if (cssElement == null) {
            cssElement = docTagx.createElement("link");
            cssElement.setAttribute("rel", "stylesheet");
            cssElement.setAttribute("type", "text/css");
            cssElement.setAttribute("media", "screen");
            cssElement.setAttribute("href", "${".concat(varName).concat("}"));
            root.appendChild(cssElement);
            modified = true;
        }
        return modified;
    }

    /**
     * Append a css definition in loadScript.tagx
     * <p/>
     * This first append a "spring:url" (if not exists) and then add the "link"
     * tag (if not exists)
     * 
     * @param docTagx loadScript.tagx document
     * @param root root node
     * @param varName name of variable to hold css url
     * @param location css location
     * @return document has changed
     */

    public boolean updateCssToTag(Document docTagx, Element root,
            String varName, String location) {
        boolean modified = false;

        // add url resolution
        modified = updateUrlToTag(docTagx, root, varName, location);

        // Add link
        Element cssElement = XmlUtils.findFirstElement(
                String.format("link[@href='${%s}']", varName), root);
        if (cssElement == null) {
            cssElement = docTagx.createElement("link");
            cssElement.setAttribute("rel", "stylesheet");
            cssElement.setAttribute("type", "text/css");
            cssElement.setAttribute("media", "screen");
            cssElement.setAttribute("href", "${".concat(varName).concat("}"));
            root.appendChild(cssElement);
            modified = true;
        }
        return modified;
    }

    /**
     * Append a '<spring:url var="varName" value='location'/>' tag inside root
     * element if not exist.
     * <p/>
     * First try to locate element using expresion "url[@var='varName']. If not
     * found append this tag.
     * <p/>
     * 
     * @param docTagx {@code .tagx} file document
     * @param root XML root node
     * @param varName name of variable to hold url
     * @param location URL
     * @return if document has changed
     */

    public boolean addUrlToTag(Document docTagx, Element root, String varName,
            String location) {
        Element urlElement = XmlUtils.findFirstElement(
                "url[@var='".concat(varName) + "']", root);
        if (urlElement == null) {
            urlElement = docTagx.createElement("spring:url");
            urlElement.setAttribute("var", varName);
            urlElement.setAttribute(VALUE, location);
            root.appendChild(urlElement);
            return true;
        }
        return false;
    }

    /**
     * Append a '<spring:url var="varName" value='location'/>' tag inside root
     * element if not exist.
     * <p/>
     * First try to locate element using expresion "url[@var='varName']. If not
     * found append this tag.
     * <p/>
     * 
     * @param docTagx {@code .tagx} file document
     * @param root XML root node
     * @param varName name of variable to hold url
     * @param location URL
     * @return if document has changed
     */

    public boolean updateUrlToTag(Document docTagx, Element root,
            String varName, String location) {
        Element urlElement = XmlUtils.findFirstElement(
                "url[@var='".concat(varName) + "']", root);
        if (urlElement != null) {
            urlElement.setAttribute("var", varName);
            urlElement.setAttribute(VALUE, location);
            return true;
        }
        return false;
    }

    /**
     * Append a js definition in loadScript.tagx
     * <p/>
     * This first append a "spring:url" (if not exists) and then add the "link"
     * tag (if not exists)
     * 
     * @param docTagx {@code .tagx} file document
     * @param root XML root node
     * @param varName name of variable to hold js url
     * @param location js location
     * @return document has changed
     */

    public boolean addJSToTag(Document docTagx, Element root, String varName,
            String location) {
        boolean modified = false;

        // add url resolution
        modified = addUrlToTag(docTagx, root, varName, location);

        // Add script
        Element scriptElement = XmlUtils.findFirstElement(
                String.format("script[@src='${%s}']", varName), root);
        if (scriptElement == null) {
            scriptElement = docTagx.createElement("script");
            scriptElement.setAttribute("src", "${".concat(varName).concat("}"));
            scriptElement.setAttribute("type", "text/javascript");
            scriptElement.appendChild(docTagx
                    .createComment("required for FF3 and Opera"));
            root.appendChild(scriptElement);
            modified = true;
        }
        return modified;
    }

    /**
     * Append a js definition in loadScript.tagx
     * <p/>
     * This first append a "spring:url" (if not exists) and then add the "link"
     * tag (if not exists)
     * 
     * @param docTagx {@code .tagx} file document
     * @param root XML root node
     * @param varName name of variable to hold js url
     * @param location js location
     * @return document has changed
     */

    public boolean updateJSToTag(Document docTagx, Element root,
            String varName, String location) {
        boolean modified = false;

        // add url resolution
        modified = updateUrlToTag(docTagx, root, varName, location);

        // Add script
        Element scriptElement = XmlUtils.findFirstElement(
                String.format("script[@src='${%s}']", varName), root);
        if (scriptElement == null) {
            scriptElement = docTagx.createElement("script");
            scriptElement.setAttribute("src", "${".concat(varName).concat("}"));
            scriptElement.setAttribute("type", "text/javascript");
            scriptElement.appendChild(docTagx
                    .createComment("required for FF3 and Opera"));
            root.appendChild(scriptElement);
            modified = true;
        }
        return modified;
    }

    /**
     * Add css and javaScript definition to load-scripts.tagx (if not found)
     * 
     * @param cssList pairs of variable name and location
     * @param jsList pairs of variable name and location
     * @param projectOperations
     * @param fileManager
     */

    public void addJsAndCssToLoadScriptsTag(List<Pair<String, String>> cssList,
            List<Pair<String, String>> jsList,
            ProjectOperations projectOperations, FileManager fileManager) {

        // Parse load-script.tagx
        PathResolver pathResolver = projectOperations.getPathResolver();
        String docTagxPath = pathResolver.getIdentifier(
                getWebappPath(projectOperations),
                "WEB-INF/tags/util/load-scripts.tagx");
        Validate.isTrue(fileManager.exists(docTagxPath),
                "load-script.tagx not found: ".concat(docTagxPath));

        MutableFile docTagxMutableFile = null;
        Document docTagx;

        try {
            docTagxMutableFile = fileManager.updateFile(docTagxPath);
            docTagx = XmlUtils.getDocumentBuilder().parse(
                    docTagxMutableFile.getInputStream());
        }
        catch (Exception e) {
            throw new IllegalStateException(e);
        }
        Element root = docTagx.getDocumentElement();

        boolean modified = false;

        // Add css
        for (Pair<String, String> css : cssList) {
            modified = addCssToTag(docTagx, root, css.getLeft(), css.getRight())
                    || modified;
        }

        // Add js
        for (Pair<String, String> js : jsList) {
            modified = addJSToTag(docTagx, root, js.getLeft(), js.getRight())
                    || modified;
        }

        if (modified) {
            XmlUtils.writeXml(docTagxMutableFile.getOutputStream(), docTagx);
        }
    }

    /**
     * Add variable to contain request Locale in string format.
     * <p/>
     * 
     * <pre>
     * {@code
     * <c:set var="VAR_NAME">
     *   <!-- Get the user local from the page context (it was set by
     *        Spring MVC's locale resolver) -->
     *   <c:set var="jqlocale">${pageContext.response.locale}</c:set>
     *   <c:if test="${fn:length(jqlocale) eq 2}">
     *     <c:out value="${jqlocale}" />
     *   </c:if>
     *   <c:if test="${fn:length(jqlocale) gt 2}">
     *     <c:out value="${fn:substringBefore(jqlocale, '_')}" default="en" />
     *   </c:if>
     *   <c:if test="${fn:length(jqlocale) lt 2}">
     *     <c:out value="en" />
     *   </c:if>
     * </c:set>
     * }
     * </pre>
     * 
     * @param docTagx {@code .tagx} file document
     * @param root XML root node
     * @param varName name of variable to create, see {@code VAR_NAME} in
     *        example above
     */
    public boolean addLocaleVarToTag(Document docTagx, Element root,
            String varName) {

        // Add locale var
        Element varElement = XmlUtils.findFirstElement(
                String.format("c:set[@var='${%s}']", varName), root);
        if (varElement == null) {
            varElement = docTagx.createElement("c:set");
            varElement.setAttribute("var", varName);
            varElement
                    .appendChild(docTagx
                            .createComment(" Get the user local from the page context (it was set by Spring MVC's locale resolver) "));

            Element pElement = docTagx.createElement("c:set");
            pElement.setAttribute("var", "jqlocale");
            pElement.appendChild(docTagx
                    .createTextNode("${pageContext.response.locale}"));
            varElement.appendChild(pElement);

            Element ifElement = docTagx.createElement("c:if");
            ifElement.setAttribute("test", "${fn:length(jqlocale) eq 2}");

            Element outElement = docTagx.createElement("c:out");
            outElement.setAttribute(VALUE, "${jqlocale}");
            ifElement.appendChild(outElement);
            varElement.appendChild(ifElement);

            ifElement = docTagx.createElement("c:if");
            ifElement.setAttribute("test", "${fn:length(jqlocale) gt 2}");

            outElement = docTagx.createElement("c:out");
            outElement.setAttribute(VALUE,
                    "${fn:substringBefore(jqlocale, '_')}");
            outElement.setAttribute("default", "en");
            ifElement.appendChild(outElement);
            varElement.appendChild(ifElement);

            ifElement = docTagx.createElement("c:if");
            ifElement.setAttribute("test", "${fn:length(jqlocale) lt 2}");

            outElement = docTagx.createElement("c:out");
            outElement.setAttribute(VALUE, "en");
            ifElement.appendChild(outElement);
            varElement.appendChild(ifElement);

            root.appendChild(varElement);

            return true;
        }
        return false;
    }

    public OperationUtils getOperationUtils() {
        if (operationUtils == null) {
            // Get all Services implement OperationUtils interface
            try {
                ServiceReference<?>[] references = context
                        .getAllServiceReferences(
                                OperationUtils.class.getName(), null);

                for (ServiceReference<?> ref : references) {
                    operationUtils = (OperationUtils) context.getService(ref);
                    return operationUtils;
                }

                return null;

            }
            catch (InvalidSyntaxException e) {
                LOGGER.warning("Cannot load OperationUtils on WebProjectUtilsImpl.");
                return null;
            }
        }
        else {
            return operationUtils;
        }

    }
}