1 /*
2 * ====================================================================
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 * ====================================================================
20 *
21 * This software consists of voluntary contributions made by many
22 * individuals on behalf of the Apache Software Foundation. For more
23 * information on the Apache Software Foundation, please see
24 * <http://www.apache.org/>.
25 *
26 */
27 package org.apache.hc.client5.http.cookie;
28
29 import java.io.IOException;
30 import java.io.ObjectInputStream;
31 import java.io.Serializable;
32 import java.time.Instant;
33 import java.util.ArrayList;
34 import java.util.Date;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.TreeSet;
38 import java.util.concurrent.locks.ReadWriteLock;
39 import java.util.concurrent.locks.ReentrantReadWriteLock;
40
41 import org.apache.hc.core5.annotation.Contract;
42 import org.apache.hc.core5.annotation.ThreadingBehavior;
43
44 /**
45 * Default implementation of {@link CookieStore}
46 *
47 * @since 4.0
48 */
49 @Contract(threading = ThreadingBehavior.SAFE)
50 public class BasicCookieStore implements CookieStore, Serializable {
51
52 private static final long serialVersionUID = -7581093305228232025L;
53
54 private final TreeSet<Cookie> cookies;
55 private transient ReadWriteLock lock;
56
57 public BasicCookieStore() {
58 super();
59 this.cookies = new TreeSet<>(CookieIdentityComparator.INSTANCE);
60 this.lock = new ReentrantReadWriteLock();
61 }
62
63 private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException {
64 stream.defaultReadObject();
65
66 /* Reinstantiate transient fields. */
67 this.lock = new ReentrantReadWriteLock();
68 }
69
70 /**
71 * Adds an {@link Cookie HTTP cookie}, replacing any existing equivalent cookies.
72 * If the given cookie has already expired it will not be added, but existing
73 * values will still be removed.
74 *
75 * @param cookie the {@link Cookie cookie} to be added
76 *
77 * @see #addCookies(Cookie[])
78 *
79 */
80 @Override
81 public void addCookie(final Cookie cookie) {
82 if (cookie != null) {
83 lock.writeLock().lock();
84 try {
85 // first remove any old cookie that is equivalent
86 cookies.remove(cookie);
87 if (!cookie.isExpired(Instant.now())) {
88 cookies.add(cookie);
89 }
90 } finally {
91 lock.writeLock().unlock();
92 }
93 }
94 }
95
96 /**
97 * Adds an array of {@link Cookie HTTP cookies}. Cookies are added individually and
98 * in the given array order. If any of the given cookies has already expired it will
99 * not be added, but existing values will still be removed.
100 *
101 * @param cookies the {@link Cookie cookies} to be added
102 *
103 * @see #addCookie(Cookie)
104 *
105 */
106 public void addCookies(final Cookie[] cookies) {
107 if (cookies != null) {
108 for (final Cookie cookie : cookies) {
109 this.addCookie(cookie);
110 }
111 }
112 }
113
114 /**
115 * Returns an immutable array of {@link Cookie cookies} that this HTTP
116 * state currently contains.
117 *
118 * @return an array of {@link Cookie cookies}.
119 */
120 @Override
121 public List<Cookie> getCookies() {
122 lock.readLock().lock();
123 try {
124 //create defensive copy so it won't be concurrently modified
125 return new ArrayList<>(cookies);
126 } finally {
127 lock.readLock().unlock();
128 }
129 }
130
131 /**
132 * Removes all of {@link Cookie cookies} in this HTTP state
133 * that have expired by the specified {@link java.util.Date date}.
134 *
135 * @return true if any cookies were purged.
136 *
137 * @see Cookie#isExpired(Date)
138 */
139 @Override
140 @SuppressWarnings("deprecation")
141 public boolean clearExpired(final Date date) {
142 if (date == null) {
143 return false;
144 }
145 lock.writeLock().lock();
146 try {
147 boolean removed = false;
148 for (final Iterator<Cookie> it = cookies.iterator(); it.hasNext(); ) {
149 if (it.next().isExpired(date)) {
150 it.remove();
151 removed = true;
152 }
153 }
154 return removed;
155 } finally {
156 lock.writeLock().unlock();
157 }
158 }
159
160 /**
161 * Removes all of {@link Cookie cookies} in this HTTP state that have expired by the specified
162 * {@link Instant date}.
163 *
164 * @return true if any cookies were purged.
165 * @see Cookie#isExpired(Instant)
166 * @since 5.2
167 */
168 @Override
169 public boolean clearExpired(final Instant instant) {
170 if (instant == null) {
171 return false;
172 }
173 lock.writeLock().lock();
174 try {
175 boolean removed = false;
176 for (final Iterator<Cookie> it = cookies.iterator(); it.hasNext(); ) {
177 if (it.next().isExpired(instant)) {
178 it.remove();
179 removed = true;
180 }
181 }
182 return removed;
183 } finally {
184 lock.writeLock().unlock();
185 }
186 }
187
188 /**
189 * Clears all cookies.
190 */
191 @Override
192 public void clear() {
193 lock.writeLock().lock();
194 try {
195 cookies.clear();
196 } finally {
197 lock.writeLock().unlock();
198 }
199 }
200
201 @Override
202 public String toString() {
203 lock.readLock().lock();
204 try {
205 return cookies.toString();
206 } finally {
207 lock.readLock().unlock();
208 }
209 }
210
211 }