2021SC@SDUSC [application and practice of software engineering] Cocoon code analysis

Posted by dudeddy on Tue, 21 Dec 2021 23:41:46 +0100

2021SC@SDUSC

The main content of this code analysis is the content in the core folder under the sitemap impl folder. This core folder may be the core code to implement the site map. Due to the large content, I will divide it into two blogs for analysis. This blog is mainly based on the analysis of some code in the container folder in the core folder, the avalon folder in the spring folder.

AvalonBeanPostProcessor.java

This is a Spring BeanPostProcessor with added support for Avalon lifecycle interface.

There are some simple methods, such as setSettings(), setResourceLoader(), setLocation(), setBeanFactory(), setConfigurationInfo(), setContext(), to set some values.

Here are some important methods:

1.postProcessAfterInitialization():

After any bean initializes the callback, apply this BeanPostProcessor to the given new bean instance. The bean will already be populated with property values. The returned bean instance may be the wrapper of the original bean instance.

For FactoryBean, this callback function will be called on both the FactoryBean instance and the object created by FactoryBean. The post processor can check the corresponding FactoryBean instance to determine whether to apply to the FactoryBean, the created object, or both.

Compared with all other BeanPostProcessor callbacks, this callback will also be called after a short circuit triggered by the method.

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    try {
        ContainerUtil.start(bean);
    } catch (Exception e) {
        throw new BeanInitializationException("Unable to start bean " + beanName, e);
    }
    return bean;
}

2.postProcessBeforeInitialization():

Apply this BeanPostProcessor to the given new bean instance before any bean initializes the callback. The bean will already be populated with property values. The returned bean instance may be the wrapper of the original bean instance

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    final ComponentInfo info = (ComponentInfo) this.configurationInfo.getComponents().get(beanName);
    try {
        if (info == null) {
            // no info so we just return the bean and don't apply any
            // lifecycle interfaces
            return bean;
        }
        if (bean instanceof LogEnabled) {
            ContainerUtil.enableLogging(bean,
                                        new CLLoggerWrapper(LoggerUtils.getChildLogger(beanFactory, info.getLoggerCategory())));
        } else if (bean instanceof AbstractLogEnabled && info.getLoggerCategory() != null) {
            ((AbstractLogEnabled) bean).setLogger(LoggerUtils.getChildLogger(beanFactory, info.getLoggerCategory()));
        }
        ContainerUtil.contextualize(bean, this.context);
        ContainerUtil.service(bean, (ServiceManager) this.beanFactory.getBean(ServiceManager.class.getName()));
        Configuration config = info.getProcessedConfiguration();
        if (config == null) {
            config = info.getConfiguration();
            if (config == null) {
                config = EMPTY_CONFIG;
            } else {
                config = AvalonUtils.replaceProperties(config, this.settings);
            }
            info.setProcessedConfiguration(config);
        }
        if (bean instanceof Configurable) {
            ContainerUtil.configure(bean, config);
        } else if (bean instanceof Parameterizable) {
            Parameters p = info.getParameters();
            if (p == null) {
                p = Parameters.fromConfiguration(config);
                info.setParameters(p);
            }
            ContainerUtil.parameterize(bean, p);
        }
        ContainerUtil.initialize(bean);
    } catch (Exception e) {
        throw new BeanCreationException("Unable to initialize Avalon component with role " + beanName, e);
    }
    return bean;
}

3.postProcessBeforeDestruction():

Apply this BeanPostProcessor to a given bean instance before it is destroyed. You can call a custom destroy callback. This callback applies only to singleton beans (including internal beans) in the factory.

public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
    try {
        ContainerUtil.stop(bean);
    } catch (Exception e) {
        throw new BeanInitializationException("Unable to stop bean " + beanName, e);
    }
    ContainerUtil.dispose(bean);
}

AvalonContextFactoryBean.java

This factory bean sets the Avalon Context object. It is part of the Spring bridge for Avalon integration.

There are three important attributes:

//Avalon context
protected ServletContext servletContext;
//servlet context
protected Settings settings;
//set up
protected Context context;

There is a method that needs attention - init():

//Create Avalon context object.
protected void init()throws Exception {
    if ( this.settings == null ) {
        throw new IllegalArgumentException("Settings object is missing.");
    }
    //Create a new Avalon context
    final DefaultContext appContext = new ComponentContext();
    //Add environment context and configuration
    appContext.put(Constants.CONTEXT_ENVIRONMENT_CONTEXT, new HttpContext(this.servletContext));
    //Add Avalon context property contained in settings
    appContext.put(Constants.CONTEXT_WORK_DIR, new File(this.settings.getWorkDirectory()));
    appContext.put(Constants.CONTEXT_CACHE_DIR, new File(this.settings.getCacheDirectory()));
    appContext.put(Constants.CONTEXT_DEFAULT_ENCODING, this.settings.getFormEncoding());
    this.context = appContext;
}

AvalonUtils.java

This class contains some practical methods for dealing with Avalon.

1.replaceProperties():

Replace all properties in the configuration object.

public static Configuration replaceProperties(Configuration tree, Settings settings)
throws ConfigurationException {
    if (tree == null || settings == null) {
        return tree;
    }
    //First copy the tree
    final DefaultConfiguration root = new DefaultConfiguration(tree, true);
    //Then call replaceProperties method, replacing properties
    _replaceProperties(root, settings);
    return root;
}

2._replaceProperties():

Recursively replace the properties of the configuration object.

protected static void _replaceProperties(DefaultConfiguration config, Settings settings)
throws ConfigurationException {
    final String[] names = config.getAttributeNames();
    for (int i = 0; i < names.length; i++) {
        final String value = config.getAttribute(names[i]);
        config.setAttribute(names[i], PropertyHelper.replace(value, settings));
    }
    final String value = config.getValue(null);
    if (value != null) {
        config.setValue(PropertyHelper.replace(value, settings));
    }
    final Configuration[] children = config.getChildren();
    for (int m = 0; m < children.length; m++) {
        _replaceProperties((DefaultConfiguration) children[m], settings);
    }
}

ComponentContext.java

This is the context implementation of the Cocoon component. It extends the DefaultContext by obtaining special processing of objects from the object model and other application information. This class inherits from DefaultContext

Overloaded constructors, one with a final Context parent parameter and one without. The functions are to create a context with a specified parent and a context without a parent node.

There is a unique method, get() -- to retrieve the item from the context:

public Object get( final Object key )
throws ContextException {
    if ( ContextHelper.CONTEXT_OBJECT_MODEL.equals(key)) {
        final Environment env = EnvironmentHelper.getCurrentEnvironment();
        if ( env == null ) {
            throw new ContextException("Unable to locate " + key + " (No environment available)");
        }
        return env.getObjectModel();
    } else if ( ContextHelper.CONTEXT_SITEMAP_SERVICE_MANAGER.equals(key)) {
        final ServiceManager manager = EnvironmentHelper.getSitemapServiceManager();
        if ( manager == null ) {
            throw new ContextException("Unable to locate " + key + " (No environment available)");
        }
        return manager;
    }
    if ( key instanceof String ) {
        String stringKey = (String)key;
        if ( stringKey.startsWith(OBJECT_MODEL_KEY_PREFIX) ) {
            final Environment env = EnvironmentHelper.getCurrentEnvironment();
            if ( env == null ) {
                throw new ContextException("Unable to locate " + key + " (No environment available)");
            }
            final Map objectModel = env.getObjectModel();
            String objectKey = stringKey.substring(OBJECT_MODEL_KEY_PREFIX.length());

            Object o = objectModel.get( objectKey );
            if ( o == null ) {
                final String message = "Unable to locate " + key;
                throw new ContextException( message );
            }
            return o;
        }
    }
    return super.get( key );
}

ComponentInfo.java

Meta information about Avalon based components. This simple bean stores most of the information of the components defined in the Avalon based configuration file, such as the configuration of the component, its recorder, etc.

There are some properties that need attention in this class:

Attribute nameAttribute description
MODEL_POOLEDEach time a component of this type is requested, a new instance is created.
MODEL_PRIMITIVEOnly components of this type exist in each container.
MODEL_SINGLETONThe container creates a component pool for this type and services the requests in the pool.
MODEL_UNKNOWNThe model of the component is unknown. Reflection is later used to determine the model.

There is a method called copy that needs attention. As the name suggests, this method is used to create a new component with the same information as the current configuration.

public ComponentInfo copy() {
    final ComponentInfo info = new ComponentInfo();
    //The model of the component.
    info.model = this.model;
    //An optional method that is called by the container during initialization.
    info.initMethodName = this.initMethodName;
    //An optional method that is called by the container when it is destroyed.
    info.destroyMethodName = this.destroyMethodName;
    //An optional method called by the container when the component is put back into the pool.
    info.poolInMethodName = this.poolInMethodName;
    //An optional method that is called by the container when the component gets from the pool.
    info.poolOutMethodName = this.poolOutMethodName;
    //Class name of the component.
    info.componentClassName = this.componentClassName;
    //Component configuration.
    info.configuration = this.configuration;
    //The configuration of the component is used as a parameter
    info.parameters = this.parameters;
    //Optional logger category (relative to the category of the container).
    info.loggerCategory = this.loggerCategory;
    //The role of the component.
    info.role = this.role;
    //Alias for the component role.
    info.alias = this.alias;
    //Default component of selector
    info.defaultValue = this.defaultValue;
    info.lazyInit = this.lazyInit;
    return info;
}

ConfigurationReader.java

This component reads the Avalon style configuration file and returns all contained components and their configurations.

Some important attributes:

  • Logger -- logger
  • resolver -- the parser reads the configuration file
  • configInfo -- configuration information
  • componentConfigs - all component configurations
  • isRootContext -- the context used to determine whether it is a root

Some important methods:

1.configureRoles() -- read the configuration object and create role, shorthand and class name mappings.

protected final void configureRoles( final Configuration configuration )
throws ConfigurationException {
    final Configuration[] roles = configuration.getChildren();
    for (int i = 0; i < roles.length; i++) {
        final Configuration role = roles[i];

        if ("alias".equals(role.getName())) {
            final String roleName = role.getAttribute("role");
            final String shorthand = role.getAttribute("shorthand");
            this.configInfo.getShorthands().put(shorthand, roleName);
            continue;
        }
        if (!"role".equals(role.getName())) {
            throw new ConfigurationException("Unexpected '" + role.getName() + "' element at " + role.getLocation());
        }

        final String roleName = role.getAttribute("name");
        final String shorthand = role.getAttribute("shorthand", null);
        final String defaultClassName = role.getAttribute("default-class", null);

        if (shorthand != null) {
            // Store the shorthand and check that its consistent with any previous one
            Object previous = this.configInfo.getShorthands().put(shorthand, roleName);
            if (previous != null && !previous.equals(roleName)) {
                throw new ConfigurationException("Shorthand '" + shorthand + "' already used for role " +
                                                 previous + ": inconsistent declaration at " + role.getLocation());
            }
        }

        if (defaultClassName != null) {
            ComponentInfo info = this.configInfo.getRole(roleName);
            if (info == null) {
                // Create a new info and store it
                info = new ComponentInfo();
                info.setComponentClassName(defaultClassName);
                info.fill(role);
                info.setRole(roleName);
                info.setConfiguration(role);
                info.setAlias(shorthand);
                this.configInfo.addRole(roleName, info);
            } else {
                // Check that it's consistent with the existing info
                if (!defaultClassName.equals(info.getComponentClassName())) {
                    throw new ConfigurationException("Invalid redeclaration: default class already set to " + info.getComponentClassName() +
                                                     " for role " + roleName + " at " + role.getLocation());
                }
                //FIXME: should check also other ServiceInfo members
            }
        }

        final Configuration[] keys = role.getChildren("hint");
        if (keys.length > 0) {
            Map keyMap = (Map) this.configInfo.getKeyClassNames().get(roleName);
            if (keyMap == null) {
                keyMap = new HashMap();
                this.configInfo.getKeyClassNames().put(roleName, keyMap);
            }

            for (int j = 0; j < keys.length; j++) {
                Configuration key = keys[j];

                final String shortHand = key.getAttribute("shorthand").trim();
                final String className = key.getAttribute("class").trim();

                ComponentInfo info = (ComponentInfo) keyMap.get(shortHand);
                if (info == null) {
                    info = new ComponentInfo();
                    info.setComponentClassName(className);
                    info.fill(key);
                    info.setConfiguration(key);
                    info.setAlias(shortHand);
                    keyMap.put(shortHand, info);
                } else {
                    // Check that it's consistent with the existing info
                    if (!className.equals(info.getComponentClassName())) {
                        throw new ConfigurationException("Invalid redeclaration: class already set to " + info.getComponentClassName() +
                                                         " for hint " + shortHand + " at " + key.getLocation());
                    }
                    //FIXME: should check also other ServiceInfo members
                }
            }
        }
    }
}

2.convertUrl() -- convert an Avalon URL (using the possible cocoon protocol) into a spring url.

protected String convertUrl(String url) {
    if (url == null) {
        return null;
    }
    if (url.startsWith("context:")) {
        return url.substring(10);
    }
    if (url.startsWith("resource:")) {
        return "classpath:" + url.substring(10);
    }
    return url;
}

3.getInputSource() -- construct an input source from a given resource and initialize the system Id

protected InputSource getInputSource(Resource rsrc)
throws IOException {
    final InputSource is = new InputSource(rsrc.getInputStream());
    is.setSystemId(getUrl(rsrc));
    return is;
}

4.handleBeanInclude() -- handle spring bean configuration

protected void handleBeanInclude(final String contextURI,
                                 final Configuration includeStatement)
throws ConfigurationException {
    final String includeURI = includeStatement.getAttribute("src", null);
    String directoryURI = null;
    if (includeURI == null) {
        // check for directories
        directoryURI = includeStatement.getAttribute("dir", null);
    }
    if (includeURI == null && directoryURI == null) {
        throw new ConfigurationException(
                "Include statement must either have a 'src' or 'dir' attribute, at "
                        + includeStatement.getLocation());
    }

    if (includeURI != null) {
        try {
            Resource src = this.resolver.getResource(getUrl(includeURI, contextURI));
            this.configInfo.addImport(getUrl(src));
        } catch (Exception e) {
            throw new ConfigurationException("Cannot load '" + includeURI + "' at " +
                                             includeStatement.getLocation(), e);
        }

    } else {
        // test if directory exists
        Resource dirResource = this.resolver.getResource(this.getUrl(directoryURI, contextURI));
        if ( dirResource.exists() ) {
            final String pattern = includeStatement.getAttribute("pattern", null);
            try {
                Resource[] resources = this.resolver.getResources(this.getUrl(directoryURI + '/' + pattern, contextURI));
                if ( resources != null ) {
                    Arrays.sort(resources, ResourceUtils.getResourceComparator());
                    for(int i=0; i < resources.length; i++) {
                       this.configInfo.addImport(getUrl(resources[i]));
                    }
                }
            } catch (IOException ioe) {
                throw new ConfigurationException("Unable to read configurations from "
                        + directoryURI);
            }
        } else {
            if (!includeStatement.getAttributeAsBoolean("optional", false)) {
                throw new ConfigurationException("Directory '" + directoryURI + "' does not exist (" +
                                                 includeStatement.getLocation() + ").");
            }
        }
    }
}

5.handleInclude() -- processing avalon configuration

protected void handleInclude(final String        contextURI,
                             final Set           loadedURIs,
                             final Configuration includeStatement)
throws ConfigurationException {
    final String includeURI = includeStatement.getAttribute("src", null);
    String directoryURI = null;
    if (includeURI == null) {
        // check for directories
        directoryURI = includeStatement.getAttribute("dir", null);
    }
    if (includeURI == null && directoryURI == null) {
        throw new ConfigurationException("Include statement must either have a 'src' or 'dir' attribute, at " +
                                         includeStatement.getLocation());
    }

    if (includeURI != null) {
        try {
            Resource src = this.resolver.getResource(getUrl(includeURI, contextURI));
            loadURI(src, loadedURIs, includeStatement);
        } catch (Exception e) {
            throw new ConfigurationException("Cannot load '" + includeURI + "' at " +
                                             includeStatement.getLocation(), e);
        }

    } else {
        boolean load = true;
        // test if directory exists (only if not classpath protocol is used)
        if (!ResourceUtils.isClasspathUri(directoryURI)) {
            Resource dirResource = this.resolver.getResource(this.getUrl(directoryURI, contextURI));
            if (!dirResource.exists()) {
                if (!includeStatement.getAttributeAsBoolean("optional", false)) {
                    throw new ConfigurationException("Directory '" + directoryURI + "' does not exist (" + includeStatement.getLocation() + ").");
                }
                load = false;
            }
        }
        if (load) {
            final String pattern = includeStatement.getAttribute("pattern", null);
            try {
                Resource[] resources = this.resolver.getResources(this.getUrl(directoryURI + '/' + pattern, contextURI));
                if (resources != null) {
                    Arrays.sort(resources, ResourceUtils.getResourceComparator());
                    for (int i = 0; i < resources.length; i++) {
                        loadURI(resources[i], loadedURIs, includeStatement);
                    }
                }
            } catch (Exception e) {
                throw new ConfigurationException("Cannot load from directory '" + directoryURI + "' at " + includeStatement.getLocation(), e);
            }
        }
    }
}

Other documents

Name of the classClass description
AvalonNamespaceHandlerSpring namespace handler for the cocoon avalon namespace
AvalonServiceManagerThe function of this bean is similar to Avalon ServiceManager
AvalonServiceSelectorThe bean behaves like Avalon servicesselector.
AvalonSitemapContextFactoryBeanThis factory bean creates a context for the site map.
BridgeElementParserThis is the main implementation of Avalon spring bridge.
ConfigurationInfoThis bean stores information about the full Avalon style configuration.
ConfigurationInfoFactoryBeanThis spring factory bean adds configuration information to the bean factory.

In this blog, I only analyzed some contents in this folder, and the rest will be given in the next blog.

Topics: Java