org.getobjects.appserver.core.WOResourceManager.java Source code

Java tutorial

Introduction

Here is the source code for org.getobjects.appserver.core.WOResourceManager.java

Source

/*
  Copyright (C) 2006-2007 Helge Hess
    
  This file is part of Go.
    
  Go 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, or (at your option) any
  later version.
    
  Go 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 Go; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

package org.getobjects.appserver.core;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.getobjects.appserver.elements.WOHTMLDynamicElement;
import org.getobjects.foundation.NSClassLookupContext;
import org.getobjects.foundation.NSJavaRuntime;
import org.getobjects.foundation.NSObject;
import org.getobjects.foundation.UData;

/**
 * WOResourceManager
 * <p>
 * Manages access to resources associated with WOApplication. In the context
 * of Java this is mostly based upon the resource mechanism supported by
 * Class.getResource().
 *
 * <h4>Component Discovery and Page Creation in SOPE</h4>
 * <p>
 *    All WO code uses either directly or indirectly the WOResourceManager's
 *    -pageWithName:languages: method to instantiate WO components.
 * <p>
 *    This methods works in three steps:
 * <ol>
 *   <li>discovery of files associated with the component</li>
 *   <li>creation of a proper WOComponentDefinition, which is some kind
 *         of 'blueprint' or 'class' for components</li>
 *   <li>component instantiation using the definition</li>
 * </ol>
 * <p>
 *    All the instantiation/setup work is done by a component definition, the
 *    resource manager is only responsible for managing those 'blueprint'
 *    resources.
 * <p>
 *    If you want to customize component creation, you can supply your
 *    own WOComponentDefinition in a subclass of WOResourceManager by
 *    overriding:
 * <pre>
 * - (WOComponentDefinition *)definitionForComponent:(id)_name
 *   inFramework:(NSString *)_frameworkName
 *   languages:(NSArray *)_languages</pre>
 * </pre>
 *
 * <p>
 * THREAD: TODO
 */
public abstract class WOResourceManager extends NSObject implements NSClassLookupContext {
    protected static final Log log = LogFactory.getLog("WOResourceManager");

    /* keep this at null if you do not want to cache ... */
    protected Map<Object, IWOComponentDefinition> componentDefinitions;
    protected boolean isCachingEnabled;

    public WOResourceManager(boolean _enableCaching) {
        this.isCachingEnabled = _enableCaching;

        if (this.isCachingEnabled) {
            this.componentDefinitions = new ConcurrentHashMap<Object, IWOComponentDefinition>(32);
        }
    }

    /* templates */

    /**
     * Locates the component definition for the given component name and
     * instantiates that definition. No other magic involved.
     * Note that the WOComponent will (per default) use templateWithName() to
     * locate its template, which involves the same WOComponentDefinition.
     * <p>
     * This method gets called by WOApplication.pageWithName(), but also by
     * the WOComponentFault. (TBD: when to use this method directly?)
     *
     * @param _name - name of page to lookup and instantiate
     * @param _ctx  - the WOContext to instantiate the page in
     * @return an instantiated WOComponent, or null on error
     */
    public WOComponent pageWithName(String _name, WOContext _ctx) {
        if (log.isDebugEnabled())
            log.debug("pageWithName(" + _name + ", " + _ctx + ")");

        IWOComponentDefinition cdef;

        /* Note: we pass in the root resource manager as the class resolver. This
         *       is used in WOComponentDefinition.load() which is triggered along
         *       the way (and needs the clsctx to resolve WOElement names in
         *       templates)
         */
        WOResourceManager rm = _ctx.rootResourceManager();
        if (rm == null) {
            log.debug("  rootResourceManager is null, falling back to this");
            rm = this;
        }

        /* the underscore method does all the caching and then triggers 'da real
         * definitionForComponent() method.
         */
        cdef = this._definitionForComponent(_name, _ctx.languages(), rm);
        if (cdef == null) {
            if (log.isDebugEnabled())
                log.debug("  found no cdef for component: " + _name);
            return null;
        }

        return cdef.instantiateComponent(this, _ctx);
    }

    /**
     * Locates the component definition for the given component name and
     * returns the associated dynamic element tree.
     * This is called by WOComponent when it requests its template for request
     * processing or rendering.
     *
     * @param _name  - name of template (same like the components name)
     * @param _langs - languages to check
     * @param _rm    - class context in which to parse template class names
     * @return the parsed template
     */
    public WOElement templateWithName(String _name, List<String> _langs, WOResourceManager _rm) {
        IWOComponentDefinition cdef;

        if ((cdef = this._definitionForComponent(_name, _langs, _rm)) == null)
            return null;

        /* this will invoke the template parser */
        return cdef.template();
    }

    /* component definitions */

    /**
     * This manages the caching of WOComponentDefinition's. When asked for a
     * definition it checks the cache and if it does not find one, it will call
     * the primary load method: definitionForComponent().
     */
    protected IWOComponentDefinition _definitionForComponent(String _name, List<String> _langs,
            WOResourceManager _rm) {
        String[] langs = _langs != null ? _langs.toArray(new String[0]) : null;

        /* look into cache */

        IWOComponentDefinition cdef;
        if ((cdef = this._cachedDefinitionForComponent(_name, langs)) != null) {
            if (cdef == null) { // TODO: add some kind of 'empty' marker
                /* component does not exist */
                return null;
            }

            cdef.touch();
            return cdef;
        }

        /* not cached, create a definition */

        cdef = this.definitionForComponent(_name, langs, _rm);

        /* cache created definition */

        return this._cacheDefinitionForComponent(_name, langs, cdef);
    }

    /**
     * This is the primary method to locate a component and return a
     * WOComponentDefinition describing it. The definition is just a blueprint,
     * not an actual instance of the component.
     * <p>
     * The method calls load() on the definition, this will actually load the
     * template of the component.
     * <p>
     * All the caching is done by the wrapping method.
     *
     * @param _name  - the name of the component to load (eg 'Main')
     * @param _langs - the languages to check
     * @param _rm    - the RM used to lookup classes
     * @return a WOComponentDefinition which represents the specific component
     */
    public IWOComponentDefinition definitionForComponent(String _name, String[] _langs, WOResourceManager _rm) {
        /*
         * Note: a 'package component' is a component which has its own package,
         *       eg: org.opengroupware.HomePage with subelements 'HomePage.class',
         *       'HomePage.html' and 'HomePage.wod'.
         */
        // TODO: complete me
        URL templateData = null;
        String type = "WOx";
        String rsrcName = _name != null ? _name.replace('.', '/') : null;
        boolean debugOn = log.isDebugEnabled();

        if (debugOn)
            log.debug("make cdef for component: " + _name);

        Class cls = this.lookupComponentClass(_name);
        if (cls == null) { /* we do not serve this class */
            if (debugOn)
                log.debug("rm does not serve the class, check for templates: " + _name);

            /* check whether its a component w/o a class */
            templateData = this.urlForResourceNamed(rsrcName + ".wox", _langs);
            if (templateData == null) {
                type = "WOWrapper";
                templateData = this.urlForResourceNamed(rsrcName + ".html", _langs);
            }

            if (templateData == null)
                return null; /* did not find a template */

            if (debugOn)
                log.debug("  found a class-less component: " + _name);
            cls = WOComponent.class; // TODO: we might want to use a different class
        }
        if (debugOn)
            log.debug("  comp class: " + cls);

        /* this is a bit hackish ;-), but well, ... */
        boolean isPackageComponent = false;
        String className = cls.getName();
        if (className.endsWith("." + _name + ".Component"))
            isPackageComponent = true;
        else if (className.endsWith("." + _name + "." + _name))
            isPackageComponent = true;

        if (debugOn) {
            if (isPackageComponent)
                log.debug("  found a package component: " + className);
            else
                log.debug("  component is not a pkg one: " + _name + "/" + className);
        }

        /* def */

        IWOComponentDefinition cdef = new WOComponentDefinition(_name, cls);

        /* find template */

        URL wodData = null;

        if (!isPackageComponent) {
            if (templateData == null)
                templateData = this.urlForResourceNamed(rsrcName + ".wox", _langs);
            if (templateData == null) {
                templateData = this.urlForResourceNamed(rsrcName + ".html", _langs);
                type = "WOWrapper";
            }

            if ("WOWrapper".equals(type))
                wodData = this.urlForResourceNamed(rsrcName + ".wod", _langs);
        } else {
            // Note: we directly access the class resources. Not sure yet whether
            //       this is a good idea or whether we should instantiate a new
            //       WOClassResourceManager for resource lookup?
            //       This resource manager could also be set as the components
            //       resource manager?
            // TODO: localization?
            templateData = cls.getResource(_name + ".wox");
            if (templateData == null)
                templateData = cls.getResource("Component.wox");

            if (templateData == null) {
                type = "WOWrapper";
                templateData = cls.getResource(_name + ".html");
                wodData = cls.getResource(_name + ".wod");

                if (debugOn)
                    log.debug("in " + cls + " lookup " + _name + ".html: " + templateData);

                if (templateData == null)
                    templateData = cls.getResource("Component.html");
                if (wodData == null)
                    wodData = cls.getResource("Component.wod");
            }
        }

        if (templateData == null) {
            if (debugOn)
                log.debug("component has no template: " + _name);
            return cdef;
        }

        /* load it */

        if (!cdef.load(type, templateData, wodData, _rm)) {
            log.error("failed to load template.");
            return null;
        }
        return cdef;
    }

    /* component definition cache */

    protected String genCacheKey(String _name, String[] _langs) {
        // TODO: improve ...
        /* Note: using arrays as keys didn't work properly? */
        StringBuilder sb = new StringBuilder(_langs.length * 8 + _name.length());

        sb.append(_name);

        for (int i = 0; i < _langs.length; i++) {
            sb.append(':');
            sb.append(_langs[i]);
        }
        return sb.toString();
    }

    protected IWOComponentDefinition _cachedDefinitionForComponent(String _name, String[] _langs) {
        if (this.componentDefinitions == null) /* caching disabled */
            return null;

        if (_langs == null)
            return this.componentDefinitions.get(_name);
        if (_langs.length == 0)
            return this.componentDefinitions.get(_name);

        String cacheKey = this.genCacheKey(_name, _langs);
        return this.componentDefinitions.get(cacheKey);
    }

    protected static boolean didWarnOnCaching = false;

    protected IWOComponentDefinition _cacheDefinitionForComponent(String _name, String[] _langs,
            IWOComponentDefinition _cdef) {
        if (this.componentDefinitions == null) { /* caching disabled */
            if (!didWarnOnCaching) {
                log.warn("component caching is disabled!");
                didWarnOnCaching = true;
            }
            return _cdef;
        }

        boolean isDebugOn = log.isDebugEnabled();
        String cacheKey;

        if (_langs == null || _langs.length == 0) {
            cacheKey = _name;
        } else {
            cacheKey = this.genCacheKey(_name, _langs);
            if (isDebugOn)
                log.debug("cache cdef w/ langs: " + cacheKey);
        }

        if (_cdef != null)
            this.componentDefinitions.put(cacheKey, _cdef);
        else // ConcurrentHashMap does not allow null
            this.componentDefinitions.remove(cacheKey);
        return _cdef;
    }

    /* resources */

    /**
     * Returns the internal resource URL for a resource name and a set of language
     * codes.
     * The default implementation just returns null, subclasses need to override
     * the method to implement resource lookup.
     * <p>
     * Important: the returned URL is usually a file: or jar: URL for use in
     * server side code. It is NOT the URL which is exposed to the browser/client.
     */
    public URL urlForResourceNamed(String _name, String[] _languages) {
        return null;
    }

    /**
     * Determines the URL of the given resource and opens an InputStream to the
     * resource identified by the URL.
     *
     * @param _name - name of the resource to be opened
     * @param _ls   - array of language codes (eg [ 'de', 'en' ])
     * @return an InputStream or null if the resource could not be found or opened
     */
    public InputStream inputStreamForResourceNamed(String _name, String[] _ls) {
        URL url = this.urlForResourceNamed(_name, _ls);
        if (url == null)
            return null;
        try {
            return url.openStream();
        } catch (IOException e) {
            log.info("could not open URL to get stream: " + url);
            return null;
        }
    }

    /**
     * Opens a stream to the given resource and loads the content into a byte
     * array.
     *
     * @param _name  - name of the resource to be opened
     * @param _langs - array of language codes (eg [ 'de', 'en' ])
     * @return byte array with the contents, or null if the resource is missing
     */
    public byte[] bytesForResourceNamed(String _name, String[] _langs) {
        InputStream in = this.inputStreamForResourceNamed(_name, _langs);
        if (in == null)
            return null;

        /* Note: this will close the stream */
        return UData.loadContentFromStream(in);
    }

    /**
     * Returns the client side (browser) URL of a <em>public</em> resource. The
     * default implementation defines 'public' resources as those living in the
     * 'www' directory, that is, the method prefixes the resource with 'www/'.
     * <p>
     * This method is used to resolve 'filename' bindings in dynamic elements.
     *
     * @param _name   - name of the resource
     * @param _fwname - unused by the default implementation, a framework name
     * @param _langs  - a set of language codes
     * @param _ctx    - a WOContext, this will be asked to construct the URL
     * @return a URL which allows the browser to retrieve the given resource
     */
    public String urlForResourceNamed(String _name, String _fwname, List<String> _langs, WOContext _ctx) {
        // TODO: crappy way to detect whether a resource is available
        InputStream in = this.inputStreamForResourceNamed("www/" + _name,
                _langs != null ? _langs.toArray(new String[0]) : null);
        if (in == null)
            return null;

        try {
            in.close();
        } catch (IOException e) {
            log.error("failed to close resource InputStream", e);
        }
        in = null;

        return _ctx.urlWithRequestHandlerKey("wr", _name, null);
    }

    /* strings */

    /**
     * Converts the _langs to an array and calls the array based
     * localForLanguages().
     * Which returns the java.util.Locale object for the given _langs.
     * 
     * <p>
     * This method is called by the WOContext.deriveLocale() method.
     * 
     * @param _langs - languages to check
     * @return the Locale object for the given languages, or Locale.US
     */
    static public Locale localeForLanguages(final Collection<String> _langs) {
        if (_langs == null)
            return Locale.US;

        int num = _langs.size();
        if (num == 0)
            return Locale.US;

        return localeForLanguages(_langs.toArray(new String[num]));
    }

    /**
     * Returns the Locale object for the given language codes. Currently this just
     * checks for the first item in the array.
     * 
     * @param _langs - languages to check for
     * @return a Locale object
     */
    static public Locale localeForLanguages(String[] _langs) {
        if (_langs == null)
            return Locale.US;
        if (_langs.length == 0)
            return Locale.US;

        String s = _langs[0];
        int idx = s.indexOf('-');
        return idx == -1 ? new Locale(s) : new Locale(s.substring(0, idx), s.substring(idx + 1));
    }

    public ResourceBundle stringTableWithName(String _table, String _fwname, String[] _langs) {
        return null;
    }

    /**
     * This method just calls stringForKey() with the _langs collection being
     * converted to an array.
     * This in turn retrieves a ResourceBundle using stringTableWithName() and
     * then performs a lookup in that bundle.
     * 
     * @param _key     - string to lookup (eg: 05_private)
     * @param _table   - name of table, eg null, LocalizableStrings, or Main
     * @param _default - string to use if the key could not be resolved
     * @param _fwname  - name of framework containing the resource, or null
     * @param _langs   - languages to check for the key
     * @return the resolved string, or the the _default
     */
    public String stringForKey(String _key, String _table, String _default, String _fwname,
            Collection<String> _langs) {
        return this.stringForKey(_key, _table, _default, _fwname,
                _langs != null ? _langs.toArray(new String[_langs.size()]) : (String[]) null);
    }

    /**
     * Retrieves the string table using stringTableWithName(), and then attempts
     * to resolve the key. If the key could not be found, the _default is returned
     * instead.
     * 
     * @param _key     - string to lookup (eg: 05_private)
     * @param _table   - name of table, eg null, LocalizableStrings, or Main
     * @param _default - string to use if the key could not be resolved
     * @param _fwname  - name of framework containing the resource, or null
     * @param _langs   - languages to check for the key
     * @return the resolved string, or the the _default
     */
    public String stringForKey(String _key, String _table, String _default, String _fwname, String[] _langs) {
        ResourceBundle rb = this.stringTableWithName(_table, _fwname, _langs);
        if (rb == null)
            return _default != null ? _default : _key;

        try {
            return rb.getString(_key);
        } catch (MissingResourceException e) {
            return _default;
        }
    }

    /* reflection */

    protected static String[] JOPELookupPath = { WOHTMLDynamicElement.class.getPackage().getName(),
            WOApplication.class.getPackage().getName() };

    /**
     * This is a context-specific class lookup method. Its added to implement
     * lookup by "short names" (eg <code>Main</code> instead of
     * <code>org.opengroupware.samples.HelloWorld.Main</code>.
     * <p>
     * Note: this is overridden by subclasses.
     *
     * @param _name - the name of the Java class to lookup
     * @return a Java class or null if none could be found for the name
     */
    public Class lookupClass(String _name) {
        // TODO: cache lookup results?
        Class cls;

        if ((cls = NSJavaRuntime.NSClassFromString(_name)) != null)
            return cls;

        /* then check package hierarchy of Go */
        cls = NSJavaRuntime.NSClassFromString(_name, JOPELookupPath);
        if (cls != null)
            return cls;

        return null;
    }

    /**
     * Used by the template parser to lookup a component name. A component class
     * does not necessarily match a Java class, eg a component written in Python
     * might use a single "WOPyComponent" class for all Python components.
     * <p>
     * However, the default implementation just calls lookupClass() with the
     * given name :-)
     * <p>
     * Note: this method is ONLY used for WOComponents.
     *
     * @param _name - the name of the component to lookup
     * @return a Class responsible for the component with the given name
     */
    public Class lookupComponentClass(String _name) {
        Class cls = this.lookupClass(_name);
        return (cls != null && WOComponent.class.isAssignableFrom(cls)) ? cls : null;
    }

    /**
     * Used by the template parser to lookup a dynamic element name.
     * <p>
     * However, the default implementation just calls lookupClass() with the
     * given name :-)
     * <p>
     * Note: this method is used for WODynamicElement classes only.
     *
     * @param _name - the name of the element to lookup
     * @return a Class responsible for the element with the given name
     */
    public Class lookupDynamicElementClass(String _name) {
        Class cls = this.lookupClass(_name);
        return (cls != null && WODynamicElement.class.isAssignableFrom(cls)) ? cls : null;
    }

    /**
     * This is invoked by code which wants to instantiate a "direct action". This
     * can be a WOAction subclass, or a WOComponent.
     * <br>
     * Note that the context is different to lookupComponentClass(), which
     * can return a WOComponent or WODynamicElement. This method usually returns a
     * WOComponent or a WOAction.
     *
     * @param _name - the name of the action or class to lookup
     * @return a Class to be used for instantiating the given action object
     */
    public Class lookupDirectActionClass(String _name) {
        return this.lookupClass(_name);
    }

    /* equality (used during RM hierarchy construction) */

    @Override
    public boolean equals(Object obj) {
        return (this == obj);
    }

    /* description */

    @Override
    public void appendAttributesToDescription(StringBuilder _d) {
        super.appendAttributesToDescription(_d);

        if (this.componentDefinitions != null) {
            _d.append(" #defs=");
            _d.append(this.componentDefinitions.size());
        }
    }
}