JAL-3829 mocking for testing 3D-Beacons FTS endpoint
[jalview.git] / src / jalview / fts / service / threedbeacons / TDBeaconsFTSRestClient.java
1 package jalview.fts.service.threedbeacons;
2
3 import java.net.URI;
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.Iterator;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Objects;
10
11 import javax.ws.rs.core.MediaType;
12
13 import org.json.simple.parser.ParseException;
14
15 import com.sun.jersey.api.client.Client;
16 import com.sun.jersey.api.client.ClientResponse;
17 import com.sun.jersey.api.client.WebResource;
18 import com.sun.jersey.api.client.config.DefaultClientConfig;
19
20 import jalview.datamodel.SequenceI;
21 import jalview.fts.api.FTSData;
22 import jalview.fts.api.FTSDataColumnI;
23 import jalview.fts.api.FTSRestClientI;
24 import jalview.fts.api.StructureFTSRestClientI;
25 import jalview.fts.core.FTSRestClient;
26 import jalview.fts.core.FTSRestRequest;
27 import jalview.fts.core.FTSRestResponse;
28 import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
29 import jalview.fts.service.pdb.PDBFTSRestClient;
30 import jalview.util.JSONUtils;
31 import jalview.util.MessageManager;
32 import jalview.util.Platform;
33
34 public class TDBeaconsFTSRestClient extends FTSRestClient
35         implements StructureFTSRestClientI
36 {
37   private static final String DEFAULT_THREEDBEACONS_DOMAIN = "https://wwwdev.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/";
38
39   private static FTSRestClientI instance = null;
40
41   protected TDBeaconsFTSRestClient()
42   {
43   }
44   public static void createMockTDBeaconsFSRestClient(String workingQuery, String jsonResponse) {
45     instance = new TDBeaconsFTSRestClient();
46     ((TDBeaconsFTSRestClient) instance).setMock(workingQuery,jsonResponse);
47   }
48   private String mockQuery = null;
49   
50   private String mockResponse = null;
51   protected void setMock(String workingQuery,String jsonResponse) {
52     mockQuery=workingQuery;
53     mockResponse = jsonResponse;
54   }
55   private boolean isMocked()
56   {
57     return mockQuery != null && mockResponse!=null;
58   }
59   @SuppressWarnings("unchecked")
60   @Override
61   public FTSRestResponse executeRequest(FTSRestRequest tdbRestRequest)
62           throws Exception
63   {
64     try
65     {
66       String query = tdbRestRequest.getSearchTerm();
67       Client client;
68       Class<ClientResponse> clientResponseClass;
69       if (Platform.isJS())
70       {
71         // JavaScript only
72         client = (Client) (Object) new jalview.javascript.web.Client();
73         clientResponseClass = (Class<ClientResponse>) (Object) jalview.javascript.web.ClientResponse.class;
74       }
75       else
76       /**
77        * Java only
78        * 
79        * @j2sIgnore
80        */
81       {
82         client = Client.create(new DefaultClientConfig());
83         clientResponseClass = ClientResponse.class;
84       }
85       WebResource webResource;
86       webResource = client.resource(DEFAULT_THREEDBEACONS_DOMAIN)
87               .path(query);
88       URI uri = webResource.getURI();
89       System.out.println(uri.toString());
90
91       // Execute the REST request
92       ClientResponse clientResponse;
93       if (isMocked()) { 
94         clientResponse = null;
95       }
96       else
97       {
98         clientResponse = webResource.accept(MediaType.APPLICATION_JSON)
99                 .get(clientResponseClass);
100       }
101
102       // Get the JSON string from the response object or directly from the
103       // client (JavaScript)
104       Map<String, Object> jsonObj = null;
105       String responseString = null;
106
107       // Check the response status and report exception if one occurs
108       int responseStatus = isMocked() ? (mockQuery.equals(query) ? 200 : 404) : clientResponse.getStatus();
109       switch (responseStatus)
110       {
111       // if success
112       case 200:
113         if (Platform.isJS())
114         {
115           jsonObj = clientResponse.getEntity(Map.class);
116         }
117         else
118         {
119           responseString = isMocked() ? mockResponse: clientResponse.getEntity(String.class);
120         }
121         break;
122       case 400:
123         throw new Exception(parseJsonExceptionString(responseString));
124       case 404:
125         return emptyTDBeaconsJsonResponse();
126       default:
127         throw new Exception(
128                 getMessageByHTTPStatusCode(responseStatus, "3DBeacons"));
129       }
130       // Process the response and return the result to the caller.
131       return parseTDBeaconsJsonResponse(responseString, jsonObj,
132               tdbRestRequest);
133     } catch (Exception e)
134     {
135       String exceptionMsg = e.getMessage();
136       if (exceptionMsg.contains("SocketException"))
137       {
138         // No internet connection
139         throw new Exception(MessageManager.getString(
140                 "exception.unable_to_detect_internet_connection"));
141       }
142       else if (exceptionMsg.contains("UnknownHostException"))
143       {
144         // The server is unreachable
145         throw new Exception(MessageManager.formatMessage(
146                 "exception.fts_server_unreachable", "3DB Hub"));
147       }
148       else
149       {
150         throw e;
151       }
152     }
153
154   }
155
156   /**
157    * returns response for when the 3D-Beacons service doesn't have a record for
158    * the given query - in 2.11.2 this triggers a failover to the PDBe FTS 
159    * 
160    * @return null
161    */
162   private FTSRestResponse emptyTDBeaconsJsonResponse()
163   {
164     return null;
165   }
166
167   public String setSearchTerm(String term)
168   {
169     return term;
170   }
171
172   public static FTSRestResponse parseTDBeaconsJsonResponse(
173           String tdbJsonResponseString, FTSRestRequest tdbRestRequest)
174   {
175     return parseTDBeaconsJsonResponse(tdbJsonResponseString,
176             (Map<String, Object>) null, tdbRestRequest);
177   }
178
179   @SuppressWarnings("unchecked")
180   public static FTSRestResponse parseTDBeaconsJsonResponse(
181           String tdbJsonResponseString, Map<String, Object> jsonObj,
182           FTSRestRequest tdbRestRequest)
183   {
184     FTSRestResponse searchResult = new FTSRestResponse();
185     List<FTSData> result = null;
186
187     try
188     {
189       if (jsonObj == null)
190       {
191         jsonObj = (Map<String, Object>) JSONUtils
192                 .parse(tdbJsonResponseString);
193       }
194
195       Object uniprot_entry = jsonObj.get("uniprot_entry");
196       // TODO: decide if anything from uniprot_entry needs to be reported via
197       // the FTSRestResponse object
198       // Arnaud added seqLength = (Long) ((Map<String, Object>)
199       // jsonObj.get("uniprot_entry")).get("sequence_length");
200
201       List<Object> structures = (List<Object>) jsonObj.get("structures");
202       result = new ArrayList<>();
203
204       int numFound = 0;
205       for (Iterator<Object> strucIter = structures.iterator(); strucIter
206               .hasNext();)
207       {
208         Map<String, Object> structure = (Map<String, Object>) strucIter
209                 .next();
210         result.add(getFTSData(structure, tdbRestRequest));
211         numFound++;
212       }
213
214       searchResult.setNumberOfItemsFound(numFound);
215       searchResult.setSearchSummary(result);
216
217     } catch (ParseException e)
218     {
219       e.printStackTrace();
220     }
221     return searchResult;
222   }
223
224   private static FTSData getFTSData(Map<String, Object> tdbJsonStructure,
225           FTSRestRequest tdbRequest)
226   {
227     // TODO: consider reusing PDBFTSRestClient.getFTSData ?
228
229     String primaryKey = null;
230     Object[] summaryRowData;
231
232     SequenceI associatedSequence;
233
234     Collection<FTSDataColumnI> displayFields = tdbRequest.getWantedFields();
235     SequenceI associatedSeq = tdbRequest.getAssociatedSequence();
236     int colCounter = 0;
237     summaryRowData = new Object[(associatedSeq != null)
238                                 ? displayFields.size() + 1
239                                 : displayFields.size()];
240                         if (associatedSeq != null)
241                         {
242                           associatedSequence = associatedSeq;
243                           summaryRowData[0] = associatedSequence;
244                           colCounter = 1;
245                         }
246
247     for (FTSDataColumnI field : displayFields)
248     {
249       String fieldData = (tdbJsonStructure.get(field.getCode()) == null)
250               ? " "
251               : tdbJsonStructure.get(field.getCode()).toString();
252       // System.out.println("Field : " + field + " Data : " + fieldData);
253       if (field.isPrimaryKeyColumn())
254       {
255         primaryKey = fieldData;
256         summaryRowData[colCounter++] = primaryKey;
257       }
258       else if (fieldData == null || fieldData.trim().isEmpty())
259       {
260         summaryRowData[colCounter++] = null;
261       }
262       else
263       {
264         try
265         {
266           summaryRowData[colCounter++] = (field.getDataType()
267                   .getDataTypeClass() == Integer.class)
268                           ? Integer.valueOf(fieldData)
269                           : (field.getDataType()
270                                   .getDataTypeClass() == Double.class)
271                                           ? Double.valueOf(fieldData)
272                                           : fieldData;
273         } catch (Exception e)
274         {
275           // e.printStackTrace();
276           System.out.println("offending value:" + fieldData + fieldData);
277         }
278       }
279     }
280     final String primaryKey1 = primaryKey;
281     final Object[] summaryRowData1 = summaryRowData;
282
283     return new FTSData()
284     {
285
286       @Override
287       public Object[] getSummaryData()
288       {
289         return summaryRowData1;
290       }
291
292       @Override
293       public Object getPrimaryKey()
294       {
295         return primaryKey1;
296       }
297
298       /**
299        * Returns a string representation of this object;
300        */
301       @Override
302       public String toString()
303       {
304         StringBuilder summaryFieldValues = new StringBuilder();
305         for (Object summaryField : summaryRowData1)
306         {
307           summaryFieldValues.append(
308                   summaryField == null ? " " : summaryField.toString())
309                   .append("\t");
310         }
311         return summaryFieldValues.toString();
312       }
313
314       /**
315        * Returns hash code value for this object
316        */
317       @Override
318       public int hashCode()
319       {
320         return Objects.hash(primaryKey1, this.toString());
321       }
322
323       @Override
324       public boolean equals(Object that)
325       {
326         return this.toString().equals(that.toString());
327       }
328     };
329   }
330
331   // private static FTSData getFTSData(Map<String, Object> doc,
332   // FTSRestRequest tdbRestRequest)
333   // {
334   // String primaryKey = null;
335   //
336   // Object[] summaryRowData;
337   //
338   // Collection<FTSDataColumnI> displayFields =
339   // tdbRestRequest.getWantedFields();
340   // int colCounter = 0;
341   // summaryRowData = new Object[displayFields.size() + 1];
342   //
343   // return null;
344   // }
345
346   private String parseJsonExceptionString(String jsonErrorString)
347   {
348     // TODO Auto-generated method stub
349     return null;
350   }
351
352   @Override
353   public String getColumnDataConfigFileName()
354   {
355     return "/fts/tdbeacons_data_columns.txt";
356   }
357
358   public static FTSRestClientI getInstance()
359   {
360     if (instance == null)
361     {
362       instance = new TDBeaconsFTSRestClient();
363     }
364     return instance;
365   }
366
367   private Collection<FTSDataColumnI> allDefaultDisplayedStructureDataColumns;
368
369   public Collection<FTSDataColumnI> getAllDefaultDisplayedStructureDataColumns()
370   {
371     if (allDefaultDisplayedStructureDataColumns == null
372             || allDefaultDisplayedStructureDataColumns.isEmpty())
373     {
374       allDefaultDisplayedStructureDataColumns = new ArrayList<>();
375       allDefaultDisplayedStructureDataColumns
376               .addAll(super.getAllDefaultDisplayedFTSDataColumns());
377     }
378     return allDefaultDisplayedStructureDataColumns;
379   }
380
381   @Override
382   public String[] getPreferencesColumnsFor(PreferenceSource source)
383   {
384     String[] columnNames = null;
385     switch (source)
386     {
387     case SEARCH_SUMMARY:
388       columnNames = new String[] { "", "Display", "Group" };
389       break;
390     case STRUCTURE_CHOOSER:
391       columnNames = new String[] { "", "Display", "Group" };
392       break;
393     case PREFERENCES:
394       columnNames = new String[] { "3DB Beacons Field", "Show in search summary",
395           "Show in structure summary" };
396       break;
397     default:
398       break;
399     }
400     return columnNames;
401   }
402 }