lt.velykis.maven.skins.reflow.SkinConfigTool.java Source code

Java tutorial

Introduction

Here is the source code for lt.velykis.maven.skins.reflow.SkinConfigTool.java

Source

/* 
 * Copyright 2012 Andrius Velykis
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package lt.velykis.maven.skins.reflow;

import lt.velykis.maven.skins.reflow.HtmlTool;

import org.apache.maven.doxia.site.decoration.DecorationModel;
import org.apache.maven.project.MavenProject;
import org.apache.velocity.tools.ToolContext;
import org.apache.velocity.tools.config.DefaultKey;
import org.apache.velocity.tools.generic.SafeConfig;
import org.apache.velocity.tools.generic.ValueParser;
import org.codehaus.plexus.util.xml.Xpp3Dom;

/**
 * An Apache Velocity tool that simplifies retrieval of custom configuration values for a
 * Maven Site.
 * <p>
 * The tool is configured to access Maven site configuration of a skin inside {@code <custom>}
 * element of site descriptor. It supports global properties (defined at skin level) and per-page
 * properties (defined in {@code <page><mypage>} element). The per-page properties override the
 * global ones.
 * </p>
 * <p>
 * A sample configuration would be like that:
 * </p>
 * 
 * <pre>
 * {@code
 * <custom>
 *   <reflowSkin>
 *     <prop1>value1</prop1>
 *     <prop2>
 *       <prop21>value2</prop21>
 *     </prop2>
 *     <pages>
 *       <mypage project="myproject">
 *         <prop1>override value1</prop1>
 *       </mypage>
 *     </pages>
 *   </reflowSkin>
 * </custom>
 * }
 * </pre>
 * <p>
 * To get the value of {@code prop1}, one would simply use {@code $config.prop1}. This would return
 * "override value1". Then {@code $config.prop2} would return "value2" - the global value.
 * </p>
 * <p>
 * The tool allows querying the value easily, falling back from page to global configuration to
 * {@code null}, if none is available. It also provides convenience accessors for common values.
 * </p>
 * <p>
 * Note
 * </p>
 * 
 * @author Andrius Velykis
 * @since 1.0
 */
@DefaultKey("config")
public class SkinConfigTool extends SafeConfig {

    public static final String DEFAULT_KEY = "config";

    /** By default use Reflow skin configuration tag */
    public static final String SKIN_KEY = "reflowSkin";

    private String key = DEFAULT_KEY;
    private String skinKey = SKIN_KEY;

    /* Create dummy nodes to avoid null checks */
    private Xpp3Dom globalProperties = new Xpp3Dom("");
    private Xpp3Dom pageProperties = new Xpp3Dom("");
    private String namespace = "";

    private String projectId = null;
    private String fileId = null;
    //   private String fileShortId = null;

    /**
     * {@inheritDoc}
     * 
     * @see SafeConfig#configure(ValueParser)
     */
    @Override
    protected void configure(ValueParser values) {
        String altkey = values.getString("key");
        if (altkey != null) {
            setKey(altkey);
        }

        // allow changing skin key in the configuration
        String altSkinKey = values.getString("skinKey");
        if (altSkinKey != null) {
            this.skinKey = altSkinKey;
        }

        // retrieve the decoration model from Velocity context
        Object velocityContext = values.get("velocityContext");

        if (!(velocityContext instanceof ToolContext)) {
            return;
        }

        ToolContext ctxt = (ToolContext) velocityContext;

        Object projectObj = ctxt.get("project");
        if (projectObj instanceof MavenProject) {
            MavenProject project = (MavenProject) projectObj;
            String artifactId = project.getArtifactId();
            // use artifactId "sluggified" as the projectId
            projectId = HtmlTool.slug(artifactId);
        }

        // calculate the page ID from the current file name
        Object currentFileObj = ctxt.get("currentFileName");
        if (currentFileObj instanceof String) {

            String currentFile = (String) currentFileObj;

            // drop the extension
            int lastDot = currentFile.lastIndexOf(".");
            if (lastDot >= 0) {
                currentFile = currentFile.substring(0, lastDot);
            }

            // get the short ID (in case of nested files)
            //         String fileName = new File(currentFile).getName();
            //         fileShortId = HtmlTool.slug(fileName);

            // full file ID includes the nested dirs
            // replace nesting "/" with "-"
            fileId = HtmlTool.slug(currentFile.replace("/", "-").replace("\\", "-"));
        }

        Object decorationObj = ctxt.get("decoration");

        if (!(decorationObj instanceof DecorationModel)) {
            return;
        }

        DecorationModel decoration = (DecorationModel) decorationObj;
        Object customObj = decoration.getCustom();

        if (!(customObj instanceof Xpp3Dom)) {
            return;
        }

        // Now that we have the custom node, get the global properties
        // under the skin tag
        Xpp3Dom customNode = (Xpp3Dom) customObj;
        Xpp3Dom skinNode = customNode.getChild(skinKey);
        String namespaceKey = ":" + skinKey;

        if (skinNode == null) {
            // try searching with any namespace
            for (Xpp3Dom child : customNode.getChildren()) {
                if (child.getName().endsWith(namespaceKey)) {
                    skinNode = child;
                    break;
                }
            }
        }

        if (skinNode != null) {
            globalProperties = skinNode;

            if (skinNode.getName().endsWith(namespaceKey)) {
                // extract the namespace (including the colon)
                namespace = skinNode.getName().substring(0,
                        skinNode.getName().length() - namespaceKey.length() + 1);
            }

            // for page properties, retrieve the file name and drop the `.html`
            // extension - this will be used, i.e. `index` instead of `index.html`
            Xpp3Dom pagesNode = getChild(skinNode, "pages");
            if (pagesNode != null) {

                // Get the page for the file
                // TODO try fileShortId as well?
                Xpp3Dom page = getChild(pagesNode, fileId);

                // Now check if the project artifact ID is set, and if so, if it matches the
                // current project. This allows preventing accidental reuse of parent page
                // configs in children modules
                if (page != null && projectId != null) {
                    String pageProject = page.getAttribute("project");
                    if (pageProject != null && !projectId.equals(pageProject)) {
                        // project ID indicated, and is different - do not use the config
                        page = null;
                    }
                }

                if (page != null) {
                    pageProperties = page;
                }
            }
        }
    }

    /**
     * Retrieves the child node. Tests both default name and with namespace.
     * 
     * @param parentNode
     * @param name
     * @return
     */
    private Xpp3Dom getChild(Xpp3Dom parentNode, String name) {
        Xpp3Dom child = parentNode.getChild(name);
        if (child != null) {
            return child;
        }

        return parentNode.getChild(namespace + name);
    }

    /**
     * Sets the key under which this tool has been configured.
     * 
     * @since 1.0
     */
    protected void setKey(String key) {
        if (key == null) {
            throw new NullPointerException("SkinConfigTool key cannot be null");
        }
        this.key = key;
    }

    /**
     * Should return the key under which this tool has been configured. The default is `config`.
     * 
     * @since 1.0
     */
    public String getKey() {
        return this.key;
    }

    /**
     * Default accessor for config properties. Instead of using {@code $config.get("myproperty")},
     * one can utilise Velocity fallback onto the default getter and use {@code $config.myproperty}.
     * 
     * @param property
     *            the property of interest
     * @return configuration node if found in the following sequence:
     *         <ol>
     *         <li>In page configuration</li>
     *         <li>In global configuration</li>
     *         <li>{@code null} otherwise</li>
     *         </ol>
     * @since 1.0
     */
    public Xpp3Dom get(String property) {

        // first try page properties
        Xpp3Dom propNode = getChild(pageProperties, property);
        if (propNode == null) {
            // try global
            propNode = getChild(globalProperties, property);
        }

        return propNode;
    }

    /**
     * Retrieves the text value of the given {@code property}, e.g. as in
     * {@code <myprop>value</myprop>}.
     * 
     * @param property
     *            the property of interest
     * @return the configuration value if found in page or globally, {@code null} otherwise.
     * @see #get(String)
     * @since 1.0
     */
    public String value(String property) {

        Xpp3Dom propNode = get(property);

        if (propNode == null) {
            // not found
            return null;
        }

        return propNode.getValue();
    }

    /**
     * A convenience method to check if the value of the {@code property} is {@code "true"}.
     * 
     * @param property
     *            the property of interest
     * @return {@code true} if the configuration value is set either in page or globally, and is
     *         equal to {@code "true"}.
     * @see #get(String)
     * @since 1.0
     */
    public boolean is(String property) {
        return "true".equals(value(property));
    }

    /**
     * A convenience method to check if the value of the {@code property} is {@code "false"}. Useful
     * for properties that are enabled by default - checks if the property is set to {@code "false"}
     * explicitly.
     * 
     * @param property
     *            the property of interest
     * @return {@code true} if the configuration value is set either in page or globally, and is
     *         equal to {@code "false"}. Note that this will return {@code false} if property is not
     *         set at all.
     * @see #get(String)
     * @since 1.0
     */
    public boolean not(String property) {
        return "false".equals(value(property));
    }

    /**
     * A convenience method to check if the {@code property} is set to a specific value.
     * 
     * @param property
     *            the property of interest
     * @param value
     *            the property value to check
     * @return {@code true} if the configuration value is set either in page or globally, and is
     *         equal to {@code value}.
     * @see #get(String)
     * @since 1.0
     */
    public boolean isValue(String property, String value) {
        return value != null && value.equals(value(property));
    }

    public String getProjectId() {
        return projectId;
    }

    public String getFileId() {
        return fileId;
    }

}