View Javadoc

1   /*
2    * Copyright (c) 2009-2012 The 99 Software Foundation
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20   * THE SOFTWARE.
21   */
22  package org.nnsoft.sameas4j;
23  
24  import static java.lang.String.format;
25  import java.io.IOException;
26  import java.io.InputStreamReader;
27  import java.io.Reader;
28  import java.net.HttpURLConnection;
29  import java.net.MalformedURLException;
30  import java.net.URI;
31  import java.net.URL;
32  import java.net.URLConnection;
33  import java.util.concurrent.locks.Lock;
34  import java.util.concurrent.locks.ReentrantLock;
35  
36  import org.nnsoft.sameas4j.cache.Cache;
37  import org.nnsoft.sameas4j.cache.CacheKey;
38  
39  import com.google.gson.Gson;
40  import com.google.gson.GsonBuilder;
41  import com.google.gson.JsonParseException;
42  
43  /**
44   * Default implementation of {@link org.nnsoft.sameas4j.SameAsService}.
45   */
46  final class SameAsServiceImpl implements SameAsService {
47  
48      /**
49       * The sameas.org service for looking up URLs template constant.
50       */
51      private static final String SERVICE_URL = "http://sameas.org/json?uri=%s";
52  
53      /**
54       * The sameas.org service for looking up keywords template constant.
55       */
56      private static final String SERVICE_KEYWORD = "http://sameas.org/json?q=%s";
57  
58      /**
59       * The GsonBuilder used to create new equivalence JSON parser.
60       */
61      private final GsonBuilder gsonBuilder = new GsonBuilder();
62  
63      /**
64       * Cache lock.
65       */
66      private final Lock lock = new ReentrantLock();
67  
68      /**
69       * The {@code Cache} reference, can be null.
70       */
71      private Cache cache;
72  
73      /**
74       * Creates a new {@link org.nnsoft.sameas4j.SameAsService} instance.
75       */
76      public SameAsServiceImpl() {
77          this.gsonBuilder.registerTypeAdapter(Equivalence.class, new EquivalenceDeserializer());
78          this.gsonBuilder.registerTypeAdapter(EquivalenceList.class, new EquivalenceListDeserializer());
79      }
80  
81      /**
82       * {@inheritDoc}
83       */
84      public void setCache(Cache cache) {
85          this.cache = cache;
86      }
87  
88      /**
89       * {@inheritDoc}
90       */
91      public Equivalence getDuplicates(URI uri) throws SameAsServiceException {
92          return invokeURL(format(SERVICE_URL, uri), Equivalence.class);
93      }
94  
95      /**
96       * {@inheritDoc}
97       */
98      public EquivalenceList getDuplicates(String keyword) throws SameAsServiceException {
99          return invokeURL(format(SERVICE_KEYWORD, keyword), EquivalenceList.class);
100     }
101 
102     /**
103      * Invokes a Sameas.org service URL and parses the JSON response.
104      *
105      * @param <T> the expected return type.
106      * @param toBeInvoked the service URL has to be invoked.
107      * @param returnType the type the JSON response has to be bind to.
108      * @return the bound object.
109      * @throws SameAsServiceException is any error occurs.
110      */
111     private <T> T invokeURL(String toBeInvoked, Class<T> returnType) throws SameAsServiceException {
112         URL url;
113         try {
114             url = new URL(toBeInvoked);
115         } catch (MalformedURLException e) {
116             throw new SameAsServiceException("An error occurred while building the URL '"
117                     + toBeInvoked
118                     + "'");
119         }
120 
121         URLConnection connection = null;
122         Reader reader = null;
123 
124         if (this.cache != null) {
125             lock.lock();
126         }
127 
128         try {
129             connection = url.openConnection();
130             long lastModified = connection.getLastModified();
131 
132             if (this.cache != null) {
133                 CacheKey cacheKey = new CacheKey(toBeInvoked, lastModified);
134                 T cached = this.cache.get(cacheKey, returnType);
135                 if (cached != null) {
136                     return cached;
137                 }
138             }
139 
140             if (connection instanceof HttpURLConnection) {
141                 ((HttpURLConnection) connection).connect();
142             }
143 
144             reader = new InputStreamReader(connection.getInputStream());
145             Gson gson = this.gsonBuilder.create();
146             T response = gson.fromJson(reader, returnType);
147 
148             if (this.cache != null) {
149                 CacheKey cacheKey = new CacheKey(toBeInvoked, lastModified);
150                 this.cache.put(cacheKey, response);
151             }
152 
153             return response;
154         } catch (IOException e) {
155             throw new SameAsServiceException(format("An error occurred while invoking the URL '%s': %s",
156                     toBeInvoked,
157                     e.getMessage()));
158         } catch (JsonParseException e) {
159             throw new SameAsServiceException("An error occurred while parsing the JSON response", e);
160         } finally {
161             if (this.cache != null) {
162                 lock.unlock();
163             }
164 
165             if (connection != null && connection instanceof HttpURLConnection) {
166                 ((HttpURLConnection) connection).disconnect();
167             }
168             if (reader != null) {
169                 try {
170                     reader.close();
171                 } catch (IOException e) {
172                     // close it quietly
173                 }
174             }
175         }
176     }
177 
178 }