blob: fa4378693ed765f785394b5dbf35095bddcd4e5e [file] [log] [blame]
/*
* 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.
*/
package org.apache.axis2.context;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.axis2.AxisFault;
import org.apache.axis2.clustering.ClusterManager;
import org.apache.axis2.clustering.context.Replicator;
import org.apache.axis2.util.JavaUtils;
import org.apache.axis2.util.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This is the top most level of the Context hierarchy and is a bag of properties.
*/
public abstract class AbstractContext {
private static final Log log = LogFactory.getLog(AbstractContext.class);
private static final int DEFAULT_MAP_SIZE = 64;
private static boolean DEBUG_ENABLED = log.isTraceEnabled();
private static boolean DEBUG_PROPERTY_SET = false;
/**
* Property used to indicate copying of properties is needed by context.
*/
public static final String COPY_PROPERTIES = "CopyProperties";
protected long lastTouchedTime;
protected transient AbstractContext parent;
protected transient Map<String, Object> properties;
private transient Map<String, Object> propertyDifferences;
protected AbstractContext(AbstractContext parent) {
this.parent = parent;
}
protected AbstractContext() {
}
/**
* @return Returns the parent of this context.
*/
public AbstractContext getParent() {
return parent;
}
/**
* @param context
* @return true if the context is an ancestor
*/
public boolean isAncestor(AbstractContext context) {
if (context == null) {
return false;
}
for (AbstractContext ancestor = getParent();
ancestor != null;
ancestor = ancestor.getParent()) {
if (ancestor == context) {
return true;
}
}
return false;
}
/**
* @return The properties
* @deprecated Use {@link #getPropertyNames()}, {@link #getProperty(String)},
* {@link #setProperty(String, Object)} & {@link #removeProperty(String)}instead.
*/
public Map<String, Object> getProperties() {
initPropertiesMap();
return properties;
}
/**
* An iterator over a collection of <code>String</code> objects, which are the
* keys in the properties object.
*
* @return Iterator over a collection of keys
*/
public Iterator<String> getPropertyNames() {
initPropertiesMap();
HashSet<String> copyKeySet = null;
// Lock the table in a try/catch so it will always be unlocked in the finally block
try {
((HashMapUpdateLockable<String, Object>) properties).lockForUpdate();
copyKeySet = new HashSet<String>(properties.keySet());
} finally {
((HashMapUpdateLockable<String, Object>) properties).unlockForUpdate();
}
return (copyKeySet != null) ? copyKeySet.iterator() : null;
}
/**
* Retrieves an object given a key.
*
* @param key - if not found, will return null
* @return Returns the property.
*/
public Object getProperty(String key) {
Object obj = properties == null ? null : properties.get(key);
if (obj!=null) {
// Assume that a property which is read may be updated.
// i.e. The object pointed to by 'value' may be modified after it is read
addPropertyDifference(key, obj, false);
} else if (parent!=null) {
obj = parent.getProperty(key);
}
return obj;
}
/**
* Retrieves an object given a key. Only searches at this level
* i.e. getLocalProperty on MessageContext does not look in
* the OperationContext properties map if a local result is not
* found.
*
* @param key - if not found, will return null
* @return Returns the property.
*/
public Object getLocalProperty(String key) {
Object obj = properties == null ? null : properties.get(key);
if ((obj == null) && (parent != null)) {
// This is getLocalProperty() don't search the hierarchy.
} else {
// Assume that a property is which is read may be updated.
// i.e. The object pointed to by 'value' may be modified after it is read
addPropertyDifference(key, obj, false);
}
return obj;
}
/**
* Retrieves an object given a key. The retrieved property will not be replicated to
* other nodes in the clustered scenario.
*
* @param key - if not found, will return null
* @return Returns the property.
*/
public Object getPropertyNonReplicable(String key) {
Object obj = properties == null ? null : properties.get(key);
if ((obj == null) && (parent != null)) {
obj = parent.getPropertyNonReplicable(key);
}
return obj;
}
/**
* Store a property in this context
*
* @param key
* @param value
*/
public void setProperty(String key, Object value) {
initPropertiesMap();
properties.put(key, value);
addPropertyDifference(key, value, false);
if (DEBUG_ENABLED) {
debugPropertySet(key, value);
}
}
private void addPropertyDifference(String key, Object value, boolean isRemoved) {
if (!needPropertyDifferences()) {
return;
}
// Narrowed the synchronization so that we only wait
// if a property difference is added.
synchronized(this) {
// Lazizly create propertyDifferences map
if (propertyDifferences == null) {
propertyDifferences = new HashMap<String, Object>(DEFAULT_MAP_SIZE);
}
propertyDifferences.put(key, new PropertyDifference(key, value, isRemoved));
}
}
/**
* @return true if we need to store property differences for this
* context in this scenario.
*/
private boolean needPropertyDifferences() {
// Don't store property differences if there are no
// cluster members.
ConfigurationContext cc = getRootContext();
if (cc == null) {
return false;
}
// Add the property differences only if Context replication is enabled,
// and there are members in the cluster
ClusterManager clusterManager = cc.getAxisConfiguration().getClusterManager();
if (clusterManager == null ||
clusterManager.getContextManager() == null) {
return false;
}
return true;
}
/**
* Store a property in this context.
* But these properties should not be replicated when Axis2 is clustered.
*
* @param key
* @param value
*/
public void setNonReplicableProperty(String key, Object value) {
initPropertiesMap();
properties.put(key, value);
}
/**
* Remove a property. Only properties at this level will be removed.
* Properties of the parents cannot be removed using this method.
*
* @param key
*/
public synchronized void removeProperty(String key) {
if(properties == null){
return;
}
Object value = properties.get(key);
if (value != null) {
if (properties != null) {
properties.remove(key);
}
addPropertyDifference(key, value, true);
}
}
/**
* Remove a property. Only properties at this level will be removed.
* Properties of the parents cannot be removed using this method.
* The removal of the property will not be replicated when Axis2 is clustered.
*
* @param key
*/
public synchronized void removePropertyNonReplicable(String key) {
if (properties != null) {
properties.remove(key);
}
}
/**
* Get the property differences since the last transmission by the clustering
* mechanism
*
* @return The property differences
*/
public synchronized Map<String, Object> getPropertyDifferences() {
if (propertyDifferences == null) {
propertyDifferences = new HashMap<String, Object>(DEFAULT_MAP_SIZE);
}
return propertyDifferences;
}
/**
* Once the clustering mechanism transmits the property differences,
* it should call this method to avoid retransmitting stuff that has already
* been sent.
*/
public synchronized void clearPropertyDifferences() {
if (propertyDifferences != null) {
propertyDifferences.clear();
}
}
/**
* @param context
*/
public void setParent(AbstractContext context) {
parent = context;
}
/**
* This will set the properties to the context. But in setting that one may need to "copy" all
* the properties from the source properties to the target properties. To enable this we introduced
* a property ({@link #COPY_PROPERTIES}) so that if set to true, this code
* will copy the whole thing, without just referencing to the source.
*
* @param properties
*/
public void setProperties(Map<String, Object> properties) {
if (properties == null) {
this.properties = null;
} else {
Boolean copyProperties = ((Boolean) properties.get(COPY_PROPERTIES));
if ((copyProperties != null) && copyProperties.booleanValue()) {
mergeProperties(properties);
} else {
if (this.properties != properties) {
if (DEBUG_ENABLED) {
for (Iterator<Entry<String, Object>> iterator = properties.entrySet().iterator();
iterator.hasNext();) {
Entry<String, Object> entry = iterator.next();
debugPropertySet((String) entry.getKey(), entry.getValue());
}
}
}
// The Map we got argument is probably NOT an instance of the Concurrent
// map we use to store properties, so create a new one using the values from the
// argument map.
this.properties = new HashMapUpdateLockable<String, Object>(properties);
}
}
}
/**
* This will do a copy of the given properties to the current properties
* table.
*
* @param props The table of properties to copy
*/
public void mergeProperties(Map<String, Object> props) {
if (props != null) {
initPropertiesMap();
for (Iterator<String> iterator = props.keySet().iterator();
iterator.hasNext();) {
String key = iterator.next();
Object value = props.get(key);
this.properties.put(key, value);
if (DEBUG_ENABLED) {
debugPropertySet((String) key, value);
}
}
}
}
/**
* ServiceContext and ServiceGroupContext are not getting automatically garbage collected. And there
* is no specific way for some one to go and make it garbage collectible.
* So the current solution is to make them time out. So the logic is that, there is a timer task
* in each and every service group which will check for the last touched time. And if it has not
* been touched for some time, the timer task will remove it from the memory.
* The touching logic happens like this. Whenever there is a call to addMessageContext in the operationContext
* it will go and update operationCOntext -> serviceContext -> serviceGroupContext.
*/
protected void touch() {
lastTouchedTime = System.currentTimeMillis();
if (parent != null) {
parent.touch();
}
}
public long getLastTouchedTime() {
return lastTouchedTime;
}
public void setLastTouchedTime(long t) {
lastTouchedTime = t;
}
public void flush() throws AxisFault {
Replicator.replicate(this);
}
public abstract ConfigurationContext getRootContext();
/**
* Debug for for property key and value.
* @param key
* @param value
*/
private void debugPropertySet(String key, Object value) {
if (DEBUG_PROPERTY_SET) {
String className = (value == null) ? "null" : value.getClass().getName();
String classloader = "null";
if(value != null) {
ClassLoader cl = Utils.getObjectClassLoader(value);
if(cl != null) {
classloader = cl.toString();
}
}
String valueText = (value instanceof String) ? value.toString() : null;
String identity = getClass().getName() + '@' +
Integer.toHexString(System.identityHashCode(this));
log.debug("==================");
log.debug(" Property set on object " + identity);
log.debug(" Key =" + key);
if (valueText != null) {
log.debug(" Value =" + valueText);
}
log.debug(" Value Class = " + className);
log.debug(" Value Classloader = " + classloader);
log.debug( "Call Stack = " + JavaUtils.callStackToString());
log.debug("==================");
}
}
/**
* If the 'properties' map has not been allocated yet, then allocate it.
*/
private void initPropertiesMap() {
if (properties == null) {
// This needs to be a concurrent collection to prevent ConcurrentModificationExcpetions
// for async-on-the-wire. It was originally:
// properties = new HashMap(DEFAULT_MAP_SIZE);
properties = new HashMapUpdateLockable<String, Object>(DEFAULT_MAP_SIZE);
}
}
}
/**
* HashMap that supports an update lock. HashMap methods that would directly update the collection
* will block if the collection is locked. They will be released when the collection is unlocked.
* Methods that do not update the collection will not be blocked.
*/
class HashMapUpdateLockable<K, V> extends HashMap<K, V> {
// Used for synchronization during update locking. Also indicates if the table is locked.
// Initially, it is not locked.
private UpdateLock updateLock = new UpdateLock(false);
HashMapUpdateLockable() {
super();
}
HashMapUpdateLockable(int size) {
super(size);
}
HashMapUpdateLockable(Map<K, V> map) {
super(map);
}
/**
* Similar to super method but will block if collection is locked.
* @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
*/
public V put(K key, V value) {
checkUpdateLock(true);
return super.put(key, value);
}
/**
* Similar to super method but will block if collection is locked.
* @see java.util.HashMap#putAll(java.util.Map)
*/
public void putAll(Map<? extends K, ? extends V> map) {
checkUpdateLock(true);
super.putAll(map);
}
/**
* Similar to super method but will block if collection is locked.
* @see java.util.HashMap#remove(java.lang.Object)
*/
public V remove(Object key) {
checkUpdateLock(true);
return super.remove(key);
}
/**
* Lock the collection for update. NOTE: This should be called inside a try block and the
* unlock method should be called inside a finally block to ensure the lock is always
* released! If the collection is locked, methods that directly update the table will
* block until released by the unlock.
* @see #unlockForUpdate()
*/
void lockForUpdate() {
synchronized(updateLock) {
updateLock.setLock(true);
}
}
/**
* Unock the collection for update. NOTE: This should be called inside a finally block and the
* lock method should be called inside a try block to ensure the lock is always
* released! If the collection is locked, methods that directly update the table will
* block until released by this unlock.
* @see #lockForUpdate()
*/
void unlockForUpdate() {
synchronized(updateLock) {
updateLock.setLock(false);
updateLock.notifyAll();
}
}
/**
* Check if the update lock is currently held. Optionally block until the lock is released.
* @param wait If true, will block until the lock is released
* @return true if the collection is currently locked for update, false if it is not.
*/
boolean checkUpdateLock(boolean wait) {
boolean isLocked = false;
synchronized(updateLock) {
if (wait) {
while (updateLock.isLocked()) {
try {
updateLock.wait();
} catch (InterruptedException e) {
// Ignore the interrupt; recheck the lock and wait if appropriate.
}
}
}
isLocked = updateLock.isLocked();
}
return isLocked;
}
class UpdateLock {
private boolean isLocked = false;
UpdateLock(boolean isLocked) {
this.isLocked = isLocked;
}
void setLock(boolean lockValue) {
this.isLocked = lockValue;
}
boolean isLocked() {
return isLocked;
}
}
}