You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-dev@axis.apache.org by di...@apache.org on 2003/07/13 00:13:01 UTC
cvs commit: xml-axis/java/src/org/apache/axis/components/threadpool ThreadPool.java
dims 2003/07/12 15:13:01
Modified: java/src/org/apache/axis/i18n resource.properties
java/src/org/apache/axis/transport/http
SimpleAxisServer.java SimpleAxisWorker.java
java/src/org/apache/axis/components/threadpool
ThreadPool.java
Added: java/src/org/apache/axis/collections LRUMap.java
SequencedHashMap.java
Log:
Fix for Bug 20260 - SimpleAxisServer's bugs
Notes:
- checking in re-worked patch from from taherkordy@dpi2.dpi.net.ir (Alireza Taherkordi)
- Reuse our ThreadPool
- Checking in LRUMap and SequencedHashMap from jakarta-commons to avoid introducing a new dependency.
Revision Changes Path
1.1 xml-axis/java/src/org/apache/axis/collections/LRUMap.java
Index: LRUMap.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.axis.collections;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Iterator;
/**
* <p>
* An implementation of a Map which has a maximum size and uses a Least Recently Used
* algorithm to remove items from the Map when the maximum size is reached and new items are added.
* </p>
*
* <p>
* A synchronized version can be obtained with:
* <code>Collections.synchronizedMap( theMapToSynchronize )</code>
* If it will be accessed by multiple threads, you _must_ synchronize access
* to this Map. Even concurrent get(Object) operations produce indeterminate
* behaviour.
* </p>
*
* <p>
* Unlike the Collections 1.0 version, this version of LRUMap does use a true
* LRU algorithm. The keys for all gets and puts are moved to the front of
* the list. LRUMap is now a subclass of SequencedHashMap, and the "LRU"
* key is now equivalent to LRUMap.getFirst().
* </p>
*
* @since Commons Collections 1.0
* @version $Revision: 1.1 $ $Date: 2003/07/12 22:13:00 $
*
* @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
* @author <a href="mailto:morgand@apache.org">Morgan Delagrange</a>
*/
public class LRUMap extends SequencedHashMap implements Externalizable {
private int maximumSize = 0;
/**
* Default constructor, primarily for the purpose of
* de-externalization. This constructors sets a default
* LRU limit of 100 keys, but this value may be overridden
* internally as a result of de-externalization.
*/
public LRUMap() {
this( 100 );
}
/**
* Create a new LRUMap with a maximum capacity of <i>i</i>.
* Once <i>i</i> capacity is achieved, subsequent gets
* and puts will push keys out of the map. See .
*
* @param i Maximum capacity of the LRUMap
*/
public LRUMap(int i) {
super( i );
maximumSize = i;
}
/**
* <p>Get the value for a key from the Map. The key
* will be promoted to the Most Recently Used position.
* Note that get(Object) operations will modify
* the underlying Collection. Calling get(Object)
* inside of an iteration over keys, values, etc. is
* currently unsupported.</p>
*
* @param key Key to retrieve
* @return Returns the value. Returns null if the key has a
* null value <i>or</i> if the key has no value.
*/
public Object get(Object key) {
if(!containsKey(key)) return null;
Object value = remove(key);
super.put(key,value);
return value;
}
/**
* <p>Removes the key and its Object from the Map.</p>
*
* <p>(Note: this may result in the "Least Recently Used"
* object being removed from the Map. In that case,
* the removeLRU() method is called. See javadoc for
* removeLRU() for more details.)</p>
*
* @param key Key of the Object to add.
* @param value Object to add
* @return Former value of the key
*/
public Object put( Object key, Object value ) {
int mapSize = size();
Object retval = null;
if ( mapSize >= maximumSize ) {
// don't retire LRU if you are just
// updating an existing key
if (!containsKey(key)) {
// lets retire the least recently used item in the cache
removeLRU();
}
}
retval = super.put(key,value);
return retval;
}
/**
* This method is used internally by the class for
* finding and removing the LRU Object.
*/
protected void removeLRU() {
Object key = getFirstKey();
// be sure to call super.get(key), or you're likely to
// get infinite promotion recursion
Object value = super.get(key);
remove(key);
processRemovedLRU(key,value);
}
/**
* Subclasses of LRUMap may hook into this method to
* provide specialized actions whenever an Object is
* automatically removed from the cache. By default,
* this method does nothing.
*
* @param key key that was removed
* @param value value of that key (can be null)
*/
protected void processRemovedLRU(Object key, Object value) {
}
// Externalizable interface
//-------------------------------------------------------------------------
public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException {
maximumSize = in.readInt();
int size = in.readInt();
for( int i = 0; i < size; i++ ) {
Object key = in.readObject();
Object value = in.readObject();
put(key,value);
}
}
public void writeExternal( ObjectOutput out ) throws IOException {
out.writeInt( maximumSize );
out.writeInt( size() );
for( Iterator iterator = keySet().iterator(); iterator.hasNext(); ) {
Object key = iterator.next();
out.writeObject( key );
// be sure to call super.get(key), or you're likely to
// get infinite promotion recursion
Object value = super.get( key );
out.writeObject( value );
}
}
// Properties
//-------------------------------------------------------------------------
/** Getter for property maximumSize.
* @return Value of property maximumSize.
*/
public int getMaximumSize() {
return maximumSize;
}
/** Setter for property maximumSize.
* @param maximumSize New value of property maximumSize.
*/
public void setMaximumSize(int maximumSize) {
this.maximumSize = maximumSize;
while (size() > maximumSize) {
removeLRU();
}
}
// add a serial version uid, so that if we change things in the future
// without changing the format, we can still deserialize properly.
private static final long serialVersionUID = 2197433140769957051L;
}
1.1 xml-axis/java/src/org/apache/axis/collections/SequencedHashMap.java
Index: SequencedHashMap.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2002-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.axis.collections;
import org.apache.axis.i18n.Messages;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A map of objects whose mapping entries are sequenced based on the order in
* which they were added. This data structure has fast <i>O(1)</i> search
* time, deletion time, and insertion time.
*
* <p>Although this map is sequenced, it cannot implement {@link
* java.util.List} because of incompatible interface definitions. The remove
* methods in List and Map have different return values (see: {@link
* java.util.List#remove(Object)} and {@link java.util.Map#remove(Object)}).
*
* <p>This class is not thread safe. When a thread safe implementation is
* required, use {@link Collections#synchronizedMap(Map)} as it is documented,
* or use explicit synchronization controls.
*
* @since Commons Collections 2.0
* @version $Revision: 1.1 $ $Date: 2003/07/12 22:13:00 $
*
* @author <a href="mailto:mas@apache.org">Michael A. Smith</A>
* @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
* @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
* @author Stephen Colebourne
*/
public class SequencedHashMap implements Map, Cloneable, Externalizable {
/**
* {@link java.util.Map.Entry} that doubles as a node in the linked list
* of sequenced mappings.
**/
private static class Entry implements Map.Entry {
// Note: This class cannot easily be made clonable. While the actual
// implementation of a clone would be simple, defining the semantics is
// difficult. If a shallow clone is implemented, then entry.next.prev !=
// entry, which is unintuitive and probably breaks all sorts of assumptions
// in code that uses this implementation. If a deep clone is
// implementated, then what happens when the linked list is cyclical (as is
// the case with SequencedHashMap)? It's impossible to know in the clone
// when to stop cloning, and thus you end up in a recursive loop,
// continuously cloning the "next" in the list.
private final Object key;
private Object value;
// package private to allow the SequencedHashMap to access and manipulate
// them.
Entry next = null;
Entry prev = null;
public Entry(Object key, Object value) {
this.key = key;
this.value = value;
}
// per Map.Entry.getKey()
public Object getKey() {
return this.key;
}
// per Map.Entry.getValue()
public Object getValue() {
return this.value;
}
// per Map.Entry.setValue()
public Object setValue(Object value) {
Object oldValue = this.value;
this.value = value;
return oldValue;
}
public int hashCode() {
// implemented per api docs for Map.Entry.hashCode()
return ((getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()));
}
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
if (!(obj instanceof Map.Entry))
return false;
Map.Entry other = (Map.Entry) obj;
// implemented per api docs for Map.Entry.equals(Object)
return (
(getKey() == null ? other.getKey() == null : getKey().equals(other.getKey()))
&& (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue())));
}
public String toString() {
return "[" + getKey() + "=" + getValue() + "]";
}
}
/**
* Construct an empty sentinel used to hold the head (sentinel.next) and the
* tail (sentinel.prev) of the list. The sentinal has a <code>null</code>
* key and value.
**/
private static final Entry createSentinel() {
Entry s = new Entry(null, null);
s.prev = s;
s.next = s;
return s;
}
/**
* Sentinel used to hold the head and tail of the list of entries.
**/
private Entry sentinel;
/**
* Map of keys to entries
**/
private HashMap entries;
/**
* Holds the number of modifications that have occurred to the map,
* excluding modifications made through a collection view's iterator
* (e.g. entrySet().iterator().remove()). This is used to create a
* fail-fast behavior with the iterators.
**/
private transient long modCount = 0;
/**
* Construct a new sequenced hash map with default initial size and load
* factor.
**/
public SequencedHashMap() {
sentinel = createSentinel();
entries = new HashMap();
}
/**
* Construct a new sequenced hash map with the specified initial size and
* default load factor.
*
* @param initialSize the initial size for the hash table
*
* @see HashMap#HashMap(int)
**/
public SequencedHashMap(int initialSize) {
sentinel = createSentinel();
entries = new HashMap(initialSize);
}
/**
* Construct a new sequenced hash map with the specified initial size and
* load factor.
*
* @param initialSize the initial size for the hash table
*
* @param loadFactor the load factor for the hash table.
*
* @see HashMap#HashMap(int,float)
**/
public SequencedHashMap(int initialSize, float loadFactor) {
sentinel = createSentinel();
entries = new HashMap(initialSize, loadFactor);
}
/**
* Construct a new sequenced hash map and add all the elements in the
* specified map. The order in which the mappings in the specified map are
* added is defined by {@link #putAll(Map)}.
**/
public SequencedHashMap(Map m) {
this();
putAll(m);
}
/**
* Removes an internal entry from the linked list. This does not remove
* it from the underlying map.
**/
private void removeEntry(Entry entry) {
entry.next.prev = entry.prev;
entry.prev.next = entry.next;
}
/**
* Inserts a new internal entry to the tail of the linked list. This does
* not add the entry to the underlying map.
**/
private void insertEntry(Entry entry) {
entry.next = sentinel;
entry.prev = sentinel.prev;
sentinel.prev.next = entry;
sentinel.prev = entry;
}
// per Map.size()
/**
* Implements {@link Map#size()}.
*/
public int size() {
// use the underlying Map's size since size is not maintained here.
return entries.size();
}
/**
* Implements {@link Map#isEmpty()}.
*/
public boolean isEmpty() {
// for quick check whether the map is entry, we can check the linked list
// and see if there's anything in it.
return sentinel.next == sentinel;
}
/**
* Implements {@link Map#containsKey(Object)}.
*/
public boolean containsKey(Object key) {
// pass on to underlying map implementation
return entries.containsKey(key);
}
/**
* Implements {@link Map#containsValue(Object)}.
*/
public boolean containsValue(Object value) {
// unfortunately, we cannot just pass this call to the underlying map
// because we are mapping keys to entries, not keys to values. The
// underlying map doesn't have an efficient implementation anyway, so this
// isn't a big deal.
// do null comparison outside loop so we only need to do it once. This
// provides a tighter, more efficient loop at the expense of slight
// code duplication.
if (value == null) {
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
if (pos.getValue() == null)
return true;
}
} else {
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
if (value.equals(pos.getValue()))
return true;
}
}
return false;
}
/**
* Implements {@link Map#get(Object)}.
*/
public Object get(Object o) {
// find entry for the specified key object
Entry entry = (Entry) entries.get(o);
if (entry == null)
return null;
return entry.getValue();
}
/**
* Return the entry for the "oldest" mapping. That is, return the Map.Entry
* for the key-value pair that was first put into the map when compared to
* all the other pairings in the map. This behavior is equivalent to using
* <code>entrySet().iterator().next()</code>, but this method provides an
* optimized implementation.
*
* @return The first entry in the sequence, or <code>null</code> if the
* map is empty.
**/
public Map.Entry getFirst() {
// sentinel.next points to the "first" element of the sequence -- the head
// of the list, which is exactly the entry we need to return. We must test
// for an empty list though because we don't want to return the sentinel!
return (isEmpty()) ? null : sentinel.next;
}
/**
* Return the key for the "oldest" mapping. That is, return the key for the
* mapping that was first put into the map when compared to all the other
* objects in the map. This behavior is equivalent to using
* <code>getFirst().getKey()</code>, but this method provides a slightly
* optimized implementation.
*
* @return The first key in the sequence, or <code>null</code> if the
* map is empty.
**/
public Object getFirstKey() {
// sentinel.next points to the "first" element of the sequence -- the head
// of the list -- and the requisite key is returned from it. An empty list
// does not need to be tested. In cases where the list is empty,
// sentinel.next will point to the sentinel itself which has a null key,
// which is exactly what we would want to return if the list is empty (a
// nice convient way to avoid test for an empty list)
return sentinel.next.getKey();
}
/**
* Return the value for the "oldest" mapping. That is, return the value for
* the mapping that was first put into the map when compared to all the
* other objects in the map. This behavior is equivalent to using
* <code>getFirst().getValue()</code>, but this method provides a slightly
* optimized implementation.
*
* @return The first value in the sequence, or <code>null</code> if the
* map is empty.
**/
public Object getFirstValue() {
// sentinel.next points to the "first" element of the sequence -- the head
// of the list -- and the requisite value is returned from it. An empty
// list does not need to be tested. In cases where the list is empty,
// sentinel.next will point to the sentinel itself which has a null value,
// which is exactly what we would want to return if the list is empty (a
// nice convient way to avoid test for an empty list)
return sentinel.next.getValue();
}
/**
* Return the entry for the "newest" mapping. That is, return the Map.Entry
* for the key-value pair that was first put into the map when compared to
* all the other pairings in the map. The behavior is equivalent to:
*
* <pre>
* Object obj = null;
* Iterator iter = entrySet().iterator();
* while(iter.hasNext()) {
* obj = iter.next();
* }
* return (Map.Entry)obj;
* </pre>
*
* However, the implementation of this method ensures an O(1) lookup of the
* last key rather than O(n).
*
* @return The last entry in the sequence, or <code>null</code> if the map
* is empty.
**/
public Map.Entry getLast() {
// sentinel.prev points to the "last" element of the sequence -- the tail
// of the list, which is exactly the entry we need to return. We must test
// for an empty list though because we don't want to return the sentinel!
return (isEmpty()) ? null : sentinel.prev;
}
/**
* Return the key for the "newest" mapping. That is, return the key for the
* mapping that was last put into the map when compared to all the other
* objects in the map. This behavior is equivalent to using
* <code>getLast().getKey()</code>, but this method provides a slightly
* optimized implementation.
*
* @return The last key in the sequence, or <code>null</code> if the map is
* empty.
**/
public Object getLastKey() {
// sentinel.prev points to the "last" element of the sequence -- the tail
// of the list -- and the requisite key is returned from it. An empty list
// does not need to be tested. In cases where the list is empty,
// sentinel.prev will point to the sentinel itself which has a null key,
// which is exactly what we would want to return if the list is empty (a
// nice convient way to avoid test for an empty list)
return sentinel.prev.getKey();
}
/**
* Return the value for the "newest" mapping. That is, return the value for
* the mapping that was last put into the map when compared to all the other
* objects in the map. This behavior is equivalent to using
* <code>getLast().getValue()</code>, but this method provides a slightly
* optimized implementation.
*
* @return The last value in the sequence, or <code>null</code> if the map
* is empty.
**/
public Object getLastValue() {
// sentinel.prev points to the "last" element of the sequence -- the tail
// of the list -- and the requisite value is returned from it. An empty
// list does not need to be tested. In cases where the list is empty,
// sentinel.prev will point to the sentinel itself which has a null value,
// which is exactly what we would want to return if the list is empty (a
// nice convient way to avoid test for an empty list)
return sentinel.prev.getValue();
}
/**
* Implements {@link Map#put(Object, Object)}.
*/
public Object put(Object key, Object value) {
modCount++;
Object oldValue = null;
// lookup the entry for the specified key
Entry e = (Entry) entries.get(key);
// check to see if it already exists
if (e != null) {
// remove from list so the entry gets "moved" to the end of list
removeEntry(e);
// update value in map
oldValue = e.setValue(value);
// Note: We do not update the key here because its unnecessary. We only
// do comparisons using equals(Object) and we know the specified key and
// that in the map are equal in that sense. This may cause a problem if
// someone does not implement their hashCode() and/or equals(Object)
// method properly and then use it as a key in this map.
} else {
// add new entry
e = new Entry(key, value);
entries.put(key, e);
}
// assert(entry in map, but not list)
// add to list
insertEntry(e);
return oldValue;
}
/**
* Implements {@link Map#remove(Object)}.
*/
public Object remove(Object key) {
Entry e = removeImpl(key);
return (e == null) ? null : e.getValue();
}
/**
* Fully remove an entry from the map, returning the old entry or null if
* there was no such entry with the specified key.
**/
private Entry removeImpl(Object key) {
Entry e = (Entry) entries.remove(key);
if (e == null)
return null;
modCount++;
removeEntry(e);
return e;
}
/**
* Adds all the mappings in the specified map to this map, replacing any
* mappings that already exist (as per {@link Map#putAll(Map)}). The order
* in which the entries are added is determined by the iterator returned
* from {@link Map#entrySet()} for the specified map.
*
* @param t the mappings that should be added to this map.
*
* @throws NullPointerException if <code>t</code> is <code>null</code>
**/
public void putAll(Map t) {
Iterator iter = t.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
put(entry.getKey(), entry.getValue());
}
}
/**
* Implements {@link Map#clear()}.
*/
public void clear() {
modCount++;
// remove all from the underlying map
entries.clear();
// and the list
sentinel.next = sentinel;
sentinel.prev = sentinel;
}
/**
* Implements {@link Map#equals(Object)}.
*/
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
if (!(obj instanceof Map))
return false;
return entrySet().equals(((Map) obj).entrySet());
}
/**
* Implements {@link Map#hashCode()}.
*/
public int hashCode() {
return entrySet().hashCode();
}
/**
* Provides a string representation of the entries within the map. The
* format of the returned string may change with different releases, so this
* method is suitable for debugging purposes only. If a specific format is
* required, use {@link #entrySet()}.{@link Set#iterator() iterator()} and
* iterate over the entries in the map formatting them as appropriate.
**/
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append('[');
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
buf.append(pos.getKey());
buf.append('=');
buf.append(pos.getValue());
if (pos.next != sentinel) {
buf.append(',');
}
}
buf.append(']');
return buf.toString();
}
/**
* Implements {@link Map#keySet()}.
*/
public Set keySet() {
return new AbstractSet() {
// required impls
public Iterator iterator() {
return new OrderedIterator(KEY);
}
public boolean remove(Object o) {
Entry e = SequencedHashMap.this.removeImpl(o);
return (e != null);
}
// more efficient impls than abstract set
public void clear() {
SequencedHashMap.this.clear();
}
public int size() {
return SequencedHashMap.this.size();
}
public boolean isEmpty() {
return SequencedHashMap.this.isEmpty();
}
public boolean contains(Object o) {
return SequencedHashMap.this.containsKey(o);
}
};
}
/**
* Implements {@link Map#values()}.
*/
public Collection values() {
return new AbstractCollection() {
// required impl
public Iterator iterator() {
return new OrderedIterator(VALUE);
}
public boolean remove(Object value) {
// do null comparison outside loop so we only need to do it once. This
// provides a tighter, more efficient loop at the expense of slight
// code duplication.
if (value == null) {
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
if (pos.getValue() == null) {
SequencedHashMap.this.removeImpl(pos.getKey());
return true;
}
}
} else {
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
if (value.equals(pos.getValue())) {
SequencedHashMap.this.removeImpl(pos.getKey());
return true;
}
}
}
return false;
}
// more efficient impls than abstract collection
public void clear() {
SequencedHashMap.this.clear();
}
public int size() {
return SequencedHashMap.this.size();
}
public boolean isEmpty() {
return SequencedHashMap.this.isEmpty();
}
public boolean contains(Object o) {
return SequencedHashMap.this.containsValue(o);
}
};
}
/**
* Implements {@link Map#entrySet()}.
*/
public Set entrySet() {
return new AbstractSet() {
// helper
private Entry findEntry(Object o) {
if (o == null)
return null;
if (!(o instanceof Map.Entry))
return null;
Map.Entry e = (Map.Entry) o;
Entry entry = (Entry) entries.get(e.getKey());
if (entry != null && entry.equals(e))
return entry;
else
return null;
}
// required impl
public Iterator iterator() {
return new OrderedIterator(ENTRY);
}
public boolean remove(Object o) {
Entry e = findEntry(o);
if (e == null)
return false;
return SequencedHashMap.this.removeImpl(e.getKey()) != null;
}
// more efficient impls than abstract collection
public void clear() {
SequencedHashMap.this.clear();
}
public int size() {
return SequencedHashMap.this.size();
}
public boolean isEmpty() {
return SequencedHashMap.this.isEmpty();
}
public boolean contains(Object o) {
return findEntry(o) != null;
}
};
}
// constants to define what the iterator should return on "next"
private static final int KEY = 0;
private static final int VALUE = 1;
private static final int ENTRY = 2;
private static final int REMOVED_MASK = 0x80000000;
private class OrderedIterator implements Iterator {
/**
* Holds the type that should be returned from the iterator. The value
* should be either {@link #KEY}, {@link #VALUE}, or {@link #ENTRY}. To
* save a tiny bit of memory, this field is also used as a marker for when
* remove has been called on the current object to prevent a second remove
* on the same element. Essientially, if this value is negative (i.e. the
* bit specified by {@link #REMOVED_MASK} is set), the current position
* has been removed. If positive, remove can still be called.
**/
private int returnType;
/**
* Holds the "current" position in the iterator. When pos.next is the
* sentinel, we've reached the end of the list.
**/
private Entry pos = sentinel;
/**
* Holds the expected modification count. If the actual modification
* count of the map differs from this value, then a concurrent
* modification has occurred.
**/
private transient long expectedModCount = modCount;
/**
* Construct an iterator over the sequenced elements in the order in which
* they were added. The {@link #next()} method returns the type specified
* by <code>returnType</code> which must be either {@link #KEY}, {@link
* #VALUE}, or {@link #ENTRY}.
**/
public OrderedIterator(int returnType) {
// Set the "removed" bit so that the iterator starts in a state where
// "next" must be called before "remove" will succeed.
this.returnType = returnType | REMOVED_MASK;
}
/**
* Returns whether there is any additional elements in the iterator to be
* returned.
*
* @return <code>true</code> if there are more elements left to be
* returned from the iterator; <code>false</code> otherwise.
**/
public boolean hasNext() {
return pos.next != sentinel;
}
/**
* Returns the next element from the iterator.
*
* @return the next element from the iterator.
*
* @throws NoSuchElementException if there are no more elements in the
* iterator.
*
* @throws ConcurrentModificationException if a modification occurs in
* the underlying map.
**/
public Object next() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException(Messages.getMessage("seqHashMapConcurrentModificationException00"));
}
if (pos.next == sentinel) {
throw new NoSuchElementException(Messages.getMessage("seqHashMapNoSuchElementException00"));
}
// clear the "removed" flag
returnType = returnType & ~REMOVED_MASK;
pos = pos.next;
switch (returnType) {
case KEY :
return pos.getKey();
case VALUE :
return pos.getValue();
case ENTRY :
return pos;
default :
// should never happen
throw new Error(Messages.getMessage("seqHashMapBadIteratorType01", new Integer(returnType).toString()));
}
}
/**
* Removes the last element returned from the {@link #next()} method from
* the sequenced map.
*
* @throws IllegalStateException if there isn't a "last element" to be
* removed. That is, if {@link #next()} has never been called, or if
* {@link #remove()} was already called on the element.
*
* @throws ConcurrentModificationException if a modification occurs in
* the underlying map.
**/
public void remove() {
if ((returnType & REMOVED_MASK) != 0) {
throw new IllegalStateException(Messages.getMessage("seqHashMapIllegalStateException00"));
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException(Messages.getMessage("seqHashMapConcurrentModificationException00"));
}
SequencedHashMap.this.removeImpl(pos.getKey());
// update the expected mod count for the remove operation
expectedModCount++;
// set the removed flag
returnType = returnType | REMOVED_MASK;
}
}
// APIs maintained from previous version of SequencedHashMap for backwards
// compatibility
/**
* Creates a shallow copy of this object, preserving the internal structure
* by copying only references. The keys and values themselves are not
* <code>clone()</code>'d. The cloned object maintains the same sequence.
*
* @return A clone of this instance.
*
* @throws CloneNotSupportedException if clone is not supported by a
* subclass.
*/
public Object clone() throws CloneNotSupportedException {
// yes, calling super.clone() silly since we're just blowing away all
// the stuff that super might be doing anyway, but for motivations on
// this, see:
// http://www.javaworld.com/javaworld/jw-01-1999/jw-01-object.html
SequencedHashMap map = (SequencedHashMap) super.clone();
// create new, empty sentinel
map.sentinel = createSentinel();
// create a new, empty entry map
// note: this does not preserve the initial capacity and load factor.
map.entries = new HashMap();
// add all the mappings
map.putAll(this);
// Note: We cannot just clone the hashmap and sentinel because we must
// duplicate our internal structures. Cloning those two will not clone all
// the other entries they reference, and so the cloned hash map will not be
// able to maintain internal consistency because there are two objects with
// the same entries. See discussion in the Entry implementation on why we
// cannot implement a clone of the Entry (and thus why we need to recreate
// everything).
return map;
}
/**
* Returns the Map.Entry at the specified index
*
* @throws ArrayIndexOutOfBoundsException if the specified index is
* <code>< 0</code> or <code>></code> the size of the map.
**/
private Map.Entry getEntry(int index) {
Entry pos = sentinel;
if (index < 0) {
throw new ArrayIndexOutOfBoundsException(Messages.getMessage("seqHashMapArrayIndexOutOfBoundsException01", new Integer(index).toString()));
}
// loop to one before the position
int i = -1;
while (i < (index - 1) && pos.next != sentinel) {
i++;
pos = pos.next;
}
// pos.next is the requested position
// if sentinel is next, past end of list
if (pos.next == sentinel) {
throw new ArrayIndexOutOfBoundsException(Messages.getMessage("seqHashMapArrayIndexOutOfBoundsException02",
new Integer(index).toString(),
new Integer(i + 1).toString()));
}
return pos.next;
}
/**
* Gets the key at the specified index.
*
* @param index the index to retrieve
* @return the key at the specified index, or null
* @throws ArrayIndexOutOfBoundsException if the <code>index</code> is
* <code>< 0</code> or <code>></code> the size of the map.
*/
public Object get(int index) {
return getEntry(index).getKey();
}
/**
* Gets the value at the specified index.
*
* @param index the index to retrieve
* @return the value at the specified index, or null
* @throws ArrayIndexOutOfBoundsException if the <code>index</code> is
* <code>< 0</code> or <code>></code> the size of the map.
*/
public Object getValue(int index) {
return getEntry(index).getValue();
}
/**
* Gets the index of the specified key.
*
* @param key the key to find the index of
* @return the index, or -1 if not found
*/
public int indexOf(Object key) {
Entry e = (Entry) entries.get(key);
if (e == null) {
return -1;
}
int pos = 0;
while (e.prev != sentinel) {
pos++;
e = e.prev;
}
return pos;
}
/**
* Gets an iterator over the keys.
*
* @return an iterator over the keys
*/
public Iterator iterator() {
return keySet().iterator();
}
/**
* Gets the last index of the specified key.
*
* @param key the key to find the index of
* @return the index, or -1 if not found
*/
public int lastIndexOf(Object key) {
// keys in a map are guarunteed to be unique
return indexOf(key);
}
/**
* Returns a List view of the keys rather than a set view. The returned
* list is unmodifiable. This is required because changes to the values of
* the list (using {@link java.util.ListIterator#set(Object)}) will
* effectively remove the value from the list and reinsert that value at
* the end of the list, which is an unexpected side effect of changing the
* value of a list. This occurs because changing the key, changes when the
* mapping is added to the map and thus where it appears in the list.
*
* <p>An alternative to this method is to use {@link #keySet()}
*
* @see #keySet()
* @return The ordered list of keys.
*/
public List sequence() {
List l = new ArrayList(size());
Iterator iter = keySet().iterator();
while (iter.hasNext()) {
l.add(iter.next());
}
return Collections.unmodifiableList(l);
}
/**
* Removes the element at the specified index.
*
* @param index The index of the object to remove.
* @return The previous value coressponding the <code>key</code>, or
* <code>null</code> if none existed.
*
* @throws ArrayIndexOutOfBoundsException if the <code>index</code> is
* <code>< 0</code> or <code>></code> the size of the map.
*/
public Object remove(int index) {
return remove(get(index));
}
// per Externalizable.readExternal(ObjectInput)
/**
* Deserializes this map from the given stream.
*
* @param in the stream to deserialize from
* @throws IOException if the stream raises it
* @throws ClassNotFoundException if the stream raises it
*/
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
int size = in.readInt();
for (int i = 0; i < size; i++) {
Object key = in.readObject();
Object value = in.readObject();
put(key, value);
}
}
/**
* Serializes this map to the given stream.
*
* @param out the stream to serialize to
* @throws IOException if the stream raises it
*/
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(size());
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
out.writeObject(pos.getKey());
out.writeObject(pos.getValue());
}
}
// add a serial version uid, so that if we change things in the future
// without changing the format, we can still deserialize properly.
private static final long serialVersionUID = 3380552487888102930L;
}
1.61 +6 -0 xml-axis/java/src/org/apache/axis/i18n/resource.properties
Index: resource.properties
===================================================================
RCS file: /home/cvs/xml-axis/java/src/org/apache/axis/i18n/resource.properties,v
retrieving revision 1.60
retrieving revision 1.61
diff -u -r1.60 -r1.61
--- resource.properties 11 Jul 2003 12:59:19 -0000 1.60
+++ resource.properties 12 Jul 2003 22:13:00 -0000 1.61
@@ -1148,3 +1148,9 @@
noNamespace00=Attempted to write schema for bad QName (no namespace) : {0}
charsInArray=Found character data inside an array element while deserializing
+seqHashMapConcurrentModificationException00=modification has occured in the underlying map
+seqHashMapNoSuchElementException00=there are no more elements in the iterator
+seqHashMapBadIteratorType01=bad iterator type: {0}
+seqHashMapIllegalStateException00=remove() must follow next()
+seqHashMapArrayIndexOutOfBoundsException01={0} < 0
+seqHashMapArrayIndexOutOfBoundsException02={0} >= {1}
\ No newline at end of file
1.80 +63 -22 xml-axis/java/src/org/apache/axis/transport/http/SimpleAxisServer.java
Index: SimpleAxisServer.java
===================================================================
RCS file: /home/cvs/xml-axis/java/src/org/apache/axis/transport/http/SimpleAxisServer.java,v
retrieving revision 1.79
retrieving revision 1.80
diff -u -r1.79 -r1.80
--- SimpleAxisServer.java 10 Jul 2003 15:18:07 -0000 1.79
+++ SimpleAxisServer.java 12 Jul 2003 22:13:00 -0000 1.80
@@ -56,18 +56,19 @@
package org.apache.axis.transport.http;
import org.apache.axis.components.logger.LogFactory;
+import org.apache.axis.components.threadpool.ThreadPool;
import org.apache.axis.server.AxisServer;
import org.apache.axis.session.Session;
import org.apache.axis.session.SimpleSession;
import org.apache.axis.utils.Messages;
import org.apache.axis.utils.Options;
+import org.apache.axis.collections.LRUMap;
import org.apache.commons.logging.Log;
-import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
-import java.util.Hashtable;
+import java.util.Map;
/**
* This is a simple implementation of an HTTP server for processing
@@ -77,6 +78,7 @@
*
* @author Sam Ruby (ruby@us.ibm.com)
* @author Rob Jellinghaus (robj@unrealities.com)
+ * @author Alireza Taherkordi (a_taherkordi@users.sourceforge.net)
*/
public class SimpleAxisServer implements Runnable {
protected static Log log =
@@ -85,13 +87,18 @@
// session state.
// This table maps session keys (random numbers) to SimpleAxisSession objects.
//
- // There is NO CLEANUP of this table at present, and if clients are not
+ // There is a simple LRU based session cleanup mechanism, but if clients are not
// passing cookies, then a new session will be created for *every* request.
- // This is the biggest impediment to any kind of real SimpleAxisServer use.
- // So, if this becomes objectionable, we will implement some simpleminded
- // cleanup (perhaps just a cap on max # of sessions, and some kind of LRU
- // cleanup policy).
- private Hashtable sessions = new Hashtable();
+ private Map sessions;
+ //Maximum capacity of the LRU Map used for session cleanup
+ private int maxSessions;
+ public static final int MAX_SESSIONS_DEFAULT = 100;
+
+ public static ThreadPool getPool() {
+ return pool;
+ }
+
+ private static ThreadPool pool;
// Are we doing threads?
private static boolean doThreads = true;
@@ -100,6 +107,40 @@
// Set this to false if you don't want any session overhead.
private static boolean doSessions = true;
+ public SimpleAxisServer() {
+ this(ThreadPool.DEFAULT_MAX_THREADS);
+ }
+
+ /**
+ * @param maxPoolSize maximum thread pool size
+ */
+ public SimpleAxisServer(int maxPoolSize) {
+ this(maxPoolSize, MAX_SESSIONS_DEFAULT);
+ }
+
+ /**
+ * Constructor
+ * @param maxSessions maximum sessions
+ */
+ public SimpleAxisServer(int maxPoolSize, int maxSessions) {
+ this.maxSessions = maxSessions;
+ sessions = new LRUMap(maxSessions);
+ pool = new ThreadPool(maxPoolSize);
+ }
+
+ public int getMaxSessions() {
+ return maxSessions;
+ }
+
+ /**
+ * @param maxSessions maximum sessions
+ */
+ public void setMaxSessions(int maxSessions) {
+ this.maxSessions = maxSessions;
+ ((LRUMap)sessions).setMaximumSize(maxSessions);
+ }
+ //---------------------------------------------------
+
protected boolean isSessionUsed() {
return doSessions;
}
@@ -113,6 +154,7 @@
}
protected Session createSession(String cooky) {
+
// is there a session already?
Session session = null;
if (sessions.containsKey(cooky)) {
@@ -135,7 +177,7 @@
// Axis server (shared between instances)
private static AxisServer myAxisServer = null;
- protected static synchronized AxisServer getAxisServer() {
+ public static synchronized AxisServer getAxisServer() {
if (myAxisServer == null) {
myAxisServer = new AxisServer();
}
@@ -167,9 +209,7 @@
if (socket != null) {
SimpleAxisWorker worker = new SimpleAxisWorker(this, socket);
if (doThreads) {
- Thread thread = new Thread(worker);
- thread.setDaemon(true);
- thread.start();
+ pool.addWorker(worker);
} else {
worker.run();
}
@@ -195,12 +235,6 @@
*/
public void setServerSocket(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
- try {
- Class [] clazzParms = new Class[]{boolean.class};
- Object[] parms = new Object[] {Boolean.TRUE};
- Method m = this.serverSocket.getClass().getMethod("setReuseAddress", clazzParms);
- m.invoke(this.serverSocket, parms);
- } catch (Exception e) {}
}
/**
@@ -247,7 +281,8 @@
log.info(Messages.getMessage("quit00", "SimpleAxisServer"));
// Kill the JVM, which will interrupt pending accepts even on linux.
- System.exit(0);
+ //System.exit(0);
+ pool.shutdown();
}
/**
@@ -255,8 +290,6 @@
*/
public static void main(String args[]) {
- SimpleAxisServer sas = new SimpleAxisServer();
-
Options opts = null;
try {
opts = new Options(args);
@@ -265,6 +298,15 @@
return;
}
+ String maxPoolSize = opts.isValueSet('t');
+ if (maxPoolSize==null) maxPoolSize = ThreadPool.DEFAULT_MAX_THREADS + "";
+
+ String maxSessions = opts.isValueSet('m');
+ if (maxSessions==null) maxSessions = MAX_SESSIONS_DEFAULT + "";
+
+ SimpleAxisServer sas = new SimpleAxisServer(Integer.parseInt(maxPoolSize),
+ Integer.parseInt(maxSessions));
+
try {
doThreads = (opts.isFlagSet('t') > 0);
@@ -292,6 +334,5 @@
log.error(Messages.getMessage("exception00"), e);
return;
}
-
}
}
1.25 +12 -1 xml-axis/java/src/org/apache/axis/transport/http/SimpleAxisWorker.java
Index: SimpleAxisWorker.java
===================================================================
RCS file: /home/cvs/xml-axis/java/src/org/apache/axis/transport/http/SimpleAxisWorker.java,v
retrieving revision 1.24
retrieving revision 1.25
diff -u -r1.24 -r1.25
--- SimpleAxisWorker.java 22 Apr 2003 19:35:52 -0000 1.24
+++ SimpleAxisWorker.java 12 Jul 2003 22:13:00 -0000 1.25
@@ -183,9 +183,20 @@
}
/**
+ * Run method
+ */
+ public void run() {
+ try {
+ execute();
+ } finally {
+ SimpleAxisServer.getPool().workerDone(this, false);
+ }
+ }
+
+ /**
* The main workhorse method.
*/
- public void run() {
+ public void execute () {
byte buf[] = new byte[BUFSIZ];
// create an Axis server
AxisServer engine = server.getAxisServer();
1.4 +11 -4 xml-axis/java/src/org/apache/axis/components/threadpool/ThreadPool.java
Index: ThreadPool.java
===================================================================
RCS file: /home/cvs/xml-axis/java/src/org/apache/axis/components/threadpool/ThreadPool.java,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- ThreadPool.java 22 Apr 2003 19:34:13 -0000 1.3
+++ ThreadPool.java 12 Jul 2003 22:13:01 -0000 1.4
@@ -71,11 +71,19 @@
protected static Log log =
LogFactory.getLog(ThreadPool.class.getName());
- public static final long MAX_THREADS = 100;
+ public static final int DEFAULT_MAX_THREADS = 100;
protected Map threads = new Hashtable();
protected long threadcount;
public boolean _shutdown;
+ private int maxPoolSize = DEFAULT_MAX_THREADS;
+
+ public ThreadPool() {
+ }
+
+ public ThreadPool(int maxPoolSize) {
+ this.maxPoolSize = maxPoolSize;
+ }
public void cleanup()
throws InterruptedException {
@@ -130,9 +138,9 @@
if (log.isDebugEnabled()) {
log.debug("Enter: ThreadPool::addWorker");
}
- if (_shutdown ||
- threadcount == MAX_THREADS)
+ if (_shutdown || threadcount == maxPoolSize) {
throw new IllegalStateException(Messages.getMessage("illegalStateException00"));
+ }
Thread thread = new Thread(worker);
threads.put(worker, thread);
threadcount++;
@@ -231,7 +239,6 @@
}
return false;
}
- long start = System.currentTimeMillis();
for (; ;) {
wait(waittime);
if (threadcount == 0) {