| // |
| // ======================================================================== |
| // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.nosql.mongodb; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.net.UnknownHostException; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.jetty.nosql.NoSqlSession; |
| import org.eclipse.jetty.nosql.NoSqlSessionManager; |
| import org.eclipse.jetty.server.SessionIdManager; |
| import org.eclipse.jetty.util.annotation.ManagedAttribute; |
| import org.eclipse.jetty.util.annotation.ManagedObject; |
| import org.eclipse.jetty.util.annotation.ManagedOperation; |
| import org.eclipse.jetty.util.log.Log; |
| import org.eclipse.jetty.util.log.Logger; |
| |
| import com.mongodb.BasicDBObject; |
| import com.mongodb.DBCollection; |
| import com.mongodb.DBObject; |
| import com.mongodb.MongoException; |
| import com.mongodb.WriteConcern; |
| import com.mongodb.WriteResult; |
| |
| |
| /** |
| * MongoSessionManager |
| * |
| * Clustered session manager using MongoDB as the shared DB instance. |
| * The document model is an outer object that contains the elements: |
| * <ul> |
| * <li>"id" : session_id </li> |
| * <li>"created" : create_time </li> |
| * <li>"accessed": last_access_time </li> |
| * <li>"maxIdle" : max_idle_time setting as session was created </li> |
| * <li>"expiry" : time at which session should expire </li> |
| * <li>"valid" : session_valid </li> |
| * <li>"context" : a nested object containing 1 nested object per context for which the session id is in use |
| * </ul> |
| * Each of the nested objects inside the "context" element contains: |
| * <ul> |
| * <li>unique_context_name : nested object containing name:value pairs of the session attributes for that context</li> |
| * </ul> |
| * <p> |
| * One of the name:value attribute pairs will always be the special attribute "__metadata__". The value |
| * is an object representing a version counter which is incremented every time the attributes change. |
| * </p> |
| * <p> |
| * For example: |
| * <code> |
| * { "_id" : ObjectId("52845534a40b66410f228f23"), |
| * "accessed" : NumberLong("1384818548903"), |
| * "maxIdle" : 1, |
| * "context" : { "::/contextA" : { "A" : "A", |
| * "__metadata__" : { "version" : NumberLong(2) } |
| * }, |
| * "::/contextB" : { "B" : "B", |
| * "__metadata__" : { "version" : NumberLong(1) } |
| * } |
| * }, |
| * "created" : NumberLong("1384818548903"), |
| * "expiry" : NumberLong("1384818549903"), |
| * "id" : "w01ijx2vnalgv1sqrpjwuirprp7", |
| * "valid" : true |
| * } |
| * </code> |
| * </p> |
| * <p> |
| * In MongoDB, the nesting level is indicated by "." separators for the key name. Thus to |
| * interact with a session attribute, the key is composed of: |
| * "context".unique_context_name.attribute_name |
| * Eg "context"."::/contextA"."A" |
| * </p> |
| */ |
| @ManagedObject("Mongo Session Manager") |
| public class MongoSessionManager extends NoSqlSessionManager |
| { |
| private static final Logger LOG = Log.getLogger(MongoSessionManager.class); |
| |
| private final static Logger __log = Log.getLogger("org.eclipse.jetty.server.session"); |
| |
| /* |
| * strings used as keys or parts of keys in mongo |
| */ |
| /** |
| * Special attribute for a session that is context-specific |
| */ |
| private final static String __METADATA = "__metadata__"; |
| |
| |
| /** |
| * Session id |
| */ |
| public final static String __ID = "id"; |
| |
| /** |
| * Time of session creation |
| */ |
| private final static String __CREATED = "created"; |
| |
| /** |
| * Whether or not session is valid |
| */ |
| public final static String __VALID = "valid"; |
| |
| /** |
| * Time at which session was invalidated |
| */ |
| public final static String __INVALIDATED = "invalidated"; |
| |
| /** |
| * Last access time of session |
| */ |
| public final static String __ACCESSED = "accessed"; |
| |
| /** |
| * Time this session will expire, based on last access time and maxIdle |
| */ |
| public final static String __EXPIRY = "expiry"; |
| |
| /** |
| * The max idle time of a session (smallest value across all contexts which has a session with the same id) |
| */ |
| public final static String __MAX_IDLE = "maxIdle"; |
| |
| /** |
| * Name of nested document field containing 1 sub document per context for which the session id is in use |
| */ |
| private final static String __CONTEXT = "context"; |
| |
| |
| /** |
| * Special attribute per session per context, incremented each time attributes are modified |
| */ |
| public final static String __VERSION = __METADATA + ".version"; |
| |
| /** |
| * the context id is only set when this class has been started |
| */ |
| private String _contextId = null; |
| |
| |
| /** |
| * Access to MongoDB |
| */ |
| private DBCollection _dbSessions; |
| |
| |
| /** |
| * Utility value of 1 for a session version for this context |
| */ |
| private DBObject _version_1; |
| |
| |
| /* ------------------------------------------------------------ */ |
| public MongoSessionManager() throws UnknownHostException, MongoException |
| { |
| |
| } |
| |
| |
| |
| /*------------------------------------------------------------ */ |
| @Override |
| public void doStart() throws Exception |
| { |
| super.doStart(); |
| String[] hosts = getContextHandler().getVirtualHosts(); |
| |
| if (hosts == null || hosts.length == 0) |
| hosts = new String[] |
| { "::" }; // IPv6 equiv of 0.0.0.0 |
| |
| String contextPath = getContext().getContextPath(); |
| if (contextPath == null || "".equals(contextPath)) |
| { |
| contextPath = "*"; |
| } |
| |
| _contextId = createContextId(hosts,contextPath); |
| _version_1 = new BasicDBObject(getContextAttributeKey(__VERSION),1); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * @see org.eclipse.jetty.server.session.AbstractSessionManager#setSessionIdManager(org.eclipse.jetty.server.SessionIdManager) |
| */ |
| @Override |
| public void setSessionIdManager(SessionIdManager metaManager) |
| { |
| MongoSessionIdManager msim = (MongoSessionIdManager)metaManager; |
| _dbSessions=msim.getSessions(); |
| super.setSessionIdManager(metaManager); |
| |
| } |
| |
| /* ------------------------------------------------------------ */ |
| @Override |
| protected synchronized Object save(NoSqlSession session, Object version, boolean activateAfterSave) |
| { |
| try |
| { |
| __log.debug("MongoSessionManager:save session {}", session.getClusterId()); |
| session.willPassivate(); |
| |
| // Form query for upsert |
| BasicDBObject key = new BasicDBObject(__ID,session.getClusterId()); |
| |
| // Form updates |
| BasicDBObject update = new BasicDBObject(); |
| boolean upsert = false; |
| BasicDBObject sets = new BasicDBObject(); |
| BasicDBObject unsets = new BasicDBObject(); |
| |
| |
| // handle valid or invalid |
| if (session.isValid()) |
| { |
| long expiry = (session.getMaxInactiveInterval() > 0?(session.getAccessed()+(1000L*getMaxInactiveInterval())):0); |
| __log.debug("MongoSessionManager: calculated expiry {} for session {}", expiry, session.getId()); |
| |
| // handle new or existing |
| if (version == null) |
| { |
| // New session |
| upsert = true; |
| version = new Long(1); |
| sets.put(__CREATED,session.getCreationTime()); |
| sets.put(__VALID,true); |
| |
| sets.put(getContextAttributeKey(__VERSION),version); |
| sets.put(__MAX_IDLE, getMaxInactiveInterval()); |
| sets.put(__EXPIRY, expiry); |
| } |
| else |
| { |
| version = new Long(((Number)version).longValue() + 1); |
| update.put("$inc",_version_1); |
| //if max idle time and/or expiry is smaller for this context, then choose that for the whole session doc |
| BasicDBObject fields = new BasicDBObject(); |
| fields.append(__MAX_IDLE, true); |
| fields.append(__EXPIRY, true); |
| DBObject o = _dbSessions.findOne(new BasicDBObject("id",session.getClusterId()), fields); |
| if (o != null) |
| { |
| Integer currentMaxIdle = (Integer)o.get(__MAX_IDLE); |
| Long currentExpiry = (Long)o.get(__EXPIRY); |
| if (currentMaxIdle != null && getMaxInactiveInterval() > 0 && getMaxInactiveInterval() < currentMaxIdle) |
| sets.put(__MAX_IDLE, getMaxInactiveInterval()); |
| if (currentExpiry != null && expiry > 0 && expiry != currentExpiry) |
| sets.put(__EXPIRY, expiry); |
| } |
| } |
| |
| sets.put(__ACCESSED,session.getAccessed()); |
| Set<String> names = session.takeDirty(); |
| if (isSaveAllAttributes() || upsert) |
| { |
| names.addAll(session.getNames()); // note dirty may include removed names |
| } |
| |
| for (String name : names) |
| { |
| Object value = session.getAttribute(name); |
| if (value == null) |
| unsets.put(getContextKey() + "." + encodeName(name),1); |
| else |
| sets.put(getContextKey() + "." + encodeName(name),encodeName(value)); |
| } |
| } |
| else |
| { |
| sets.put(__VALID,false); |
| sets.put(__INVALIDATED, System.currentTimeMillis()); |
| unsets.put(getContextKey(),1); |
| } |
| |
| // Do the upsert |
| if (!sets.isEmpty()) |
| update.put("$set",sets); |
| if (!unsets.isEmpty()) |
| update.put("$unset",unsets); |
| |
| _dbSessions.update(key,update,upsert,false,WriteConcern.SAFE); |
| |
| if (__log.isDebugEnabled()) |
| __log.debug("MongoSessionManager:save:db.sessions.update( {}, {} )", key, update); |
| |
| if (activateAfterSave) |
| session.didActivate(); |
| |
| return version; |
| } |
| catch (Exception e) |
| { |
| LOG.warn(e); |
| } |
| return null; |
| } |
| |
| /*------------------------------------------------------------ */ |
| @Override |
| protected Object refresh(NoSqlSession session, Object version) |
| { |
| __log.debug("MongoSessionManager:refresh session {}", session.getId()); |
| |
| // check if our in memory version is the same as what is on the disk |
| if (version != null) |
| { |
| DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,session.getClusterId()),_version_1); |
| |
| if (o != null) |
| { |
| Object saved = getNestedValue(o, getContextAttributeKey(__VERSION)); |
| |
| if (saved != null && saved.equals(version)) |
| { |
| __log.debug("MongoSessionManager:refresh not needed session {}", session.getId()); |
| return version; |
| } |
| version = saved; |
| } |
| } |
| |
| // If we are here, we have to load the object |
| DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,session.getClusterId())); |
| |
| // If it doesn't exist, invalidate |
| if (o == null) |
| { |
| __log.debug("MongoSessionManager:refresh:marking session {} invalid, no object", session.getClusterId()); |
| session.invalidate(); |
| return null; |
| } |
| |
| // If it has been flagged invalid, invalidate |
| Boolean valid = (Boolean)o.get(__VALID); |
| if (valid == null || !valid) |
| { |
| __log.debug("MongoSessionManager:refresh:marking session {} invalid, valid flag {}", session.getClusterId(), valid); |
| session.invalidate(); |
| return null; |
| } |
| |
| // We need to update the attributes. We will model this as a passivate, |
| // followed by bindings and then activation. |
| session.willPassivate(); |
| try |
| { |
| DBObject attrs = (DBObject)getNestedValue(o,getContextKey()); |
| //if disk version now has no attributes, get rid of them |
| if (attrs == null || attrs.keySet().size() == 0) |
| { |
| session.clearAttributes(); |
| } |
| else |
| { |
| //iterate over the names of the attributes on the disk version, updating the value |
| for (String name : attrs.keySet()) |
| { |
| //skip special metadata field which is not one of the session attributes |
| if (__METADATA.equals(name)) |
| continue; |
| |
| String attr = decodeName(name); |
| Object value = decodeValue(attrs.get(name)); |
| |
| //session does not already contain this attribute, so bind it |
| if (session.getAttribute(attr) == null) |
| { |
| session.doPutOrRemove(attr,value); |
| session.bindValue(attr,value); |
| } |
| else //session already contains this attribute, update its value |
| { |
| session.doPutOrRemove(attr,value); |
| } |
| |
| } |
| // cleanup, remove values from session, that don't exist in data anymore: |
| for (String str : session.getNames()) |
| { |
| if (!attrs.keySet().contains(encodeName(str))) |
| { |
| session.doPutOrRemove(str,null); |
| session.unbindValue(str,session.getAttribute(str)); |
| } |
| } |
| } |
| |
| /* |
| * We are refreshing so we should update the last accessed time. |
| */ |
| BasicDBObject key = new BasicDBObject(__ID,session.getClusterId()); |
| BasicDBObject sets = new BasicDBObject(); |
| // Form updates |
| BasicDBObject update = new BasicDBObject(); |
| sets.put(__ACCESSED,System.currentTimeMillis()); |
| // Do the upsert |
| if (!sets.isEmpty()) |
| { |
| update.put("$set",sets); |
| } |
| |
| _dbSessions.update(key,update,false,false,WriteConcern.SAFE); |
| |
| session.didActivate(); |
| |
| return version; |
| } |
| catch (Exception e) |
| { |
| LOG.warn(e); |
| } |
| |
| return null; |
| } |
| |
| /*------------------------------------------------------------ */ |
| @Override |
| protected synchronized NoSqlSession loadSession(String clusterId) |
| { |
| DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,clusterId)); |
| |
| __log.debug("MongoSessionManager:id={} loaded={}", clusterId, o); |
| if (o == null) |
| return null; |
| |
| Boolean valid = (Boolean)o.get(__VALID); |
| __log.debug("MongoSessionManager:id={} valid={}", clusterId, valid); |
| if (valid == null || !valid) |
| return null; |
| |
| try |
| { |
| Object version = o.get(getContextAttributeKey(__VERSION)); |
| Long created = (Long)o.get(__CREATED); |
| Long accessed = (Long)o.get(__ACCESSED); |
| |
| NoSqlSession session = null; |
| |
| // get the session for the context |
| DBObject attrs = (DBObject)getNestedValue(o,getContextKey()); |
| |
| __log.debug("MongoSessionManager:attrs {}", attrs); |
| if (attrs != null) |
| { |
| __log.debug("MongoSessionManager: session {} present for context {}", clusterId, getContextKey()); |
| //only load a session if it exists for this context |
| session = new NoSqlSession(this,created,accessed,clusterId,version); |
| |
| for (String name : attrs.keySet()) |
| { |
| //skip special metadata attribute which is not one of the actual session attributes |
| if ( __METADATA.equals(name) ) |
| continue; |
| |
| String attr = decodeName(name); |
| Object value = decodeValue(attrs.get(name)); |
| |
| session.doPutOrRemove(attr,value); |
| session.bindValue(attr,value); |
| } |
| session.didActivate(); |
| } |
| else |
| __log.debug("MongoSessionManager: session {} not present for context {}",clusterId, getContextKey()); |
| |
| return session; |
| } |
| catch (Exception e) |
| { |
| LOG.warn(e); |
| } |
| return null; |
| } |
| |
| |
| |
| /*------------------------------------------------------------ */ |
| /** |
| * Remove the per-context sub document for this session id. |
| * @see org.eclipse.jetty.nosql.NoSqlSessionManager#remove(org.eclipse.jetty.nosql.NoSqlSession) |
| */ |
| @Override |
| protected boolean remove(NoSqlSession session) |
| { |
| __log.debug("MongoSessionManager:remove:session {} for context {}",session.getClusterId(), getContextKey()); |
| |
| /* |
| * Check if the session exists and if it does remove the context |
| * associated with this session |
| */ |
| BasicDBObject key = new BasicDBObject(__ID,session.getClusterId()); |
| |
| DBObject o = _dbSessions.findOne(key,_version_1); |
| |
| if (o != null) |
| { |
| BasicDBObject remove = new BasicDBObject(); |
| BasicDBObject unsets = new BasicDBObject(); |
| unsets.put(getContextKey(),1); |
| remove.put("$unset",unsets); |
| _dbSessions.update(key,remove,false,false,WriteConcern.SAFE); |
| |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| |
| |
| /** |
| * @see org.eclipse.jetty.nosql.NoSqlSessionManager#expire(java.lang.String) |
| */ |
| @Override |
| protected void expire (String idInCluster) |
| { |
| __log.debug("MongoSessionManager:expire session {} ", idInCluster); |
| |
| //Expire the session for this context |
| super.expire(idInCluster); |
| |
| //If the outer session document has not already been marked invalid, do so. |
| DBObject validKey = new BasicDBObject(__VALID, true); |
| DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,idInCluster), validKey); |
| |
| if (o != null && (Boolean)o.get(__VALID)) |
| { |
| BasicDBObject update = new BasicDBObject(); |
| BasicDBObject sets = new BasicDBObject(); |
| sets.put(__VALID,false); |
| sets.put(__INVALIDATED, System.currentTimeMillis()); |
| update.put("$set",sets); |
| |
| BasicDBObject key = new BasicDBObject(__ID,idInCluster); |
| _dbSessions.update(key,update,false,false,WriteConcern.SAFE); |
| } |
| } |
| |
| |
| /*------------------------------------------------------------ */ |
| /** |
| * Change the session id. Note that this will change the session id for all contexts for which the session id is in use. |
| * @see org.eclipse.jetty.nosql.NoSqlSessionManager#update(org.eclipse.jetty.nosql.NoSqlSession, java.lang.String, java.lang.String) |
| */ |
| @Override |
| protected void update(NoSqlSession session, String newClusterId, String newNodeId) throws Exception |
| { |
| BasicDBObject key = new BasicDBObject(__ID, session.getClusterId()); |
| BasicDBObject sets = new BasicDBObject(); |
| BasicDBObject update = new BasicDBObject(__ID, newClusterId); |
| sets.put("$set", update); |
| _dbSessions.update(key, sets, false, false,WriteConcern.SAFE); |
| } |
| |
| /*------------------------------------------------------------ */ |
| protected String encodeName(String name) |
| { |
| return name.replace("%","%25").replace(".","%2E"); |
| } |
| |
| /*------------------------------------------------------------ */ |
| protected String decodeName(String name) |
| { |
| return name.replace("%2E",".").replace("%25","%"); |
| } |
| |
| /*------------------------------------------------------------ */ |
| protected Object encodeName(Object value) throws IOException |
| { |
| if (value instanceof Number || value instanceof String || value instanceof Boolean || value instanceof Date) |
| { |
| return value; |
| } |
| else if (value.getClass().equals(HashMap.class)) |
| { |
| BasicDBObject o = new BasicDBObject(); |
| for (Map.Entry<?, ?> entry : ((Map<?, ?>)value).entrySet()) |
| { |
| if (!(entry.getKey() instanceof String)) |
| { |
| o = null; |
| break; |
| } |
| o.append(encodeName(entry.getKey().toString()),encodeName(entry.getValue())); |
| } |
| |
| if (o != null) |
| return o; |
| } |
| |
| ByteArrayOutputStream bout = new ByteArrayOutputStream(); |
| ObjectOutputStream out = new ObjectOutputStream(bout); |
| out.reset(); |
| out.writeUnshared(value); |
| out.flush(); |
| return bout.toByteArray(); |
| } |
| |
| /*------------------------------------------------------------ */ |
| protected Object decodeValue(final Object valueToDecode) throws IOException, ClassNotFoundException |
| { |
| if (valueToDecode == null || valueToDecode instanceof Number || valueToDecode instanceof String || valueToDecode instanceof Boolean || valueToDecode instanceof Date) |
| { |
| return valueToDecode; |
| } |
| else if (valueToDecode instanceof byte[]) |
| { |
| final byte[] decodeObject = (byte[])valueToDecode; |
| final ByteArrayInputStream bais = new ByteArrayInputStream(decodeObject); |
| final ClassLoadingObjectInputStream objectInputStream = new ClassLoadingObjectInputStream(bais); |
| return objectInputStream.readUnshared(); |
| } |
| else if (valueToDecode instanceof DBObject) |
| { |
| Map<String, Object> map = new HashMap<String, Object>(); |
| for (String name : ((DBObject)valueToDecode).keySet()) |
| { |
| String attr = decodeName(name); |
| map.put(attr,decodeValue(((DBObject)valueToDecode).get(name))); |
| } |
| return map; |
| } |
| else |
| { |
| throw new IllegalStateException(valueToDecode.getClass().toString()); |
| } |
| } |
| |
| |
| /*------------------------------------------------------------ */ |
| private String getContextKey() |
| { |
| return __CONTEXT + "." + _contextId; |
| } |
| |
| /*------------------------------------------------------------ */ |
| /** Get a dot separated key for |
| * @param key |
| * @return |
| */ |
| private String getContextAttributeKey(String attr) |
| { |
| return getContextKey()+ "." + attr; |
| } |
| |
| /*------------------------------------------------------------ */ |
| @ManagedOperation(value="purge invalid sessions in the session store based on normal criteria", impact="ACTION") |
| public void purge() |
| { |
| ((MongoSessionIdManager)_sessionIdManager).purge(); |
| } |
| |
| |
| /*------------------------------------------------------------ */ |
| @ManagedOperation(value="full purge of invalid sessions in the session store", impact="ACTION") |
| public void purgeFully() |
| { |
| ((MongoSessionIdManager)_sessionIdManager).purgeFully(); |
| } |
| |
| /*------------------------------------------------------------ */ |
| @ManagedOperation(value="scavenge sessions known to this manager", impact="ACTION") |
| public void scavenge() |
| { |
| ((MongoSessionIdManager)_sessionIdManager).scavenge(); |
| } |
| |
| /*------------------------------------------------------------ */ |
| @ManagedOperation(value="scanvenge all sessions", impact="ACTION") |
| public void scavengeFully() |
| { |
| ((MongoSessionIdManager)_sessionIdManager).scavengeFully(); |
| } |
| |
| /*------------------------------------------------------------ */ |
| /** |
| * returns the total number of session objects in the session store |
| * |
| * the count() operation itself is optimized to perform on the server side |
| * and avoid loading to client side. |
| */ |
| @ManagedAttribute("total number of known sessions in the store") |
| public long getSessionStoreCount() |
| { |
| return _dbSessions.find().count(); |
| } |
| |
| /*------------------------------------------------------------ */ |
| /** |
| * MongoDB keys are . delimited for nesting so .'s are protected characters |
| * |
| * @param virtualHosts |
| * @param contextPath |
| * @return |
| */ |
| private String createContextId(String[] virtualHosts, String contextPath) |
| { |
| String contextId = virtualHosts[0] + contextPath; |
| |
| contextId.replace('/', '_'); |
| contextId.replace('.','_'); |
| contextId.replace('\\','_'); |
| |
| return contextId; |
| } |
| |
| /*------------------------------------------------------------ */ |
| /** |
| * Dig through a given dbObject for the nested value |
| */ |
| private Object getNestedValue(DBObject dbObject, String nestedKey) |
| { |
| String[] keyChain = nestedKey.split("\\."); |
| |
| DBObject temp = dbObject; |
| |
| for (int i = 0; i < keyChain.length - 1; ++i) |
| { |
| temp = (DBObject)temp.get(keyChain[i]); |
| |
| if ( temp == null ) |
| { |
| return null; |
| } |
| } |
| |
| return temp.get(keyChain[keyChain.length - 1]); |
| } |
| |
| |
| /*------------------------------------------------------------ */ |
| /** |
| * ClassLoadingObjectInputStream |
| * |
| * |
| */ |
| protected class ClassLoadingObjectInputStream extends ObjectInputStream |
| { |
| public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException |
| { |
| super(in); |
| } |
| |
| public ClassLoadingObjectInputStream () throws IOException |
| { |
| super(); |
| } |
| |
| @Override |
| public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException |
| { |
| try |
| { |
| return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader()); |
| } |
| catch (ClassNotFoundException e) |
| { |
| return super.resolveClass(cl); |
| } |
| } |
| } |
| |
| |
| } |