View Javadoc
1   package de.juplo.httpresources;
2   
3   import org.springframework.http.HttpHeaders;
4   import org.springframework.util.MimeType;
5   
6   import java.io.*;
7   import java.net.URI;
8   import java.net.URL;
9   import java.time.Clock;
10  import java.util.Objects;
11  
12  
13  public class HttpResource implements org.springframework.web.servlet.resource.HttpResource
14  {
15    final HttpResources resources;
16    final HttpResourceFetcher fetcher;
17    final Clock clock;
18    final URI uri;
19  
20    HttpData data;
21    boolean modified;
22  
23    HttpResource(
24        HttpResources resources,
25        HttpResourceFetcher fetcher,
26        Clock clock,
27        URI uri)
28    {
29      this(resources, fetcher, clock, uri, HttpData.NOT_FETCHED);
30    }
31  
32    HttpResource(
33          HttpResources resources,
34          HttpResourceFetcher fetcher,
35          Clock clock,
36          URI uri,
37          HttpData data)
38      {
39      this.resources = resources;
40      this.fetcher = fetcher;
41      this.clock = clock;
42      this.uri = uri;
43      this.data = data;
44    }
45  
46  
47    synchronized public long expires()
48    {
49      return data.expires;
50    }
51  
52    synchronized public boolean isExpired()
53    {
54      return data.expires < clock.millis();
55    }
56  
57    @Override
58    synchronized public boolean exists()
59    {
60      if (isExpired())
61        fetch();
62      return data.content != null;
63    }
64  
65    @Override
66    synchronized public boolean isReadable()
67    {
68      return exists();
69    }
70  
71    @Override
72    public boolean isOpen()
73    {
74      return false;
75    }
76  
77    @Override
78    public URL getURL() throws IOException
79    {
80      return uri.toURL();
81    }
82  
83    @Override
84    public URI getURI()
85    {
86      return uri;
87    }
88  
89    @Override
90    public File getFile() throws IOException
91    {
92      throw new FileNotFoundException("The resource " + getDescription() + " cannot be retrived as file");
93    }
94  
95    @Override
96    synchronized public long contentLength() throws IOException
97    {
98      if (!fetched())
99        fetch();
100 
101     if (data.content == null)
102       throw new FileNotFoundException("The resource " + getDescription() + " cannot be retrived");
103 
104     return data.content.length;
105   }
106 
107   synchronized public MimeType contentType()
108   {
109     return data.headers.getContentType();
110   }
111 
112   @Override
113   synchronized public long lastModified() throws IOException
114   {
115     if (!fetched())
116       fetch();
117 
118     if (data.content == null)
119       throw new FileNotFoundException("The resource " + getDescription() + " cannot be retrived");
120 
121     return data.lastModified;
122   }
123 
124   synchronized public String eTag()
125   {
126     return data.eTag;
127   }
128 
129   @Override
130   public HttpResource createRelative(String relativePath) throws IOException
131   {
132     try
133     {
134       return createRelative(HttpResources.convert(relativePath));
135     }
136     catch (IllegalArgumentException e)
137     {
138       throw new IOException("Invalid relative path: " + relativePath, e);
139     }
140   }
141 
142   public HttpResource createRelative(URI relative) throws IOException
143   {
144     return resources.getResource(HttpResources.resolve(relative, uri));
145   }
146 
147   @Override
148   public String getFilename()
149   {
150     return uri.getPath();
151   }
152 
153   @Override
154   public String getDescription()
155   {
156     return "HTTP [" + this.uri + "]";
157   }
158 
159   @Override
160   synchronized public InputStream getInputStream() throws IOException
161   {
162     if (!fetched())
163     {
164       fetch();
165     }
166     else
167     {
168       isModified();
169     }
170 
171     // Clear the modified-flag
172     modified = false;
173 
174     if (data.content == null)
175       throw new FileNotFoundException("The resource " + getDescription() + " cannot be retrived as file");
176 
177     return new ByteArrayInputStream(data.content);
178   }
179 
180   synchronized public boolean isModified()
181   {
182     if (modified)
183     {
184       return true;
185     }
186 
187     if (!isExpired())
188     {
189       return false;
190     }
191 
192     return fetch();
193   }
194 
195   /**
196    * Checks, if the remote resource was already fetched.
197    *
198    * @return {@code true}, if the resource was already fetched, otherwise
199    * {@code false}
200    */
201   synchronized public boolean fetched()
202   {
203     return data.expires != Long.MIN_VALUE;
204   }
205 
206   /**
207    * Fetches the remote resource and reports, if it was modified.
208    * <p>
209    * This method fetches the remote resource, if was not already fetched.
210    * If the resource was already fetched, it revalidates it, if necessary.
211    *
212    * @return {@code true}, if the resource has changed or was fetched for
213    * the first time, otherwise {@code false}
214    */
215   synchronized public boolean fetch()
216   {
217     HttpData result = fetcher.fetch(uri, data);
218 
219     // Flag the resource as modified, if the data has changed
220     // Otherwise, keep the old state of the modified-flag.
221     modified = !data.equals(result);
222 
223     data = result;
224 
225     return modified;
226   }
227 
228   @Override
229   synchronized public HttpHeaders getResponseHeaders()
230   {
231     return data.headers;
232   }
233 
234 
235   @Override
236   public int hashCode()
237   {
238     return uri.hashCode();
239   }
240 
241   @Override
242   public boolean equals(Object o)
243   {
244     if (o == null)
245       return false;
246     if (o == this)
247       return true;
248     if (!(o instanceof HttpResource))
249       return false;
250     return Objects.equals(uri, ((HttpResource) o).uri);
251   }
252 
253   @Override
254   public String toString()
255   {
256     return getDescription();
257   }
258 }