1 /* 2 * Copyright (C) 2010 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 17 package tests.http; 18 19 import java.io.ByteArrayInputStream; 20 import java.io.ByteArrayOutputStream; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.OutputStream; 24 import java.net.CacheRequest; 25 import java.net.CacheResponse; 26 import java.net.ResponseCache; 27 import java.net.URI; 28 import java.net.URLConnection; 29 import java.util.ArrayList; 30 import java.util.HashMap; 31 import java.util.LinkedHashMap; 32 import java.util.List; 33 import java.util.Map; 34 35 /** 36 * Cache all responses in memory by URI. 37 */ 38 public final class DefaultResponseCache extends ResponseCache { 39 private final Map<URI, Entry> entries = new HashMap<URI, Entry>(); 40 private int abortCount; 41 private int successCount; 42 private int hitCount; 43 private int missCount; 44 45 @Override public synchronized CacheResponse get(URI uri, String requestMethod, 46 Map<String, List<String>> requestHeaders) throws IOException { 47 // TODO: honor the request headers in the cache key 48 Entry entry = entries.get(uri); 49 if (entry != null) { 50 hitCount++; 51 return entry.asResponse(); 52 } else { 53 missCount++; 54 return null; 55 } 56 } 57 58 @Override public CacheRequest put(URI uri, URLConnection urlConnection) 59 throws IOException { 60 // TODO: honor the response headers for cache invalidation 61 return new Entry(uri, urlConnection).asRequest(); 62 } 63 64 public synchronized Map<URI, Entry> getContents() { 65 return new HashMap<URI, Entry>(entries); 66 } 67 68 /** 69 * Returns the number of requests that were aborted before they were closed. 70 */ 71 public synchronized int getAbortCount() { 72 return abortCount; 73 } 74 75 /** 76 * Returns the number of requests that were closed successfully. 77 */ 78 public synchronized int getSuccessCount() { 79 return successCount; 80 } 81 82 /** 83 * Returns the number of responses served by the cache. 84 */ 85 public synchronized int getHitCount() { 86 return hitCount; 87 } 88 89 /** 90 * Returns the number of responses that couldn't be served by the cache. 91 */ 92 public synchronized int getMissCount() { 93 return missCount; 94 } 95 96 public final class Entry { 97 private final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream() { 98 private boolean closed; 99 @Override public void close() throws IOException { 100 synchronized (DefaultResponseCache.this) { 101 if (closed) { 102 return; 103 } 104 105 super.close(); 106 entries.put(uri, Entry.this); 107 successCount++; 108 closed = true; 109 } 110 } 111 }; 112 private final Map<String, List<String>> headers; 113 private final URI uri; 114 115 private Entry(URI uri, URLConnection conn) { 116 this.uri = uri; 117 this.headers = deepCopy(conn.getHeaderFields()); 118 } 119 120 public CacheRequest asRequest() { 121 return new CacheRequest() { 122 private boolean aborted; 123 @Override public void abort() { 124 synchronized (DefaultResponseCache.this) { 125 if (aborted) { 126 return; 127 } 128 129 abortCount++; 130 aborted = true; 131 } 132 } 133 @Override public OutputStream getBody() throws IOException { 134 return bytesOut; 135 } 136 }; 137 } 138 139 public CacheResponse asResponse() { 140 return new CacheResponse() { 141 @Override public InputStream getBody() throws IOException { 142 return new ByteArrayInputStream(getBytes()); 143 } 144 @Override public Map<String, List<String>> getHeaders() throws IOException { 145 return deepCopy(headers); 146 } 147 }; 148 } 149 150 public byte[] getBytes() { 151 return bytesOut.toByteArray(); 152 } 153 } 154 155 private static Map<String, List<String>> deepCopy(Map<String, List<String>> input) { 156 Map<String, List<String>> result = new LinkedHashMap<String, List<String>>(input); 157 for (Map.Entry<String, List<String>> entry : result.entrySet()) { 158 entry.setValue(new ArrayList<String>(entry.getValue())); 159 } 160 return result; 161 } 162 } 163