ch.entwine.weblounge.common.impl.site.ModuleImpl.java Source code

Java tutorial

Introduction

Here is the source code for ch.entwine.weblounge.common.impl.site.ModuleImpl.java

Source

/*
 *  Weblounge: Web Content Management System
 *  Copyright (c) 2003 - 2011 The Weblounge Team
 *  http://entwinemedia.com/weblounge
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software Foundation
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

package ch.entwine.weblounge.common.impl.site;

import ch.entwine.weblounge.common.content.SearchResultItem;
import ch.entwine.weblounge.common.content.image.ImageStyle;
import ch.entwine.weblounge.common.content.page.PageletRenderer;
import ch.entwine.weblounge.common.impl.content.image.ImageStyleImpl;
import ch.entwine.weblounge.common.impl.content.page.PageletRendererImpl;
import ch.entwine.weblounge.common.impl.scheduler.QuartzJob;
import ch.entwine.weblounge.common.impl.url.WebUrlImpl;
import ch.entwine.weblounge.common.impl.util.config.OptionsHelper;
import ch.entwine.weblounge.common.impl.util.xml.XPathHelper;
import ch.entwine.weblounge.common.impl.util.xml.XPathNamespaceContext;
import ch.entwine.weblounge.common.scheduler.Job;
import ch.entwine.weblounge.common.site.Action;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.Module;
import ch.entwine.weblounge.common.site.ModuleException;
import ch.entwine.weblounge.common.site.ModuleListener;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.common.site.SiteURL;
import ch.entwine.weblounge.common.url.UrlUtils;
import ch.entwine.weblounge.common.url.WebUrl;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;

/**
 * Base implementation for a module. It is recommended that individual
 * <code>Module</code> implementations extend this class.
 */
public class ModuleImpl implements Module {

    /** Logging facility */
    protected static final Logger logger = LoggerFactory.getLogger(ModuleImpl.class);

    /** Regular expression to test the validity of a module identifier */
    private static final String MODULE_IDENTIFIER_REGEX = "^[a-zA-Z0-9]+[a-zA-Z0-9-_.]*$";

    /** Xml namespace for the module */
    public static final String MODULE_XMLNS = "http://www.entwinemedia.com/weblounge/3.0/module";

    /** The module identifier */
    protected String identifier = null;

    /** The url that is used to reach module assets */
    protected WebUrl url = null;

    /** Module enabled state */
    protected boolean enabled = true;

    /** Module running state */
    private boolean running = false;

    /** Can this module be searched? */
    protected boolean searchable = false;

    /** The hosting site */
    protected Site site = null;

    /** Option handling support */
    protected OptionsHelper options = null;

    /** Localized module title */
    protected String name = null;

    /** Module pagelet renderers */
    protected Map<String, PageletRenderer> renderers = null;

    /** Module actions */
    protected Map<String, Action> actions = null;

    /** Module image styles */
    protected Map<String, ImageStyle> imagestyles = null;

    /** Module jobs */
    protected Map<String, Job> jobs = null;

    /** List of module listeners */
    protected List<ModuleListener> moduleListeners = null;

    /** The environment */
    protected Environment environment = Environment.Production;

    /**
     * Creates a new module.
     */
    public ModuleImpl() {
        renderers = new HashMap<String, PageletRenderer>();
        actions = new HashMap<String, Action>();
        imagestyles = new HashMap<String, ImageStyle>();
        jobs = new HashMap<String, Job>();
        options = new OptionsHelper();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#initialize(ch.entwine.weblounge.common.site.Environment)
     */
    public void initialize(Environment environment) {
        this.environment = environment;

        // Tell the renderers about the environment
        for (PageletRenderer renderer : renderers.values()) {
            renderer.setEnvironment(environment);
        }

        // Tell the actions about the environment
        for (Action action : actions.values()) {
            action.setEnvironment(environment);
        }

        // Tell the jobs about the environment
        for (Job job : jobs.values()) {
            job.setEnvironment(environment);
        }

        // Switch the options to the new environment
        options.setEnvironment(environment);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#setIdentifier(java.lang.String)
     */
    public void setIdentifier(String identifier) {
        if (identifier == null)
            throw new IllegalArgumentException("Module identifier must not be null");
        else if (!Pattern.matches(MODULE_IDENTIFIER_REGEX, identifier))
            throw new IllegalArgumentException("Module identifier '" + identifier + "' is malformed");
        this.identifier = identifier;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getIdentifier()
     */
    public String getIdentifier() {
        return identifier;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getUrl()
     */
    public WebUrl getUrl() {
        return getUrl(environment);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getUrl(ch.entwine.weblounge.common.site.Environment)
     */
    public WebUrl getUrl(Environment environment) {
        if (url != null)
            return url;
        if (site == null)
            throw new IllegalStateException("Site has not yet been set");
        SiteURL siteURL = site.getHostname(environment);
        url = new WebUrlImpl(site, UrlUtils.concat(siteURL.toExternalForm(), "module", identifier));
        return url;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#addModuleListener(ch.entwine.weblounge.common.site.ModuleListener)
     */
    public void addModuleListener(ModuleListener listener) {
        if (moduleListeners == null)
            moduleListeners = new ArrayList<ModuleListener>();
        synchronized (moduleListeners) {
            moduleListeners.add(listener);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#removeModuleListener(ch.entwine.weblounge.common.site.ModuleListener)
     */
    public void removeModuleListener(ModuleListener listener) {
        if (moduleListeners != null) {
            synchronized (moduleListeners) {
                moduleListeners.remove(listener);
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#setSite(ch.entwine.weblounge.common.site.Site)
     */
    public void setSite(Site site) throws ModuleException {
        this.site = site;

        // Initialize actions
        for (Action action : actions.values()) {
            action.setSite(site);
        }

        // Initialize renderers
        for (PageletRenderer renderer : renderers.values()) {
            renderer.setModule(this);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#destroy()
     */
    public void destroy() {
        this.site = null;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#addAction(ch.entwine.weblounge.common.site.Action)
     */
    public void addAction(Action action) {
        actions.put(action.getIdentifier(), action);
        action.setModule(this);
        if (site != null)
            action.setSite(site);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#removeAction(ch.entwine.weblounge.common.site.Action)
     */
    public void removeAction(Action action) {
        actions.remove(action.getIdentifier());
        action.setModule(null);
        action.setSite(null);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getAction(java.lang.String)
     */
    public Action getAction(String id) {
        return actions.get(id);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getActions()
     */
    public Action[] getActions() {
        return actions.values().toArray(new Action[actions.size()]);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#addImageStyle(ch.entwine.weblounge.common.content.image.ImageStyle)
     */
    public void addImageStyle(ImageStyle imagestyle) {
        imagestyles.put(imagestyle.getIdentifier(), imagestyle);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#removeImageStyle(ch.entwine.weblounge.common.content.image.ImageStyle)
     */
    public void removeImageStyle(ImageStyle imagestyle) {
        imagestyles.remove(imagestyle.getIdentifier());
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getImageStyles()
     */
    public ImageStyle[] getImageStyles() {
        return imagestyles.values().toArray(new ImageStyle[imagestyles.size()]);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getImageStyle(java.lang.String)
     */
    public ImageStyle getImageStyle(String id) {
        return imagestyles.get(id);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#addRenderer(ch.entwine.weblounge.common.content.page.PageletRenderer)
     */
    public void addRenderer(PageletRenderer renderer) {
        renderers.put(renderer.getIdentifier(), renderer);
        if (site != null) {
            renderer.setModule(this);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#removeRenderer(ch.entwine.weblounge.common.content.page.PageletRenderer)
     */
    public void removeRenderer(PageletRenderer renderer) {
        renderers.remove(renderer.getIdentifier());
        renderer.setModule(null);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getRenderer(java.lang.String)
     */
    public PageletRenderer getRenderer(String id) {
        return renderers.get(id);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getRenderers()
     */
    public PageletRenderer[] getRenderers() {
        return renderers.values().toArray(new PageletRenderer[renderers.size()]);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#addJob(ch.entwine.weblounge.common.scheduler.Job)
     */
    public void addJob(Job job) {
        jobs.put(job.getIdentifier(), job);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#removeJob(ch.entwine.weblounge.common.scheduler.Job)
     */
    public void removeJob(Job job) {
        jobs.remove(job.getIdentifier());
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getJob(java.lang.String)
     */
    public Job getJob(String id) {
        return jobs.get(id);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getJobs()
     */
    public Job[] getJobs() {
        return jobs.values().toArray(new Job[jobs.size()]);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getSite()
     */
    public Site getSite() {
        return site;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#setName(java.lang.String)
     */
    public void setName(String title) {
        this.name = title;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#getName()
     */
    public String getName() {
        return name;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#setEnabled(boolean)
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#isEnabled()
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#setSearchable(boolean)
     */
    public void setSearchable(boolean searchable) {
        this.searchable = searchable;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#isSearchable()
     */
    public boolean isSearchable() {
        return searchable;
    }

    /**
     * {@inheritDoc}
     * <p>
     * This default implementation will always return an empty result set.
     * Subclasses returning <code>true</code> in {@link #isSearchable()} should
     * therefore overwrite this method.
     * 
     * @see ch.entwine.weblounge.common.site.Module#search(java.lang.String)
     */
    public SearchResultItem[] search(String query) {
        return new SearchResultItem[] {};
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#start()
     */
    public void start() throws ModuleException {
        logger.debug("Starting module {}", this);
        if (running)
            throw new IllegalStateException("Module is already running");
        if (!enabled)
            throw new IllegalStateException("Cannot start a disabled module");

        // Finally, mark this module as running
        running = true;
        logger.debug("Module '{}' started", this);

        // Tell listeners
        fireModuleStarted();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#stop()
     */
    public void stop() throws ModuleException {
        logger.debug("Stopping module {}", this);
        if (!running)
            throw new IllegalStateException("Module is not running");

        // Finally, mark this module as stopped
        running = false;
        logger.debug("Module '{}' stopped", this);

        // Tell listeners
        fireModuleStopped();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#setOption(java.lang.String,
     *      java.lang.String)
     */
    public void setOption(String name, String value) {
        options.setOption(name, value);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#setOption(java.lang.String,
     *      java.lang.String, ch.entwine.weblounge.common.site.Environment)
     */
    public void setOption(String name, String value, Environment environment) {
        options.setOption(name, value, environment);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#removeOption(java.lang.String)
     */
    public void removeOption(String name) {
        options.removeOption(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#getOptionValue(java.lang.String)
     */
    public String getOptionValue(String name) {
        return options.getOptionValue(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#getOptionValue(java.lang.String,
     *      java.lang.String)
     */
    public String getOptionValue(String name, String defaultValue) {
        return options.getOptionValue(name, defaultValue);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#getOptionValues(java.lang.String)
     */
    public String[] getOptionValues(String name) {
        return options.getOptionValues(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#hasOption(java.lang.String)
     */
    public boolean hasOption(String name) {
        return options.hasOption(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#getOptionNames()
     */
    public String[] getOptionNames() {
        return options.getOptionNames();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#getOptions()
     */
    public Map<String, Map<Environment, List<String>>> getOptions() {
        return options.getOptions();
    }

    /**
     * Method to fire a <code>moduleStarted()</code> message to all registered
     * <code>ModuleListener</code>s.
     */
    protected void fireModuleStarted() {
        if (moduleListeners == null)
            return;
        synchronized (moduleListeners) {
            for (ModuleListener listener : moduleListeners) {
                listener.moduleStarted(this);
            }
        }
    }

    /**
     * Method to fire a <code>moduleStopped()</code> message to all registered
     * <code>ModuleListener</code>s.
     */
    protected void fireModuleStopped() {
        if (moduleListeners == null)
            return;
        synchronized (moduleListeners) {
            for (ModuleListener listener : moduleListeners) {
                listener.moduleStopped(this);
            }
        }
    }

    /**
     * Initializes this module from an XML node that was generated using
     * {@link #toXml()}.
     * <p>
     * To speed things up, you might consider using the second signature that uses
     * an existing <code>XPath</code> instance instead of creating a new one.
     * 
     * @param config
     *          the module node
     * @throws IllegalStateException
     *           if the module cannot be parsed
     * @see #fromXml(Node, XPath)
     * @see #toXml()
     */
    public static Module fromXml(Node config) throws IllegalStateException {
        XPath xpath = XPathFactory.newInstance().newXPath();

        // Define the xml namespace
        XPathNamespaceContext nsCtx = new XPathNamespaceContext(false);
        nsCtx.defineNamespaceURI("m", MODULE_XMLNS);
        xpath.setNamespaceContext(nsCtx);

        return fromXml(config, xpath);
    }

    /**
     * Initializes this module from an XML node that was generated using
     * {@link #toXml()}.
     * 
     * @param config
     *          the module node
     * @param xpathProcessor
     *          xpath processor to use
     * @throws IllegalStateException
     *           if the module cannot be parsed
     * @see #toXml()
     */
    @SuppressWarnings("unchecked")
    public static Module fromXml(Node config, XPath xpathProcessor) throws IllegalStateException {

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        // identifier
        String identifier = XPathHelper.valueOf(config, "@id", xpathProcessor);
        if (identifier == null)
            throw new IllegalStateException("Unable to create module without identifier");

        // class
        Module module = null;
        String className = XPathHelper.valueOf(config, "m:class", xpathProcessor);
        if (className != null) {
            try {
                Class<? extends Module> c = (Class<? extends Module>) classLoader.loadClass(className);
                module = c.newInstance();
                module.setIdentifier(identifier);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(
                        "Implementation " + className + " for module '" + identifier + "' not found", e);
            } catch (InstantiationException e) {
                throw new IllegalStateException(
                        "Error instantiating impelementation " + className + " for module '" + identifier + "'", e);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Access violation instantiating implementation " + className
                        + " for module '" + identifier + "'", e);
            } catch (Throwable t) {
                throw new IllegalStateException(
                        "Error loading implementation " + className + " for module '" + identifier + "'", t);
            }
        } else {
            module = new ModuleImpl();
            module.setIdentifier(identifier);
        }

        // Check if module is enabled
        Boolean enabled = Boolean.valueOf(XPathHelper.valueOf(config, "m:enable", xpathProcessor));
        module.setEnabled(enabled);

        // name
        String name = XPathHelper.valueOf(config, "m:name", xpathProcessor);
        module.setName(name);

        // pagelets
        NodeList pageletNodes = XPathHelper.selectList(config, "m:pagelets/m:pagelet", xpathProcessor);
        for (int i = 0; i < pageletNodes.getLength(); i++) {
            PageletRenderer pagelet = PageletRendererImpl.fromXml(pageletNodes.item(i), xpathProcessor);
            module.addRenderer(pagelet);
        }

        // actions
        NodeList actionNodes = XPathHelper.selectList(config, "m:actions/m:action", xpathProcessor);
        for (int i = 0; i < actionNodes.getLength(); i++) {
            module.addAction(ActionSupport.fromXml(actionNodes.item(i), xpathProcessor));
        }

        // image styles
        NodeList imagestyleNodes = XPathHelper.selectList(config, "m:imagestyles/m:imagestyle", xpathProcessor);
        for (int i = 0; i < imagestyleNodes.getLength(); i++) {
            module.addImageStyle(ImageStyleImpl.fromXml(imagestyleNodes.item(i), xpathProcessor));
        }

        // jobs
        NodeList jobNodes = XPathHelper.selectList(config, "m:jobs/m:job", xpathProcessor);
        for (int i = 0; i < jobNodes.getLength(); i++) {
            module.addJob(QuartzJob.fromXml(jobNodes.item(i), xpathProcessor));
        }

        // options
        Node optionsNode = XPathHelper.select(config, "m:options", xpathProcessor);
        OptionsHelper.fromXml(optionsNode, module, xpathProcessor);

        return module;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Module#toXml()
     */
    public String toXml() {
        StringBuffer b = new StringBuffer();
        b.append("<module id=\"");
        b.append(identifier);
        b.append("\" ");

        // namespace and schema
        b.append(
                "xmlns=\"http://www.entwinemedia.com/weblounge/3.0/module\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.entwinemedia.com/weblounge/3.0/module http://www.entwinemedia.com/xsd/weblounge/3.0/module.xsd\"");

        b.append(">");

        // enable
        b.append("<enable>").append(enabled).append("</enable>");

        // Names
        if (StringUtils.isNotBlank(name)) {
            b.append("<name><![CDATA[");
            b.append(name);
            b.append("]]></name>");
        }

        // class
        if (!this.getClass().equals(ModuleImpl.class))
            b.append("<class>").append(getClass().getName()).append("</class>");

        // pagelets
        if (renderers.size() > 0) {
            b.append("<pagelets>");
            for (PageletRenderer renderer : renderers.values()) {
                b.append(renderer.toXml());
            }
            b.append("</pagelets>");
        }

        // actions
        if (actions.size() > 0) {
            b.append("<actions>");
            for (Action action : actions.values()) {
                b.append(action.toXml());
            }
            b.append("</actions>");
        }

        // jobs
        if (jobs.size() > 0) {
            b.append("<jobs>");
            for (Job job : jobs.values()) {
                b.append(job.toXml());
            }
            b.append("</jobs>");
        }

        // image styles
        if (imagestyles.size() > 0) {
            b.append("<imagestyles>");
            for (ImageStyle imagestyle : imagestyles.values()) {
                b.append(imagestyle.toXml());
            }
            b.append("</imagestyles>");
        }

        // Options
        b.append(options.toXml());

        b.append("</module>");
        return b.toString();
    }

    /**
     * Returns <code>true</code> if <code>obj</code> is a module featuring the
     * same module identifier than this one.
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (obj != null && obj instanceof Module) {
            Module m = (Module) obj;
            if (identifier == null || m.getIdentifier() == null || !identifier.equals(m.getIdentifier()))
                return false;
            if (site == null && m.getSite() != null)
                return false;
            if (site != null && !site.equals(m.getSite()))
                return false;
            return true;
        }
        return false;
    }

    /**
     * Returns the module identifier's hash code.
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        if (identifier != null)
            return identifier.hashCode();
        else
            return super.hashCode();
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        if (identifier != null)
            return identifier;
        else
            return super.toString();
    }

}