ClientSessionContext.java revision 6b811c5daec1b28e6f63b57f98a032236f2c3cf7
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package org.apache.harmony.xnet.provider.jsse; 18 19import java.util.Iterator; 20import java.util.LinkedHashMap; 21import java.util.Map; 22import java.util.HashMap; 23import java.util.ArrayList; 24import java.util.Arrays; 25 26import javax.net.ssl.SSLSession; 27 28/** 29 * Caches client sessions. Indexes by host and port. Users are typically 30 * looking to reuse any session for a given host and port. Users of the 31 * standard API are forced to iterate over the sessions semi-linearly as 32 * opposed to in constant time. 33 */ 34public class ClientSessionContext extends AbstractSessionContext { 35 36 /* 37 * We don't care about timeouts in the client implementation. Trying 38 * to reuse an expired session and having to start a new one requires no 39 * more effort than starting a new one, so you might as well try to reuse 40 * one on the off chance it's still valid. 41 */ 42 43 /** Sessions indexed by host and port in access order. */ 44 final Map<HostAndPort, SSLSession> sessions 45 = new LinkedHashMap<HostAndPort, SSLSession>() { 46 @Override 47 protected boolean removeEldestEntry( 48 Map.Entry<HostAndPort, SSLSession> eldest) { 49 // Called while lock is held on sessions. 50 boolean remove = maximumSize > 0 && size() > maximumSize; 51 if (remove) { 52 removeById(eldest.getValue()); 53 } 54 return remove; 55 } 56 }; 57 58 /** 59 * Sessions indexed by ID. Initialized on demand. Protected from concurrent 60 * access by holding a lock on sessions. 61 */ 62 Map<ByteArray, SSLSession> sessionsById; 63 64 final SSLClientSessionCache persistentCache; 65 66 public ClientSessionContext(int sslCtxNativePointer, 67 SSLClientSessionCache persistentCache) { 68 super(sslCtxNativePointer, 10, 0); 69 this.persistentCache = persistentCache; 70 } 71 72 public final void setSessionTimeout(int seconds) 73 throws IllegalArgumentException { 74 if (seconds < 0) { 75 throw new IllegalArgumentException("seconds < 0"); 76 } 77 timeout = seconds; 78 } 79 80 Iterator<SSLSession> sessionIterator() { 81 synchronized (sessions) { 82 SSLSession[] array = sessions.values().toArray( 83 new SSLSession[sessions.size()]); 84 return Arrays.asList(array).iterator(); 85 } 86 } 87 88 void trimToSize() { 89 synchronized (sessions) { 90 int size = sessions.size(); 91 if (size > maximumSize) { 92 int removals = size - maximumSize; 93 Iterator<SSLSession> i = sessions.values().iterator(); 94 do { 95 removeById(i.next()); 96 i.remove(); 97 } while (--removals > 0); 98 } 99 } 100 } 101 102 void removeById(SSLSession session) { 103 if (sessionsById != null) { 104 sessionsById.remove(new ByteArray(session.getId())); 105 } 106 } 107 108 /** 109 * {@inheritDoc} 110 * 111 * @see #getSession(String, int) for an implementation-specific but more 112 * efficient approach 113 */ 114 public SSLSession getSession(byte[] sessionId) { 115 /* 116 * This method is typically used in conjunction with getIds() to 117 * iterate over the sessions linearly, so it doesn't make sense for 118 * it to impact access order. 119 * 120 * It also doesn't load sessions from the persistent cache as doing 121 * so would likely force every session to load. 122 */ 123 124 ByteArray id = new ByteArray(sessionId); 125 synchronized (sessions) { 126 indexById(); 127 return sessionsById.get(id); 128 } 129 } 130 131 /** 132 * Ensures that the ID-based index is initialized. 133 */ 134 private void indexById() { 135 if (sessionsById == null) { 136 sessionsById = new HashMap<ByteArray, SSLSession>(); 137 for (SSLSession session : sessions.values()) { 138 sessionsById.put(new ByteArray(session.getId()), session); 139 } 140 } 141 } 142 143 /** 144 * Adds the given session to the ID-based index if the index has already 145 * been initialized. 146 */ 147 private void indexById(byte[] id, SSLSession session) { 148 if (sessionsById != null) { 149 sessionsById.put(new ByteArray(id), session); 150 } 151 } 152 153 /** 154 * Finds a cached session for the given host name and port. 155 * 156 * @param host of server 157 * @param port of server 158 * @return cached session or null if none found 159 */ 160 public SSLSession getSession(String host, int port) { 161 synchronized (sessions) { 162 SSLSession session = sessions.get(new HostAndPort(host, port)); 163 if (session != null) { 164 return session; 165 } 166 } 167 168 // Look in persistent cache. 169 if (persistentCache != null) { 170 byte[] data = persistentCache.getSessionData(host, port); 171 if (data != null) { 172 SSLSession session = toSession(data, host, port); 173 if (session != null) { 174 synchronized (sessions) { 175 sessions.put(new HostAndPort(host, port), session); 176 indexById(session.getId(), session); 177 } 178 return session; 179 } 180 } 181 } 182 183 return null; 184 } 185 186 @Override 187 void putSession(SSLSession session) { 188 byte[] id = session.getId(); 189 if (id.length == 0) { 190 return; 191 } 192 HostAndPort key = new HostAndPort(session.getPeerHost(), 193 session.getPeerPort()); 194 synchronized (sessions) { 195 sessions.put(key, session); 196 indexById(id, session); 197 } 198 199 // TODO: This in a background thread. 200 if (persistentCache != null) { 201 byte[] data = toBytes(session); 202 if (data != null) { 203 persistentCache.putSessionData(session, data); 204 } 205 } 206 } 207 208 static class HostAndPort { 209 final String host; 210 final int port; 211 212 HostAndPort(String host, int port) { 213 this.host = host; 214 this.port = port; 215 } 216 217 @Override 218 public int hashCode() { 219 return host.hashCode() * 31 + port; 220 } 221 222 @Override 223 @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") 224 public boolean equals(Object o) { 225 HostAndPort other = (HostAndPort) o; 226 return host.equals(other.host) && port == other.port; 227 } 228 } 229} 230