Java tutorial
/* * Copyright 2005-2015 the original author or authors. * * Licensed 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. */ package org.springframework.ldap.pool2.factory; import org.apache.commons.pool2.BaseKeyedPooledObjectFactory; import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.impl.DefaultPooledObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.DirContextProxy; import org.springframework.ldap.pool2.DirContextType; import org.springframework.ldap.pool2.FailureAwareContext; import org.springframework.ldap.pool2.validation.DirContextValidator; import org.springframework.ldap.support.LdapUtils; import org.springframework.util.Assert; import javax.naming.CommunicationException; import javax.naming.directory.DirContext; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * Factory that creates {@link DirContext} instances for pooling via a * configured {@link ContextSource}. The {@link DirContext}s are keyed based * on if they are read only or read/write. The expected key type is the * {@link org.springframework.ldap.pool2.DirContextType} enum. * * <br> * <br> * Configuration: <table border="1"> * <tr> * <th align="left">Property</th> * <th align="left">Description</th> * <th align="left">Required</th> * <th align="left">Default</th> * </tr> * <tr> * <td valign="top">contextSource</td> * <td valign="top"> The {@link ContextSource} to get {@link DirContext}s from * for adding to the pool. </td> * <td valign="top">Yes</td> * <td valign="top">null</td> * </tr> * <tr> * <td valign="top">dirContextValidator</td> * <td valign="top"> The {@link DirContextValidator} to use to validate * {@link DirContext}s. This is only required if the pool has validation of any * kind turned on. </td> * <td valign="top">No</td> * <td valign="top">null</td> * </tr> * </table> * * @since 2.0 * @author Eric Dalquist <a * href="mailto:eric.dalquist@doit.wisc.edu">eric.dalquist@doit.wisc.edu</a> * @author Mattias Hellborg Arthursson * @author Anindya Chatterjee */ class DirContextPooledObjectFactory extends BaseKeyedPooledObjectFactory<Object, Object> { /** * Logger for this class and subclasses */ protected final Logger logger = LoggerFactory.getLogger(this.getClass()); private static final Set<Class<? extends Throwable>> DEFAULT_NONTRANSIENT_EXCEPTIONS = new HashSet<Class<? extends Throwable>>(); static { DEFAULT_NONTRANSIENT_EXCEPTIONS.add(CommunicationException.class); }; private ContextSource contextSource; private DirContextValidator dirContextValidator; private Set<Class<? extends Throwable>> nonTransientExceptions = DEFAULT_NONTRANSIENT_EXCEPTIONS; void setNonTransientExceptions(Collection<Class<? extends Throwable>> nonTransientExceptions) { this.nonTransientExceptions = new HashSet<Class<? extends Throwable>>(nonTransientExceptions); } /** * @return the contextSource */ public ContextSource getContextSource() { return this.contextSource; } /** * @param contextSource * the contextSource to set */ public void setContextSource(ContextSource contextSource) { if (contextSource == null) { throw new IllegalArgumentException("contextSource may not be null"); } this.contextSource = contextSource; } /** * @return the dirContextValidator */ public DirContextValidator getDirContextValidator() { return this.dirContextValidator; } /** * @param dirContextValidator * the dirContextValidator to set */ public void setDirContextValidator(DirContextValidator dirContextValidator) { if (dirContextValidator == null) { throw new IllegalArgumentException("dirContextValidator may not be null"); } this.dirContextValidator = dirContextValidator; } private Object makeFailureAwareProxy(DirContext readOnlyContext) { return Proxy.newProxyInstance(DirContextProxy.class.getClassLoader(), new Class<?>[] { LdapUtils.getActualTargetClass(readOnlyContext), DirContextProxy.class, FailureAwareContext.class }, new FailureAwareContextProxy(readOnlyContext)); } /** * @see BaseKeyedPooledObjectFactory#validateObject(Object, PooledObject) * * */ @Override public boolean validateObject(Object key, PooledObject<Object> pooledObject) { Assert.notNull(this.dirContextValidator, "DirContextValidator may not be null"); Assert.isTrue(key instanceof DirContextType, "key must be a DirContextType"); Assert.notNull(pooledObject, "The Object to validate must not be null"); Assert.isTrue(pooledObject.getObject() instanceof DirContext, "The Object to validate must be of type '" + DirContext.class + "'"); try { final DirContextType contextType = (DirContextType) key; final DirContext dirContext = (DirContext) pooledObject.getObject(); return this.dirContextValidator.validateDirContext(contextType, dirContext); } catch (Exception e) { this.logger.warn( "Failed to validate '" + pooledObject.getObject() + "' due to an unexpected exception.", e); return false; } } /** * @see BaseKeyedPooledObjectFactory#destroyObject(Object, PooledObject) * * */ @Override public void destroyObject(Object key, PooledObject<Object> pooledObject) throws Exception { Assert.notNull(pooledObject, "The Object to destroy must not be null"); Assert.isTrue(pooledObject.getObject() instanceof DirContext, "The Object to destroy must be of type '" + DirContext.class + "'"); try { final DirContext dirContext = (DirContext) pooledObject.getObject(); if (this.logger.isDebugEnabled()) { this.logger.debug("Closing " + key + " DirContext='" + dirContext + "'"); } dirContext.close(); if (this.logger.isDebugEnabled()) { this.logger.debug("Closed " + key + " DirContext='" + dirContext + "'"); } } catch (Exception e) { this.logger.warn("An exception occured while closing '" + pooledObject.getObject() + "'", e); } } /** * @see BaseKeyedPooledObjectFactory#create(Object) * * */ @Override public Object create(Object key) throws Exception { Assert.notNull(this.contextSource, "ContextSource may not be null"); Assert.isTrue(key instanceof DirContextType, "key must be a DirContextType"); final DirContextType contextType = (DirContextType) key; if (this.logger.isDebugEnabled()) { this.logger.debug("Creating a new " + contextType + " DirContext"); } if (contextType == DirContextType.READ_WRITE) { final DirContext readWriteContext = this.contextSource.getReadWriteContext(); if (this.logger.isDebugEnabled()) { this.logger.debug( "Created new " + DirContextType.READ_WRITE + " DirContext='" + readWriteContext + "'"); } return makeFailureAwareProxy(readWriteContext); } else if (contextType == DirContextType.READ_ONLY) { final DirContext readOnlyContext = this.contextSource.getReadOnlyContext(); if (this.logger.isDebugEnabled()) { this.logger .debug("Created new " + DirContextType.READ_ONLY + " DirContext='" + readOnlyContext + "'"); } return makeFailureAwareProxy(readOnlyContext); } else { throw new IllegalArgumentException("Unrecognized ContextType: " + contextType); } } /** * @see BaseKeyedPooledObjectFactory#wrap(Object) * * */ @Override public PooledObject<Object> wrap(Object value) { return new DefaultPooledObject<Object>(value); } /** * Invocation handler that checks thrown exceptions against the configured {@link #nonTransientExceptions}, * marking the Context as invalid on match. * * @author Mattias Hellborg Arthursson * @since 2.0 */ private class FailureAwareContextProxy implements InvocationHandler { private DirContext target; private boolean hasFailed = false; public FailureAwareContextProxy(DirContext target) { Assert.notNull(target, "Target must not be null"); this.target = target; } /* * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, * java.lang.reflect.Method, java.lang.Object[]) */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.equals("getTargetContext")) { return target; } else if (methodName.equals("hasFailed")) { return hasFailed; } try { return method.invoke(target, args); } catch (InvocationTargetException e) { Throwable targetException = e.getTargetException(); Class<? extends Throwable> targetExceptionClass = targetException.getClass(); boolean nonTransientEncountered = false; for (Class<? extends Throwable> clazz : nonTransientExceptions) { if (clazz.isAssignableFrom(targetExceptionClass)) { logger.info(String.format( "An %s - explicitly configured to be a non-transient exception - encountered; eagerly invalidating the target context.", targetExceptionClass)); nonTransientEncountered = true; break; } } if (nonTransientEncountered) { hasFailed = true; } else { if (logger.isDebugEnabled()) { logger.debug(String.format( "An %s - not explicitly configured to be a non-transient exception - encountered; ignoring.", targetExceptionClass)); } } throw targetException; } } } }