org.pepstock.jem.node.tasks.jndi.AbsoluteHashMap.java Source code

Java tutorial

Introduction

Here is the source code for org.pepstock.jem.node.tasks.jndi.AbsoluteHashMap.java

Source

/**
JEM, the BEE - Job Entry Manager, the Batch Execution Environment
Copyright (C) 2012-2015   Andrea "Stock" Stocchero
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
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 General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.pepstock.jem.node.tasks.jndi;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.codec.binary.Base64;
import org.pepstock.jem.log.LogAppl;
import org.pepstock.jem.log.MessageRuntimeException;
import org.pepstock.jem.node.NodeMessage;
import org.pepstock.jem.node.configuration.ConfigKeys;
import org.pepstock.jem.util.ReverseURLClassLoader;

/**
 * With this HashMap you can share the data of map across hierarchy of Classloaders.
 *  
 * @author Andrea "Stock" Stocchero
 * @version 1.0   
 *
 */
public final class AbsoluteHashMap extends HashMap<String, Object> implements Map<String, Object> {

    private static final long serialVersionUID = 1L;

    //  Common instance shared across a proxy
    private static Map<String, Object> instance = null;

    /**
     * Empty and private constructor
     */
    private AbsoluteHashMap() {
    }

    /**
     * This is a singleton. If instance is not null, means that local map is already loaded.
     * If null and classload is ANT classloader, then uses the proxy to load the instance from parent classloader, 
     * otherwise it creates a new instance.
     * @return shared HashMap.
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    static synchronized Map<String, Object> getInstance() {
        ClassLoader myClassLoader = AbsoluteHashMap.class.getClassLoader();
        if (instance == null) {
            // The root classloader is sun.misc.Launcher package. If we are not in a sun package,
            // we need to get hold of the instance of ourself from the class in the root classloader.
            // checks is ANT classloader
            if (myClassLoader.getClass().getName().startsWith("org.apache.tools.ant.loader.AntClassLoader")
                    || myClassLoader.getClass().getName().startsWith(ReverseURLClassLoader.class.getName())) {
                try {
                    // So we find our parent classloader
                    ClassLoader parentClassLoader = myClassLoader.getParent();
                    // And get the other version of our current class
                    Class otherClassInstance = parentClassLoader.loadClass(AbsoluteHashMap.class.getName());
                    // And call its getInstance method - this gives the correct instance of ourself
                    Method getInstanceMethod = otherClassInstance.getDeclaredMethod("getInstance",
                            new Class[] { String.class });
                    String internalKey = createKey();
                    Object otherAbsoluteSingleton = getInstanceMethod.invoke(null, new Object[] { internalKey });
                    // But, we can't cast it to our own interface directly because classes loaded from
                    // different classloaders implement different versions of an interface.
                    // So instead, we use java.lang.reflect.Proxy to wrap it in an object that
                    // supports our interface, and the proxy will use reflection to pass through all calls
                    // to the object.
                    instance = (Map<String, Object>) Proxy.newProxyInstance(myClassLoader,
                            new Class[] { Map.class }, new DelegateInvocationHandler(otherAbsoluteSingleton));
                } catch (SecurityException e) {
                    LogAppl.getInstance().debug(e.getMessage(), e);
                } catch (IllegalArgumentException e) {
                    LogAppl.getInstance().debug(e.getMessage(), e);
                } catch (ClassNotFoundException e) {
                    LogAppl.getInstance().debug(e.getMessage(), e);
                } catch (NoSuchMethodException e) {
                    LogAppl.getInstance().debug(e.getMessage(), e);
                } catch (IOException e) {
                    LogAppl.getInstance().debug(e.getMessage(), e);
                } catch (IllegalAccessException e) {
                    LogAppl.getInstance().debug(e.getMessage(), e);
                } catch (InvocationTargetException e) {
                    LogAppl.getInstance().debug(e.getMessage(), e);
                }
                // We're in the root classloader, so the instance we have here is the correct one
            } else {
                instance = new AbsoluteHashMap();
            }
        }
        return instance;
    }

    /**
     * THis method is call by proxy to get list of JNDI resources. The argument is InternalKey object, not 
     * visible outside of package, serialized and base64. The internal key must contain the output path
     * so we're sure that if someone is able to read the list of byte, is not able to use because output path
     * is related to job.
     *    
     * @param internalKeyString InternalKey objct, serialized and base 64
     * @return list of JNDI resources
     */
    public static synchronized Map<String, Object> getInstance(String internalKeyString) {
        try {
            // decodes internalKey
            byte[] internalKey = Base64.decodeBase64(internalKeyString);
            ByteArrayInputStream bais = new ByteArrayInputStream(internalKey);
            ObjectInputStream ois = new ObjectInputStream(bais);
            InternalKey key = (InternalKey) ois.readObject();
            // if keyValue must be the same of output path value
            if (key.getValue() == null) {
                throw new MessageRuntimeException(NodeMessage.JEMC236E);
            }
            if (!key.getValue().equalsIgnoreCase(System.getProperty(ConfigKeys.JEM_OUTPUT_PATH_NAME))) {
                throw new MessageRuntimeException(NodeMessage.JEMC236E);
            }
            ois.close();
        } catch (IOException e) {
            throw new MessageRuntimeException(NodeMessage.JEMC236E, e);
        } catch (ClassNotFoundException e) {
            throw new MessageRuntimeException(NodeMessage.JEMC236E, e);
        }
        return instance;
    }

    /**
     * create a InternalKey object, set the output path value, serializes in bytes and encodes in base64
     * 
     * @return a string which represent a internalKey
     * @throws IOException if any exception occurs during the serialization
     */
    private static String createKey() throws IOException {
        InternalKey key = new InternalKey();
        key.setValue(System.getProperty(ConfigKeys.JEM_OUTPUT_PATH_NAME));

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(key);
        oos.close();
        byte[] b = baos.toByteArray();
        return Base64.encodeBase64String(b);
    }

}