DefaultParserService.java

package org.apache.fulcrum.parser;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;

import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.logger.LogEnabled;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.fulcrum.parser.ValueParser.URLCaseFolding;
import org.apache.fulcrum.parser.pool.BaseValueParserFactory;
import org.apache.fulcrum.parser.pool.BaseValueParserPool;
import org.apache.fulcrum.parser.pool.CookieParserFactory;
import org.apache.fulcrum.parser.pool.CookieParserPool;
import org.apache.fulcrum.parser.pool.DefaultParameterParserFactory;
import org.apache.fulcrum.parser.pool.DefaultParameterParserPool;
import org.apache.fulcrum.pool.PoolException;
import org.apache.fulcrum.pool.PoolService;


/**
 * The DefaultParserService provides the default implementation
 * of a {@link ParserService}.
 *
 * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
 * @version $Id: BaseValueParser.java 542062 2007-05-28 00:29:43Z seade $
 */
public class DefaultParserService
    extends AbstractLogEnabled
    implements ParserService,
               Configurable, Serviceable
{

    /** The folding from the configuration */
    private URLCaseFolding folding = URLCaseFolding.NONE;

    /** The automaticUpload setting from the configuration */
    private boolean automaticUpload = AUTOMATIC_DEFAULT;

    /**
     * The parameter encoding to use when parsing parameter strings
     */
    private String parameterEncoding = PARAMETER_ENCODING_DEFAULT;
    
    /**
     * reintroduced fulcrum, may be used in the case, that 
     * commons pool2 is not configured exactly as needed properly as fast fall back.
     */
    private boolean useFulcrumPool = FULCRUM_POOL_DEFAULT;
    
    
    /**
     * The Fulcrum pool service component to use (optional), by default it is deactivated and commons pool is used.
     */
    private PoolService fulcrumPoolService = null;

    /** 
     * Use commons pool to manage value parsers 
     */
    private BaseValueParserPool valueParserPool;

    /** 
     * Use commons pool to manage parameter parsers 
     */
    private DefaultParameterParserPool parameterParserPool;

    /** 
     * Use commons pool to manage cookie parsers 
     */
    private CookieParserPool cookieParserPool;


    public DefaultParserService() 
    {
    }
    
    public DefaultParserService(GenericObjectPoolConfig<?> config) 
    {
	    // init the pool
	    valueParserPool 
    		= new BaseValueParserPool(new BaseValueParserFactory(), config);

	    parameterParserPool 
	    	= new DefaultParameterParserPool(new DefaultParameterParserFactory(), config);
    }

    
    /**
     * Get the character encoding that will be used by this ValueParser.
     */
    @Override
    public String getParameterEncoding()
    {
        return parameterEncoding;
    }
    
    /**
     * Set the character encoding that will be used by this ValueParser.
     */
    public void setParameterEncoding(String encoding)
    {
        parameterEncoding = encoding;
    }

    /**
     * Trims the string data and applies the conversion specified in
     * the property given by URL_CASE_FOLDING.  It returns a new
     * string so that it does not destroy the value data.
     *
     * @param value A String to be processed.
     * @return A new String converted to the case as specified by URL_CASE_FOLDING and trimmed.
     */
    @Override
    public String convert(String value)
    {
        return convertAndTrim(value);
    }

    /**
     * Convert a String value according to the url-case-folding property.
     *
     * @param value the String to convert
     *
     * @return a new String.
     *
     */
    @Override
    public String convertAndTrim(String value)
    {
        return convertAndTrim(value, getUrlFolding());
    }

    /**
     * A static version of the convert method, which
     * trims the string data and applies the conversion specified in
     * the property given by URL_CASE_FOLDING.  It returns a new
     * string so that it does not destroy the value data.
     *
     * @param value A String to be processed.
     * @return A new String converted to lowercase and trimmed.
     */
    @Override
    public String convertAndTrim(String value, URLCaseFolding fold)
    {
        if (value == null)
        {
            return "";
        }

        String tmp = value.trim();

        switch (fold)
        {
            case NONE:
            {
                break;
            }

            case LOWER:
            {
                tmp = tmp.toLowerCase();
                break;
            }

            case UPPER:
            {
                tmp = tmp.toUpperCase();
                break;
            }

            default:
            {
                getLogger().error("Passed " + fold + " as fold rule, which is illegal!");
                break;
            }
        }
        return tmp;
    }

    /**
     * Gets the folding value from the configuration
     *
     * @return The current Folding Value
     */
    @Override
    public URLCaseFolding getUrlFolding()
    {
        return folding;
    }

    /**
     * Gets the automaticUpload value from the configuration
     *
     * @return The current automaticUpload Value
     */
    @Override
    public boolean getAutomaticUpload()
    {
        return automaticUpload;
    }

    /**
     * Parse the given request for uploaded files
     *
     * @return A list of {@link javax.servlet.http.Part}s
     *
     * @throws ServiceException if parsing fails
     */
    @Override
    public List<Part> parseUpload(HttpServletRequest request) throws ServiceException
    {
        try
        {
            return new ArrayList<Part>(request.getParts());
        }
        catch (IOException | ServletException e)
        {
            throw new ServiceException(ParserService.ROLE, "Could not parse upload request", e);
        }
    }

    /**
     * Get a {@link ValueParser} instance from the service. Use the
     * given Class to create the object.
     *
     * @return An object that implements ValueParser
     *
     * @throws InstantiationException if the instance could not be created
     */
    @SuppressWarnings("unchecked")
	@Override
    public <P extends ValueParser> P getParser(Class<P> ppClass) throws InstantiationException
    {
        P vp = null;

        try
        {
            if (useFulcrumPool) {
                try
                {
                    P parserInstance = (P) fulcrumPoolService.getInstance(ppClass);
                    vp = parserInstance;
                }
                catch (PoolException pe)
                {
                    throw new InstantiationException("Parser class '" + ppClass + "' is illegal. " + pe.getMessage());
                }
            } else if ( ppClass.equals(BaseValueParser.class) )
            {
            	BaseValueParser parserInstance = null;
				try {
				    parserInstance = valueParserPool.borrowObject();
					vp = (P) parserInstance;
					if (vp == null) {
                        throw new InstantiationException("Could not borrow object from pool: " + valueParserPool);
                    }
				} catch (Exception e) {
                    try {
                        valueParserPool.invalidateObject(parserInstance);
                        parserInstance = null;
                    } catch (Exception e1) {
                        throw new InstantiationException("Could not invalidate object " + e1.getMessage() + " after exception: " + e.getMessage());
                    }
				}
            } else if ( ppClass.equals(DefaultParameterParser.class) )
            {
                DefaultParameterParser parserInstance = null;
                try {
                    parserInstance = parameterParserPool.borrowObject();
                	vp = (P) parserInstance;
                	if (vp == null) {
                        throw new InstantiationException("Could not borrow object from pool: " + parameterParserPool);
                    }
                } catch (Exception e) {
                    try {
                        parameterParserPool.invalidateObject(parserInstance);
                        parserInstance = null;
                    } catch (Exception e1) {
                        throw new InstantiationException("Could not invalidate object " + e1.getMessage() + " after exception: " + e.getMessage());
                    }
                }
            } else if ( ppClass.equals(DefaultCookieParser.class) )
            {
                DefaultCookieParser parserInstance = null;
                try {
                    parserInstance = cookieParserPool.borrowObject();
                	vp = (P) parserInstance;
                    if (vp == null) {
                          throw new InstantiationException("Could not borrow object from pool: " + cookieParserPool);
                    }
                } catch (Exception e) {
                    try {
                        cookieParserPool.invalidateObject(parserInstance);
                        parserInstance = null;
                    } catch (Exception e1) {
                        throw new InstantiationException("Could not invalidate object " + e1.getMessage() + " after exception: " + e.getMessage());
                    }
                }
            }
            
            if (vp != null && vp instanceof ParserServiceSupport ) {
                ((ParserServiceSupport)vp).setParserService(this);
            } else {
                throw new InstantiationException("Could not set parser");
            }
            if (vp instanceof LogEnabled) 
            {
                ((LogEnabled)vp).enableLogging(getLogger().getChildLogger(ppClass.getSimpleName()));
            }
        }
        catch (ClassCastException x)
        {
            throw new InstantiationException("Parser class '" + ppClass + "' is illegal. " + x.getMessage());
        }

        return vp;
    }

    /**
     * Clears the parse and puts it back into
     * the pool service. This allows for pooling 
     * and recycling
     * 
     * As we are not yet using org.apache.fulcrum.pool.Recyclable, we call insteda {@link ValueParser#dispose()}.
     *
     * @param parser The value parser to use
     */
    @Override
    public void putParser(ValueParser parser)
    {
        parser.clear();
        parser.dispose(); 
    
        if (useFulcrumPool) {
            
            fulcrumPoolService.putInstance(parser);
            
        } else if( parser.getClass().equals(BaseValueParser.class) )
        {
            valueParserPool.returnObject( (BaseValueParser) parser );
        
        } else if ( parser.getClass().equals(DefaultParameterParser.class) ||
                parser instanceof DefaultParameterParser)
        {
            parameterParserPool.returnObject( (DefaultParameterParser) parser );
        	
        } else if ( parser.getClass().equals(DefaultCookieParser.class) ||
                parser instanceof DefaultCookieParser)
        {
            cookieParserPool.returnObject( (DefaultCookieParser) parser );
        	
        } else {
            // log
            getLogger().warn(parser.getClass() + " could not be put back into any pool exhausting some pool");
            // log even borrowed count of each pool?: cookieParserPool.getBorrowedCount())
            }
    }

    /**
     * Avalon component lifecycle method
     * 
     * @param conf the configuration
     * @throws ConfigurationException Generic exception
     */
    @Override
    public void configure(Configuration conf) throws ConfigurationException
    {
        String foldString = conf.getChild(URL_CASE_FOLDING_KEY).getValue(URLCaseFolding.NONE.name()).toLowerCase();

        folding = URLCaseFolding.NONE;

        getLogger().debug("Setting folding from " + foldString);

        if (StringUtils.isNotEmpty(foldString))
        {
            try
            {
                folding = URLCaseFolding.valueOf(foldString.toUpperCase());
            }
            catch (IllegalArgumentException e)
            {
                getLogger().error("Got " + foldString + " from " + URL_CASE_FOLDING_KEY + " property, which is illegal!");
                throw new ConfigurationException("Value " + foldString + " is illegal!", e);
            }
        }

        parameterEncoding = conf.getChild(PARAMETER_ENCODING_KEY)
                            .getValue(PARAMETER_ENCODING_DEFAULT).toLowerCase();

        automaticUpload = conf.getChild(AUTOMATIC_KEY).getValueAsBoolean(AUTOMATIC_DEFAULT);
        
        useFulcrumPool = conf.getChild(FULCRUM_POOL_KEY).getValueAsBoolean(FULCRUM_POOL_DEFAULT);
        
        if (useFulcrumPool) {
            if (fulcrumPoolService == null) 
            {
                    // only for fulcrum  pool, need to call internal service, if role pool service is set
                    throw new ConfigurationException("Fulcrum Pool is activated", new ServiceException(ParserService.ROLE,
                            "Fulcrum enabled Pool Service requires " +
                            PoolService.ROLE + " to be available"));
            }
            getLogger().info("Using Fulcrum Pool Service: "+ fulcrumPoolService);
        } else {
            // reset not used fulcrum pool
            fulcrumPoolService = null;
            
            // Define the default configuration
            GenericObjectPoolConfig config = new GenericObjectPoolConfig();
            config.setMaxIdle(DEFAULT_MAX_IDLE);
            config.setMaxTotal(DEFAULT_POOL_CAPACITY);

            // init the pool
            valueParserPool 
                = new BaseValueParserPool(new BaseValueParserFactory(), config);

            // init the pool
            parameterParserPool 
                = new DefaultParameterParserPool(new DefaultParameterParserFactory(), config);
            
            // init the pool
            cookieParserPool 
                = new CookieParserPool(new CookieParserFactory(), config);
            
            getLogger().info("Init Commons2 Pool Services.." );
            getLogger().info(valueParserPool.getClass().getName());
            getLogger().info(parameterParserPool.getClass().getName());
            getLogger().info(cookieParserPool.getClass().getName());
        }
        
        Configuration[] poolChildren = conf.getChild(POOL_KEY).getChildren();
        if (poolChildren.length > 0) {
            GenericObjectPoolConfig genObjPoolConfig = new GenericObjectPoolConfig();
            genObjPoolConfig.setMaxIdle(DEFAULT_MAX_IDLE);
            genObjPoolConfig.setMaxTotal(DEFAULT_POOL_CAPACITY);
            for (Configuration poolConf : poolChildren) {
                // use common pool2 configuration names
                switch (poolConf.getName()) {
                case "maxTotal":
                    int defaultCapacity = poolConf.getValueAsInteger();
                    genObjPoolConfig.setMaxTotal(defaultCapacity);
                    break;
                case "maxWaitMillis":
                    int maxWaitMillis = poolConf.getValueAsInteger();
                    genObjPoolConfig.setMaxWaitMillis(maxWaitMillis);
                    break;
                case "blockWhenExhausted":
                    boolean blockWhenExhausted = poolConf.getValueAsBoolean();
                    genObjPoolConfig.setBlockWhenExhausted(blockWhenExhausted);
                    break;
                case "maxIdle":
                    int maxIdle = poolConf.getValueAsInteger();
                    genObjPoolConfig.setMaxIdle(maxIdle);
                    break;
                case "minIdle":
                    int minIdle = poolConf.getValueAsInteger();
                    genObjPoolConfig.setMinIdle(minIdle);
                    break;
                case "testOnReturn":
                    boolean testOnReturn = poolConf.getValueAsBoolean();
                    genObjPoolConfig.setTestOnReturn(testOnReturn);
                    break;
                case "testOnBorrow":
                    boolean testOnBorrow = poolConf.getValueAsBoolean();
                    genObjPoolConfig.setTestOnBorrow(testOnBorrow);
                    break;
                case "testOnCreate":
                    boolean testOnCreate = poolConf.getValueAsBoolean();
                    genObjPoolConfig.setTestOnCreate(testOnCreate);
                    break;
                default:
                    
                    break;
                }  
            }    
            // reinit the pools
            valueParserPool.setConfig(genObjPoolConfig);
            parameterParserPool.setConfig(genObjPoolConfig);
            cookieParserPool.setConfig(genObjPoolConfig);
            
            getLogger().debug(valueParserPool.toString());
            getLogger().debug(parameterParserPool.toString());
            getLogger().debug(cookieParserPool.toString());
        }
                
    }

    // ---------------- Avalon Lifecycle Methods ---------------------
    /**
     * Avalon component lifecycle method
     * 
     * @param manager The service manager instance
     * @throws ServiceException generic exception
     * 
     */
    @Override
    public void service(ServiceManager manager) throws ServiceException
    {
        // only for fulcrum  pool, need to call internal service, if role pool service is set
        if (manager.hasService(PoolService.ROLE))
        {
            fulcrumPoolService = (PoolService)manager.lookup(PoolService.ROLE);
        } 
        // no check here, until configuration is read
    }
}