/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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.apereo.portal.spring.web.context.support; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.Scope; /** * Wraps a {@link Scope} to provide functionality when the wrapped scope can't function due to an * illegal state (no current session, request, ...) * */ public class FailsafeSingletonScope implements Scope, DisposableBean { protected final Log logger = LogFactory.getLog(this.getClass()); private final Map<String, InstanceHolder> instances = new HashMap<String, InstanceHolder>(); private final Scope delegateScope; public FailsafeSingletonScope(Scope delegateScope) { this.delegateScope = delegateScope; } /* (non-Javadoc) * @see org.springframework.beans.factory.DisposableBean#destroy() */ public void destroy() throws Exception { for (final InstanceHolder instanceHolder : this.instances.values()) { if (instanceHolder.destructionCallback != null) { try { instanceHolder.destructionCallback.run(); } catch (Exception e) { this.logger.warn( "Destruction callback for bean named '" + instanceHolder.name + "' failed.", e); } } } this.instances.clear(); } /** * @see org.springframework.web.context.request.SessionScope#get(java.lang.String, * org.springframework.beans.factory.ObjectFactory) */ public Object get(String name, ObjectFactory<?> objectFactory) { try { return this.delegateScope.get(name, objectFactory); } catch (IllegalStateException ise) { synchronized (this.instances) { InstanceHolder instanceHolder = this.instances.get(name); if (instanceHolder == null) { if (this.logger.isDebugEnabled()) { this.logger.debug("Creating singleton instance for bean '" + name + "'"); } //Add to instances map before creating to ensure if a destruction callback is added it is caught instanceHolder = new InstanceHolder(name); this.instances.put(name, instanceHolder); instanceHolder.instance = objectFactory.getObject(); } else if (this.logger.isDebugEnabled()) { this.logger.debug("Using existing singleton instance for bean '" + name + "'"); } return instanceHolder.instance; } } } /** @see org.springframework.web.context.request.SessionScope#getConversationId() */ public String getConversationId() { try { return this.delegateScope.getConversationId(); } catch (IllegalStateException ise) { return "NO_SESSION_SINGLETON"; } } /** * @see * org.springframework.web.context.request.AbstractRequestAttributesScope#registerDestructionCallback(java.lang.String, * java.lang.Runnable) */ public void registerDestructionCallback(String name, Runnable callback) { try { this.delegateScope.registerDestructionCallback(name, callback); } catch (IllegalStateException ise) { final InstanceHolder instanceHolder; synchronized (this.instances) { instanceHolder = this.instances.get(name); } if (instanceHolder != null) { if (this.logger.isInfoEnabled()) { this.logger.info( "Adding destruction callback singleton for bean '" + name + "'"); } instanceHolder.destructionCallback = callback; } else if (this.logger.isInfoEnabled()) { this.logger.info( "Ignoring destruction callback for singleton bean '" + name + "' because there currently is no instance"); } } } /** @see org.springframework.web.context.request.SessionScope#remove(java.lang.String) */ public Object remove(String name) { try { return this.delegateScope.remove(name); } catch (IllegalStateException ise) { final InstanceHolder instanceHolder; synchronized (this.instances) { instanceHolder = this.instances.remove(name); } if (instanceHolder != null) { if (this.logger.isInfoEnabled()) { this.logger.info("Removing singleton bean '" + name + "'"); } return instanceHolder.instance; } return null; } } /** Holder class for singleton instances */ private static class InstanceHolder { public final String name; public Object instance; public Runnable destructionCallback; public InstanceHolder(String name) { this.name = name; } /** @see java.lang.Object#equals(Object) */ @Override public boolean equals(Object object) { if (object == this) { return true; } if (!(object instanceof InstanceHolder)) { return false; } InstanceHolder rhs = (InstanceHolder) object; return new EqualsBuilder().append(this.name, rhs.name).isEquals(); } /** @see java.lang.Object#hashCode() */ @Override public int hashCode() { return new HashCodeBuilder(217891979, 1307635269).append(this.name).toHashCode(); } /** @see java.lang.Object#toString() */ @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("name", this.name) .append("instance", this.instance) .append("destructionCallback", this.destructionCallback) .toString(); } } @Override public Object resolveContextualObject(String arg0) { // TODO Auto-generated method stub return null; } }