View Javadoc
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.http.impl.client.cache;
28  
29  import java.io.IOException;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Date;
33  import java.util.List;
34  import java.util.ListIterator;
35  
36  import org.apache.http.Header;
37  import org.apache.http.HttpResponse;
38  import org.apache.http.HttpStatus;
39  import org.apache.http.annotation.Contract;
40  import org.apache.http.annotation.ThreadingBehavior;
41  import org.apache.http.client.cache.HeaderConstants;
42  import org.apache.http.client.cache.HttpCacheEntry;
43  import org.apache.http.client.cache.Resource;
44  import org.apache.http.client.cache.ResourceFactory;
45  import org.apache.http.client.utils.DateUtils;
46  import org.apache.http.protocol.HTTP;
47  import org.apache.http.util.Args;
48  
49  /**
50   * Update a {@link HttpCacheEntry} with new or updated information based on the latest
51   * 304 status response from the Server.  Use the {@link HttpResponse} to perform
52   * the update.
53   *
54   * @since 4.1
55   */
56  @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
57  class CacheEntryUpdater {
58  
59      private final ResourceFactory resourceFactory;
60  
61      CacheEntryUpdater() {
62          this(new HeapResourceFactory());
63      }
64  
65      CacheEntryUpdater(final ResourceFactory resourceFactory) {
66          super();
67          this.resourceFactory = resourceFactory;
68      }
69  
70      /**
71       * Update the entry with the new information from the response.  Should only be used for
72       * 304 responses.
73       *
74       * @param requestId
75       * @param entry The cache Entry to be updated
76       * @param requestDate When the request was performed
77       * @param responseDate When the response was gotten
78       * @param response The HttpResponse from the backend server call
79       * @return HttpCacheEntry an updated version of the cache entry
80       * @throws java.io.IOException if something bad happens while trying to read the body from the original entry
81       */
82      public HttpCacheEntry updateCacheEntry(
83              final String requestId,
84              final HttpCacheEntry entry,
85              final Date requestDate,
86              final Date responseDate,
87              final HttpResponse response) throws IOException {
88          Args.check(response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED,
89                  "Response must have 304 status code");
90          final Header[] mergedHeaders = mergeHeaders(entry, response);
91          Resource resource = null;
92          if (entry.getResource() != null) {
93              resource = resourceFactory.copy(requestId, entry.getResource());
94          }
95          return new HttpCacheEntry(
96                  requestDate,
97                  responseDate,
98                  entry.getStatusLine(),
99                  mergedHeaders,
100                 resource,
101                 entry.getRequestMethod());
102     }
103 
104     protected Header[] mergeHeaders(final HttpCacheEntry entry, final HttpResponse response) {
105 
106         if (entryAndResponseHaveDateHeader(entry, response)
107                 && entryDateHeaderNewerThenResponse(entry, response)) {
108             // Don't merge headers, keep the entry's headers as they are newer.
109             return entry.getAllHeaders();
110         }
111 
112         final List<Header> cacheEntryHeaderList = new ArrayList<Header>(Arrays.asList(entry
113                 .getAllHeaders()));
114         removeCacheHeadersThatMatchResponse(cacheEntryHeaderList, response);
115         removeCacheEntry1xxWarnings(cacheEntryHeaderList, entry);
116         cacheEntryHeaderList.addAll(Arrays.asList(response.getAllHeaders()));
117 
118         return cacheEntryHeaderList.toArray(new Header[cacheEntryHeaderList.size()]);
119     }
120 
121     private void removeCacheHeadersThatMatchResponse(final List<Header> cacheEntryHeaderList,
122             final HttpResponse response) {
123         for (final Header responseHeader : response.getAllHeaders()) {
124             final ListIterator<Header> cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator();
125 
126             while (cacheEntryHeaderListIter.hasNext()) {
127                 final String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName();
128 
129                 if (cacheEntryHeaderName.equals(responseHeader.getName())) {
130                     cacheEntryHeaderListIter.remove();
131                 }
132             }
133         }
134     }
135 
136     private void removeCacheEntry1xxWarnings(final List<Header> cacheEntryHeaderList, final HttpCacheEntry entry) {
137         final ListIterator<Header> cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator();
138 
139         while (cacheEntryHeaderListIter.hasNext()) {
140             final String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName();
141 
142             if (HeaderConstants.WARNING.equals(cacheEntryHeaderName)) {
143                 for (final Header cacheEntryWarning : entry.getHeaders(HeaderConstants.WARNING)) {
144                     if (cacheEntryWarning.getValue().startsWith("1")) {
145                         cacheEntryHeaderListIter.remove();
146                     }
147                 }
148             }
149         }
150     }
151 
152     private boolean entryDateHeaderNewerThenResponse(final HttpCacheEntry entry, final HttpResponse response) {
153         final Date entryDate = DateUtils.parseDate(entry.getFirstHeader(HTTP.DATE_HEADER)
154                 .getValue());
155         final Date responseDate = DateUtils.parseDate(response.getFirstHeader(HTTP.DATE_HEADER)
156                 .getValue());
157         if (entryDate == null || responseDate == null) {
158             return false;
159         }
160         if (!entryDate.after(responseDate)) {
161             return false;
162         }
163         return true;
164     }
165 
166     private boolean entryAndResponseHaveDateHeader(final HttpCacheEntry entry, final HttpResponse response) {
167         if (entry.getFirstHeader(HTTP.DATE_HEADER) != null
168                 && response.getFirstHeader(HTTP.DATE_HEADER) != null) {
169             return true;
170         }
171 
172         return false;
173     }
174 
175 }