/* * 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 org.apache.axis2.AxisFault; import org.apache.axis2.clustering.ClusteringAgent; import org.apache.axis2.clustering.state.Replicator; import org.apache.axis2.util.JavaUtils; import org.apache.axis2.util.OnDemandLogger; import org.apache.axis2.util.Utils; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; /** * This is the top most level of the Context hierarchy and is a bag of properties. */ public abstract class AbstractContext { private static final OnDemandLogger log = new OnDemandLogger(AbstractContext.class); private static final int DEFAULT_MAP_SIZE = 64; private static boolean DEBUG_ENABLED = log.isTraceEnabled(); private static boolean DEBUG_PROPERTY_SET = false; private boolean isClusteringOn = false; private boolean isClusteringCheckDone = 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(); while (true) { try { return new HashSet<String>(properties.keySet()).iterator(); } catch (Exception cme) { } } } /** * 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 if(!isClusteringCheckDone) { isClusteringCheckDone = true; isClusteringOn = needPropertyDifferences(); } if(isClusteringOn) { 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 { if(!isClusteringCheckDone) { isClusteringCheckDone = true; isClusteringOn = needPropertyDifferences(); } if(isClusteringOn) { // 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(); while (true) { try { properties.put(key, value); break; } catch (ConcurrentModificationException cme) { } } if(!isClusteringCheckDone) { isClusteringCheckDone = true; isClusteringOn = needPropertyDifferences(); } if(isClusteringOn) { addPropertyDifference(key, value, false); } if (DEBUG_ENABLED) { debugPropertySet(key, value); } } private void addPropertyDifference(String key, Object value, boolean isRemoved) { // 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 ClusteringAgent clusteringAgent = cc.getAxisConfiguration().getClusteringAgent(); if (clusteringAgent == null || clusteringAgent.getStateManager() == 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(); while (true) { try { properties.put(key, value); break; } catch (ConcurrentModificationException cme) { } } } /** * 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) { while (true) { try { properties.remove(key); break; } catch (ConcurrentModificationException cme) { } } } if(!isClusteringCheckDone) { isClusteringCheckDone = true; isClusteringOn = needPropertyDifferences(); } if(isClusteringOn) { 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) { while (true) { try { properties.remove(key); break; } catch (ConcurrentModificationException cme) { } } } } /** * 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. while (true) { try { this.properties = new HashMap(properties); break; } catch (ConcurrentModificationException cme) { } } } } } /** * 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); while (true) { try { properties.put(key, value); break; } catch (ConcurrentModificationException cme) { } } 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 HashMap<String,Object>(DEFAULT_MAP_SIZE); } } }