483bb2f6bdad2f18c2d19e903eb1903dad7baf5b
[jalview.git] / src / jalview / fts / service / pdb / PDBFTSRestClient.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.fts.service.pdb;
22
23 import java.net.URI;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Objects;
30
31 import javax.ws.rs.core.MediaType;
32
33 import org.json.simple.parser.ParseException;
34
35 import com.sun.jersey.api.client.Client;
36 import com.sun.jersey.api.client.ClientResponse;
37 import com.sun.jersey.api.client.WebResource;
38 import com.sun.jersey.api.client.config.DefaultClientConfig;
39
40 import jalview.datamodel.SequenceI;
41 import jalview.fts.api.FTSData;
42 import jalview.fts.api.FTSDataColumnI;
43 import jalview.fts.api.FTSRestClientI;
44 import jalview.fts.api.StructureFTSRestClientI;
45 import jalview.fts.core.FTSDataColumnPreferences;
46 import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
47 import jalview.fts.core.FTSRestClient;
48 import jalview.fts.core.FTSRestRequest;
49 import jalview.fts.core.FTSRestResponse;
50 import jalview.fts.service.alphafold.AlphafoldRestClient;
51 import jalview.util.JSONUtils;
52 import jalview.util.MessageManager;
53 import jalview.util.Platform;
54
55 /**
56  * A rest client for querying the Search endpoint of the PDB API
57  * 
58  * @author tcnofoegbu
59  */
60 public class PDBFTSRestClient extends FTSRestClient implements StructureFTSRestClientI
61 {
62
63   private static FTSRestClientI instance = null;
64
65   public static final String PDB_SEARCH_ENDPOINT = "https://www.ebi.ac.uk/pdbe/search/pdb/select?";
66
67   protected PDBFTSRestClient()
68   {
69   }
70
71   /**
72    * Takes a PDBRestRequest object and returns a response upon execution
73    * 
74    * @param pdbRestRequest
75    *          the PDBRestRequest instance to be processed
76    * @return the pdbResponse object for the given request
77    * @throws Exception
78    */
79   @SuppressWarnings({ "unused", "unchecked" })
80   @Override
81   public FTSRestResponse executeRequest(FTSRestRequest pdbRestRequest)
82           throws Exception
83   {
84     try
85     {
86       String wantedFields = getDataColumnsFieldsAsCommaDelimitedString(
87               pdbRestRequest.getWantedFields());
88       int responseSize = (pdbRestRequest.getResponseSize() == 0)
89               ? getDefaultResponsePageSize()
90               : pdbRestRequest.getResponseSize();
91       int offSet = pdbRestRequest.getOffSet();
92       String sortParam = null;
93       if (pdbRestRequest.getFieldToSortBy() == null
94               || pdbRestRequest.getFieldToSortBy().trim().isEmpty())
95       {
96         sortParam = "";
97       }
98       else
99       {
100         if (pdbRestRequest.getFieldToSortBy()
101                 .equalsIgnoreCase("Resolution"))
102         {
103           sortParam = pdbRestRequest.getFieldToSortBy()
104                   + (pdbRestRequest.isAscending() ? " asc" : " desc");
105         }
106         else
107         {
108           sortParam = pdbRestRequest.getFieldToSortBy()
109                   + (pdbRestRequest.isAscending() ? " desc" : " asc");
110         }
111       }
112
113       String facetPivot = (pdbRestRequest.getFacetPivot() == null
114               || pdbRestRequest.getFacetPivot().isEmpty()) ? ""
115                       : pdbRestRequest.getFacetPivot();
116       String facetPivotMinCount = String
117               .valueOf(pdbRestRequest.getFacetPivotMinCount());
118
119       String query = pdbRestRequest.getFieldToSearchBy()
120               + pdbRestRequest.getSearchTerm()
121               + (pdbRestRequest.isAllowEmptySeq() ? ""
122                       : " AND molecule_sequence:['' TO *]")
123               + (pdbRestRequest.isAllowUnpublishedEntries() ? ""
124                       : " AND status:REL");
125
126       // Build request parameters for the REST Request
127
128       // BH 2018 the trick here is to coerce the classes in Javascript to be 
129       // different from the ones in Java yet still allow this to be correct for Java
130       Client client;
131       Class<ClientResponse> clientResponseClass;
132       if (Platform.isJS())
133       {
134         // JavaScript only -- coerce types to Java types for Java
135         client = (Client) (Object) new jalview.javascript.web.Client();
136         clientResponseClass = (Class<ClientResponse>) (Object) jalview.javascript.web.ClientResponse.class;
137       }
138       else
139       /**
140        * Java only
141        * 
142        * @j2sIgnore
143        */
144       {
145         client = Client.create(new DefaultClientConfig());
146         clientResponseClass = ClientResponse.class;
147       }
148
149       WebResource webResource;
150       if (pdbRestRequest.isFacet())
151       {
152         webResource = client.resource(PDB_SEARCH_ENDPOINT)
153                 .queryParam("wt", "json").queryParam("fl", wantedFields)
154                 .queryParam("rows", String.valueOf(responseSize))
155                 .queryParam("q", query)
156                 .queryParam("start", String.valueOf(offSet))
157                 .queryParam("sort", sortParam).queryParam("facet", "true")
158                 .queryParam("facet.pivot", facetPivot)
159                 .queryParam("facet.pivot.mincount", facetPivotMinCount);
160       }
161       else
162       {
163         webResource = client.resource(PDB_SEARCH_ENDPOINT)
164                 .queryParam("wt", "json").queryParam("fl", wantedFields)
165                 .queryParam("rows", String.valueOf(responseSize))
166                 .queryParam("start", String.valueOf(offSet))
167                 .queryParam("q", query).queryParam("sort", sortParam);
168       }
169
170       URI uri = webResource.getURI();
171
172       System.out.println(uri);
173       ClientResponse clientResponse =null;
174       int responseStatus = -1; 
175       // Get the JSON string from the response object or directly from the
176       // client (JavaScript)
177       Map<String, Object> jsonObj = null;
178       String responseString = null;
179
180       System.out.println("query >>>>>>> " + pdbRestRequest.toString());
181
182       if (!isMocked())
183       {
184         // Execute the REST request
185         clientResponse = webResource.accept(MediaType.APPLICATION_JSON)
186                 .get(clientResponseClass);
187         responseStatus = clientResponse.getStatus();
188       }
189       else
190       {
191         // mock response
192         if (uri.toString().equals(mockQuery))
193         {
194           responseStatus = 200;
195         }
196         else
197         {
198           // FIXME - may cause unexpected exceptions for callers when mocked
199           responseStatus = 400;
200         }
201       }
202
203       // Check the response status and report exception if one occurs
204       switch (responseStatus)
205       {
206       case 200:
207
208         if (isMocked())
209         {
210           responseString = mockResponse;
211         }
212         else
213         {
214           if (Platform.isJS())
215           {
216             jsonObj = clientResponse.getEntity(Map.class);
217           }
218           else
219           {
220             responseString = clientResponse.getEntity(String.class);
221           }
222         }
223         break;
224       case 400:
225         throw new Exception(parseJsonExceptionString(responseString));
226       default:
227         throw new Exception(
228                 getMessageByHTTPStatusCode(responseStatus, "PDB"));
229       }
230
231       // Process the response and return the result to the caller.
232       return parsePDBJsonResponse(responseString, jsonObj, pdbRestRequest);
233     } catch (Exception e)
234     {
235       if (e.getMessage()==null)
236       {
237         throw(e);
238       }
239       String exceptionMsg = e.getMessage();
240       if (exceptionMsg.contains("SocketException"))
241       {
242         // No internet connection
243         throw new Exception(MessageManager.getString(
244                 "exception.unable_to_detect_internet_connection"));
245       }
246       else if (exceptionMsg.contains("UnknownHostException"))
247       {
248         // The server 'www.ebi.ac.uk' is unreachable
249         throw new Exception(MessageManager.formatMessage(
250                 "exception.fts_server_unreachable", "PDB Solr"));
251       }
252       else
253       {
254         throw e;
255       }
256     }
257   }
258
259   /**
260    * Process error response from PDB server if/when one occurs.
261    * 
262    * @param jsonResponse
263    *          the JSON string containing error message from the server
264    * @return the processed error message from the JSON string
265    */
266   @SuppressWarnings("unchecked")
267 public static String parseJsonExceptionString(String jsonErrorResponse)
268   {
269     StringBuilder errorMessage = new StringBuilder(
270             "\n============= PDB Rest Client RunTime error =============\n");
271
272     
273 //    {
274 //      "responseHeader":{
275 //        "status":0,
276 //        "QTime":0,
277 //        "params":{
278 //          "q":"(text:q93xj9_soltu) AND molecule_sequence:['' TO *] AND status:REL",
279 //          "fl":"pdb_id,title,experimental_method,resolution",
280 //          "start":"0",
281 //          "sort":"overall_quality desc",
282 //          "rows":"500",
283 //          "wt":"json"}},
284 //      "response":{"numFound":1,"start":0,"docs":[
285 //          {
286 //            "experimental_method":["X-ray diffraction"],
287 //            "pdb_id":"4zhp",
288 //            "resolution":2.46,
289 //            "title":"The crystal structure of Potato ferredoxin I with 2Fe-2S cluster"}]
290 //      }}
291 //    
292     try
293     {
294       Map<String, Object> jsonObj = (Map<String, Object>) JSONUtils.parse(jsonErrorResponse);
295       Map<String, Object> errorResponse = (Map<String, Object>) jsonObj.get("error");
296
297       Map<String, Object> responseHeader = (Map<String, Object>) jsonObj
298               .get("responseHeader");
299       Map<String, Object> paramsObj = (Map<String, Object>) responseHeader.get("params");
300       String status = responseHeader.get("status").toString();
301       String message = errorResponse.get("msg").toString();
302       String query = paramsObj.get("q").toString();
303       String fl = paramsObj.get("fl").toString();
304
305       errorMessage.append("Status: ").append(status).append("\n");
306       errorMessage.append("Message: ").append(message).append("\n");
307       errorMessage.append("query: ").append(query).append("\n");
308       errorMessage.append("fl: ").append(fl).append("\n");
309
310     } catch (ParseException e)
311     {
312       e.printStackTrace();
313     }
314     return errorMessage.toString();
315   }
316
317   /**
318    * Parses the JSON response string from PDB REST API. The response is dynamic
319    * hence, only fields specifically requested for in the 'wantedFields'
320    * parameter is fetched/processed
321    * 
322    * @param pdbJsonResponseString
323    *          the JSON string to be parsed
324    * @param pdbRestRequest
325    *          the request object which contains parameters used to process the
326    *          JSON string
327    * @return
328    */
329   public static FTSRestResponse parsePDBJsonResponse(
330           String pdbJsonResponseString, FTSRestRequest pdbRestRequest)
331   {
332     return parsePDBJsonResponse(pdbJsonResponseString,
333             (Map<String, Object>) null, pdbRestRequest);
334   }
335
336   @SuppressWarnings("unchecked")
337   public static FTSRestResponse parsePDBJsonResponse(
338           String pdbJsonResponseString, Map<String, Object> jsonObj,
339           FTSRestRequest pdbRestRequest)
340   {
341     FTSRestResponse searchResult = new FTSRestResponse();
342     List<FTSData> result = null;
343     try
344     {
345       if (jsonObj == null)
346       {
347         jsonObj = (Map<String, Object>) JSONUtils.parse(pdbJsonResponseString);
348       }
349       Map<String, Object> pdbResponse = (Map<String, Object>) jsonObj.get("response");
350       String queryTime = ((Map<String, Object>) jsonObj.get("responseHeader"))
351               .get("QTime").toString();
352       int numFound = Integer
353               .valueOf(pdbResponse.get("numFound").toString());
354       List<Object> docs = (List<Object>) pdbResponse.get("docs");
355
356       result = new ArrayList<FTSData>(); 
357       if (numFound > 0)
358       {
359
360         for (Iterator<Object> docIter = docs.iterator(); docIter
361                 .hasNext();)
362         {
363           Map<String, Object> doc = (Map<String, Object>) docIter.next();
364           result.add(getFTSData(doc, pdbRestRequest));
365         }
366       }
367       searchResult.setNumberOfItemsFound(result.size());
368       searchResult.setResponseTime(queryTime);
369       searchResult.setSearchSummary(result);
370
371     } catch (ParseException e)
372     {
373       e.printStackTrace();
374     }
375     return searchResult;
376   }
377
378   public static FTSData getFTSData(Map<String, Object> pdbJsonDoc,
379           FTSRestRequest request)
380   {
381
382     String primaryKey = null;
383
384     Object[] summaryRowData;
385
386     SequenceI associatedSequence;
387
388     Collection<FTSDataColumnI> diplayFields = request.getWantedFields();
389     SequenceI associatedSeq = request.getAssociatedSequence();
390     int colCounter = 0;
391     summaryRowData = new Object[(associatedSeq != null)
392             ? diplayFields.size() + 1
393             : diplayFields.size()];
394     if (associatedSeq != null)
395     {
396       associatedSequence = associatedSeq;
397       summaryRowData[0] = associatedSequence;
398       colCounter = 1;
399     }
400
401     for (FTSDataColumnI field : diplayFields)
402     {
403       //System.out.println("Field " + field);
404       String fieldData = (pdbJsonDoc.get(field.getCode()) == null) ? ""
405               : pdbJsonDoc.get(field.getCode()).toString();
406       //System.out.println("Field Data : " + fieldData);
407       if (field.isPrimaryKeyColumn())
408       {
409         primaryKey = fieldData;
410         summaryRowData[colCounter++] = primaryKey;
411       }
412       else if (fieldData == null || fieldData.isEmpty())
413       {
414         summaryRowData[colCounter++] = null;
415       }
416       else
417       {
418         try
419         {
420           summaryRowData[colCounter++] = (field.getDataType()
421                   .getDataTypeClass() == Integer.class)
422                           ? Integer.valueOf(fieldData)
423                           : (field.getDataType()
424                                   .getDataTypeClass() == Double.class)
425                                           ? Double.valueOf(fieldData)
426                                           : sanitiseData(fieldData);
427         } catch (Exception e)
428         {
429           e.printStackTrace();
430           System.out.println("offending value:" + fieldData);
431         }
432       }
433     }
434
435     final String primaryKey1 = primaryKey;
436
437     final Object[] summaryRowData1 = summaryRowData;
438     return new FTSData()
439     {
440       @Override
441       public Object[] getSummaryData()
442       {
443         return summaryRowData1;
444       }
445
446       @Override
447       public Object getPrimaryKey()
448       {
449         return primaryKey1;
450       }
451
452       /**
453        * Returns a string representation of this object;
454        */
455       @Override
456       public String toString()
457       {
458         StringBuilder summaryFieldValues = new StringBuilder();
459         for (Object summaryField : summaryRowData1)
460         {
461           summaryFieldValues.append(
462                   summaryField == null ? " " : summaryField.toString())
463                   .append("\t");
464         }
465         return summaryFieldValues.toString();
466       }
467
468       /**
469        * Returns hash code value for this object
470        */
471       @Override
472       public int hashCode()
473       {
474         return Objects.hash(primaryKey1, this.toString());
475       }
476
477       @Override
478       public boolean equals(Object that)
479       {
480         return this.toString().equals(that.toString());
481       }
482     };
483   }
484
485   private static String sanitiseData(String data)
486   {
487     String cleanData = data.replaceAll("\\[\"", "").replaceAll("\\]\"", "")
488             .replaceAll("\\[", "").replaceAll("\\]", "")
489             .replaceAll("\",\"", ", ").replaceAll("\"", "");
490     return cleanData;
491   }
492
493   @Override
494   public String getColumnDataConfigFileName()
495   {
496     return "/fts/pdb_data_columns.txt";
497   }
498
499   public static FTSRestClientI getInstance()
500   {
501     if (instance == null)
502     {
503       instance = new PDBFTSRestClient();
504     }
505     return instance;
506   }
507
508   private Collection<FTSDataColumnI> allDefaultDisplayedStructureDataColumns;
509   @Override
510   public Collection<FTSDataColumnI> getAllDefaultDisplayedStructureDataColumns()
511   {
512     if (allDefaultDisplayedStructureDataColumns == null
513             || allDefaultDisplayedStructureDataColumns.isEmpty())
514     {
515       allDefaultDisplayedStructureDataColumns = new ArrayList<>();
516       allDefaultDisplayedStructureDataColumns
517               .addAll(super.getAllDefaultDisplayedFTSDataColumns());
518     }
519     return allDefaultDisplayedStructureDataColumns;
520   }
521   @Override
522   public String[] getPreferencesColumnsFor(PreferenceSource source) {
523     String[] columnNames = null;
524     switch (source)
525     {
526     case SEARCH_SUMMARY:
527       columnNames = new String[] { "", "Display", "Group" };
528       break;
529     case STRUCTURE_CHOOSER:
530       columnNames = new String[] { "", "Display", "Group" };
531       break;
532     case PREFERENCES:
533       columnNames = new String[] { "PDB Field", "Show in search summary",
534           "Show in structure summary" };
535       break;
536     default:
537       break;
538     }
539     return columnNames;
540   }
541
542   public static void setMock()
543   {
544     String mockReq = "https://www.ebi.ac.uk/pdbe/search/pdb/select?wt=json&fl=pdb_id,title,experimental_method,resolution&rows=500&start=0&q=(4igk+OR+1t15+OR+4ifi+OR+1t29+OR+3pxb+OR+4y2g+OR+1y98+OR+1jnx+OR+3pxa+OR+3k0h+OR+3k0k+OR+1n5o+OR+3pxc+OR+3pxd+OR+1t2u+OR+3k15+OR+3pxe+OR+3k16+OR+4ofb+OR+3coj+OR+7lyb+OR+1t2v+OR+4y18+OR+4jlu+OR+4u4a+OR+2ing+OR+7jzv+OR+6g2i+OR+1jm7+OR+1oqa)+AND+molecule_sequence:%5B''+TO+*%5D+AND+status:REL&sort=";
545     String mockResp = "{\n"
546             + "  \"responseHeader\":{\n"
547             + "    \"status\":0,\n"
548             + "    \"QTime\":0,\n"
549             + "    \"params\":{\n"
550             + "      \"q\":\"(4igk OR 1t15 OR 4ifi OR 1t29 OR 3pxb OR 4y2g OR 1y98 OR 1jnx OR 3pxa OR 3k0h OR 3k0k OR 1n5o OR 3pxc OR 3pxd OR 1t2u OR 3k15 OR 3pxe OR 3k16 OR 4ofb OR 3coj OR 7lyb OR 1t2v OR 4y18 OR 4jlu OR 4u4a OR 2ing OR 7jzv OR 6g2i OR 1jm7 OR 1oqa) AND molecule_sequence:['' TO *] AND status:REL\",\n"
551             + "      \"fl\":\"pdb_id,title,experimental_method,resolution\",\n"
552             + "      \"start\":\"0\",\n"
553             + "      \"sort\":\"\",\n"
554             + "      \"rows\":\"500\",\n"
555             + "      \"wt\":\"json\"}},\n"
556             + "  \"response\":{\"numFound\":64,\"start\":0,\"docs\":[\n"
557             + "      {\n"
558             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
559             + "        \"pdb_id\":\"4ofb\",\n"
560             + "        \"resolution\":3.05,\n"
561             + "        \"title\":\"Crystal structure of human BRCA1 BRCT in complex with nonphosphopeptide inhibitor\"},\n"
562             + "      {\n"
563             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
564             + "        \"pdb_id\":\"3pxe\",\n"
565             + "        \"resolution\":2.85,\n"
566             + "        \"title\":\"Impact of BRCA1 BRCT domain missense substitutions on phospho-peptide recognition: E1836K\"},\n"
567             + "      {\n"
568             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
569             + "        \"pdb_id\":\"4jlu\",\n"
570             + "        \"resolution\":3.5,\n"
571             + "        \"title\":\"Crystal structure of BRCA1 BRCT with doubly phosphorylated Abraxas\"},\n"
572             + "      {\n"
573             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
574             + "        \"pdb_id\":\"4y2g\",\n"
575             + "        \"resolution\":2.5,\n"
576             + "        \"title\":\"Structure of BRCA1 BRCT domains in complex with Abraxas single phosphorylated peptide\"},\n"
577             + "      {\n"
578             + "        \"experimental_method\":[\"Solution NMR\"],\n"
579             + "        \"pdb_id\":\"1oqa\",\n"
580             + "        \"title\":\"Solution structure of the BRCT-c domain from human BRCA1\"},\n"
581             + "      {\n"
582             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
583             + "        \"pdb_id\":\"4u4a\",\n"
584             + "        \"resolution\":3.51,\n"
585             + "        \"title\":\"Complex Structure of BRCA1 BRCT with singly phospho Abraxas\"},\n"
586             + "      {\n"
587             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
588             + "        \"pdb_id\":\"1t2v\",\n"
589             + "        \"resolution\":3.3,\n"
590             + "        \"title\":\"Structural basis of phospho-peptide recognition by the BRCT domain of BRCA1, structure with phosphopeptide\"},\n"
591             + "      {\n"
592             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
593             + "        \"pdb_id\":\"3k15\",\n"
594             + "        \"resolution\":2.8,\n"
595             + "        \"title\":\"Crystal Structure of BRCA1 BRCT D1840T in complex with a minimal recognition tetrapeptide with an amidated C-terminus\"},\n"
596             + "      {\n"
597             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
598             + "        \"pdb_id\":\"1t15\",\n"
599             + "        \"resolution\":1.85,\n"
600             + "        \"title\":\"Crystal Structure of the Brca1 BRCT Domains in Complex with the Phosphorylated Interacting Region from Bach1 Helicase\"},\n"
601             + "      {\n"
602             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
603             + "        \"pdb_id\":\"3k16\",\n"
604             + "        \"resolution\":3.0,\n"
605             + "        \"title\":\"Crystal Structure of BRCA1 BRCT D1840T in complex with a minimal recognition tetrapeptide with a free carboxy C-terminus\"},\n"
606             + "      {\n"
607             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
608             + "        \"pdb_id\":\"1t29\",\n"
609             + "        \"resolution\":2.3,\n"
610             + "        \"title\":\"Crystal structure of the BRCA1 BRCT repeats bound to a phosphorylated BACH1 peptide\"},\n"
611             + "      {\n"
612             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
613             + "        \"pdb_id\":\"1y98\",\n"
614             + "        \"resolution\":2.5,\n"
615             + "        \"title\":\"Structure of the BRCT repeats of BRCA1 bound to a CtIP phosphopeptide.\"},\n"
616             + "      {\n"
617             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
618             + "        \"pdb_id\":\"4ifi\",\n"
619             + "        \"resolution\":2.2,\n"
620             + "        \"title\":\"Structure of human BRCA1 BRCT in complex with BAAT peptide\"},\n"
621             + "      {\n"
622             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
623             + "        \"pdb_id\":\"3k0k\",\n"
624             + "        \"resolution\":2.7,\n"
625             + "        \"title\":\"Crystal Structure of BRCA1 BRCT in complex with a minimal recognition tetrapeptide with a free carboxy C-terminus.\"},\n"
626             + "      {\n"
627             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
628             + "        \"pdb_id\":\"3k0h\",\n"
629             + "        \"resolution\":2.7,\n"
630             + "        \"title\":\"The crystal structure of BRCA1 BRCT in complex with a minimal recognition tetrapeptide with an amidated C-terminus\"},\n"
631             + "      {\n"
632             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
633             + "        \"pdb_id\":\"3pxd\",\n"
634             + "        \"resolution\":2.8,\n"
635             + "        \"title\":\"Impact of BRCA1 BRCT domain missense substitutions on phospho-peptide recognition: R1835P\"},\n"
636             + "      {\n"
637             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
638             + "        \"pdb_id\":\"3pxc\",\n"
639             + "        \"resolution\":2.8,\n"
640             + "        \"title\":\"Impact of BRCA1 BRCT domain missense substitutions on phospho-peptide recognition: R1699Q\"},\n"
641             + "      {\n"
642             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
643             + "        \"pdb_id\":\"3pxa\",\n"
644             + "        \"resolution\":2.55,\n"
645             + "        \"title\":\"Impact of BRCA1 BRCT domain missense substitutions on phospho-peptide recognition: G1656D\"},\n"
646             + "      {\n"
647             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
648             + "        \"pdb_id\":\"1jnx\",\n"
649             + "        \"resolution\":2.5,\n"
650             + "        \"title\":\"Crystal structure of the BRCT repeat region from the breast cancer associated protein, BRCA1\"},\n"
651             + "      {\n"
652             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
653             + "        \"pdb_id\":\"4igk\",\n"
654             + "        \"resolution\":1.75,\n"
655             + "        \"title\":\"Structure of human BRCA1 BRCT in complex with ATRIP peptide\"},\n"
656             + "      {\n"
657             + "        \"experimental_method\":[\"Solution NMR\"],\n"
658             + "        \"pdb_id\":\"1jm7\",\n"
659             + "        \"title\":\"Solution structure of the BRCA1/BARD1 RING-domain heterodimer\"},\n"
660             + "      {\n"
661             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
662             + "        \"pdb_id\":\"4jlu\",\n"
663             + "        \"resolution\":3.5,\n"
664             + "        \"title\":\"Crystal structure of BRCA1 BRCT with doubly phosphorylated Abraxas\"},\n"
665             + "      {\n"
666             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
667             + "        \"pdb_id\":\"6g2i\",\n"
668             + "        \"resolution\":5.9,\n"
669             + "        \"title\":\"Filament of acetyl-CoA carboxylase and BRCT domains of BRCA1 (ACC-BRCT) at 5.9 A resolution\"},\n"
670             + "      {\n"
671             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
672             + "        \"pdb_id\":\"3coj\",\n"
673             + "        \"resolution\":3.21,\n"
674             + "        \"title\":\"Crystal Structure of the BRCT Domains of Human BRCA1 in Complex with a Phosphorylated Peptide from Human Acetyl-CoA Carboxylase 1\"},\n"
675             + "      {\n"
676             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
677             + "        \"pdb_id\":\"3pxb\",\n"
678             + "        \"resolution\":2.5,\n"
679             + "        \"title\":\"Impact of BRCA1 BRCT domain missense substitutions on phospho-peptide recognition: T1700A\"},\n"
680             + "      {\n"
681             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
682             + "        \"pdb_id\":\"1t2u\",\n"
683             + "        \"resolution\":2.8,\n"
684             + "        \"title\":\"Structural basis of phosphopeptide recognition by the BRCT domain of BRCA1: structure of BRCA1 missense variant V1809F\"},\n"
685             + "      {\n"
686             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
687             + "        \"pdb_id\":\"1n5o\",\n"
688             + "        \"resolution\":2.8,\n"
689             + "        \"title\":\"Structural consequences of a cancer-causing BRCA1-BRCT missense mutation\"},\n"
690             + "      {\n"
691             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
692             + "        \"pdb_id\":\"4u4a\",\n"
693             + "        \"resolution\":3.51,\n"
694             + "        \"title\":\"Complex Structure of BRCA1 BRCT with singly phospho Abraxas\"},\n"
695             + "      {\n"
696             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
697             + "        \"pdb_id\":\"4y2g\",\n"
698             + "        \"resolution\":2.5,\n"
699             + "        \"title\":\"Structure of BRCA1 BRCT domains in complex with Abraxas single phosphorylated peptide\"},\n"
700             + "      {\n"
701             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
702             + "        \"pdb_id\":\"3pxe\",\n"
703             + "        \"resolution\":2.85,\n"
704             + "        \"title\":\"Impact of BRCA1 BRCT domain missense substitutions on phospho-peptide recognition: E1836K\"},\n"
705             + "      {\n"
706             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
707             + "        \"pdb_id\":\"4ofb\",\n"
708             + "        \"resolution\":3.05,\n"
709             + "        \"title\":\"Crystal structure of human BRCA1 BRCT in complex with nonphosphopeptide inhibitor\"},\n"
710             + "      {\n"
711             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
712             + "        \"pdb_id\":\"4y18\",\n"
713             + "        \"resolution\":3.5,\n"
714             + "        \"title\":\"Structure of BRCA1 BRCT domains in complex with Abraxas double phosphorylated peptide\"},\n"
715             + "      {\n"
716             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
717             + "        \"pdb_id\":\"2ing\",\n"
718             + "        \"resolution\":3.6,\n"
719             + "        \"title\":\"X-ray Structure of the BRCA1 BRCT mutant M1775K\"},\n"
720             + "      {\n"
721             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
722             + "        \"pdb_id\":\"1t29\",\n"
723             + "        \"resolution\":2.3,\n"
724             + "        \"title\":\"Crystal structure of the BRCA1 BRCT repeats bound to a phosphorylated BACH1 peptide\"},\n"
725             + "      {\n"
726             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
727             + "        \"pdb_id\":\"1t2v\",\n"
728             + "        \"resolution\":3.3,\n"
729             + "        \"title\":\"Structural basis of phospho-peptide recognition by the BRCT domain of BRCA1, structure with phosphopeptide\"},\n"
730             + "      {\n"
731             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
732             + "        \"pdb_id\":\"1t15\",\n"
733             + "        \"resolution\":1.85,\n"
734             + "        \"title\":\"Crystal Structure of the Brca1 BRCT Domains in Complex with the Phosphorylated Interacting Region from Bach1 Helicase\"},\n"
735             + "      {\n"
736             + "        \"experimental_method\":[\"Solution NMR\"],\n"
737             + "        \"pdb_id\":\"1jm7\",\n"
738             + "        \"title\":\"Solution structure of the BRCA1/BARD1 RING-domain heterodimer\"},\n"
739             + "      {\n"
740             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
741             + "        \"pdb_id\":\"4ifi\",\n"
742             + "        \"resolution\":2.2,\n"
743             + "        \"title\":\"Structure of human BRCA1 BRCT in complex with BAAT peptide\"},\n"
744             + "      {\n"
745             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
746             + "        \"pdb_id\":\"4igk\",\n"
747             + "        \"resolution\":1.75,\n"
748             + "        \"title\":\"Structure of human BRCA1 BRCT in complex with ATRIP peptide\"},\n"
749             + "      {\n"
750             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
751             + "        \"pdb_id\":\"1y98\",\n"
752             + "        \"resolution\":2.5,\n"
753             + "        \"title\":\"Structure of the BRCT repeats of BRCA1 bound to a CtIP phosphopeptide.\"},\n"
754             + "      {\n"
755             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
756             + "        \"pdb_id\":\"3k15\",\n"
757             + "        \"resolution\":2.8,\n"
758             + "        \"title\":\"Crystal Structure of BRCA1 BRCT D1840T in complex with a minimal recognition tetrapeptide with an amidated C-terminus\"},\n"
759             + "      {\n"
760             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
761             + "        \"pdb_id\":\"3k0k\",\n"
762             + "        \"resolution\":2.7,\n"
763             + "        \"title\":\"Crystal Structure of BRCA1 BRCT in complex with a minimal recognition tetrapeptide with a free carboxy C-terminus.\"},\n"
764             + "      {\n"
765             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
766             + "        \"pdb_id\":\"3k16\",\n"
767             + "        \"resolution\":3.0,\n"
768             + "        \"title\":\"Crystal Structure of BRCA1 BRCT D1840T in complex with a minimal recognition tetrapeptide with a free carboxy C-terminus\"},\n"
769             + "      {\n"
770             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
771             + "        \"pdb_id\":\"3k0h\",\n"
772             + "        \"resolution\":2.7,\n"
773             + "        \"title\":\"The crystal structure of BRCA1 BRCT in complex with a minimal recognition tetrapeptide with an amidated C-terminus\"},\n"
774             + "      {\n"
775             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
776             + "        \"pdb_id\":\"4y18\",\n"
777             + "        \"resolution\":3.5,\n"
778             + "        \"title\":\"Structure of BRCA1 BRCT domains in complex with Abraxas double phosphorylated peptide\"},\n"
779             + "      {\n"
780             + "        \"experimental_method\":[\"X-ray diffraction\"],\n"
781             + "        \"pdb_id\":\"3coj\",\n"
782             + "        \"resolution\":3.21,\n"
783             + "        \"title\":\"Crystal Structure of the BRCT Domains of Human BRCA1 in Complex with a Phosphorylated Peptide from Human Acetyl-CoA Carboxylase 1\"},\n"
784             + "      {\n"
785             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
786             + "        \"pdb_id\":\"7jzv\",\n"
787             + "        \"resolution\":3.9,\n"
788             + "        \"title\":\"Cryo-EM structure of the BRCA1-UbcH5c/BARD1 E3-E2 module bound to a nucleosome\"},\n"
789             + "      {\n"
790             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
791             + "        \"pdb_id\":\"7jzv\",\n"
792             + "        \"resolution\":3.9,\n"
793             + "        \"title\":\"Cryo-EM structure of the BRCA1-UbcH5c/BARD1 E3-E2 module bound to a nucleosome\"},\n"
794             + "      {\n"
795             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
796             + "        \"pdb_id\":\"7lyb\",\n"
797             + "        \"resolution\":3.28,\n"
798             + "        \"title\":\"Cryo-EM structure of the human nucleosome core particle in complex with BRCA1-BARD1-UbcH5c\"},\n"
799             + "      {\n"
800             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
801             + "        \"pdb_id\":\"7lyb\",\n"
802             + "        \"resolution\":3.28,\n"
803             + "        \"title\":\"Cryo-EM structure of the human nucleosome core particle in complex with BRCA1-BARD1-UbcH5c\"},\n"
804             + "      {\n"
805             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
806             + "        \"pdb_id\":\"7lyb\",\n"
807             + "        \"resolution\":3.28,\n"
808             + "        \"title\":\"Cryo-EM structure of the human nucleosome core particle in complex with BRCA1-BARD1-UbcH5c\"},\n"
809             + "      {\n"
810             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
811             + "        \"pdb_id\":\"7jzv\",\n"
812             + "        \"resolution\":3.9,\n"
813             + "        \"title\":\"Cryo-EM structure of the BRCA1-UbcH5c/BARD1 E3-E2 module bound to a nucleosome\"},\n"
814             + "      {\n"
815             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
816             + "        \"pdb_id\":\"7lyb\",\n"
817             + "        \"resolution\":3.28,\n"
818             + "        \"title\":\"Cryo-EM structure of the human nucleosome core particle in complex with BRCA1-BARD1-UbcH5c\"},\n"
819             + "      {\n"
820             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
821             + "        \"pdb_id\":\"7jzv\",\n"
822             + "        \"resolution\":3.9,\n"
823             + "        \"title\":\"Cryo-EM structure of the BRCA1-UbcH5c/BARD1 E3-E2 module bound to a nucleosome\"},\n"
824             + "      {\n"
825             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
826             + "        \"pdb_id\":\"7lyb\",\n"
827             + "        \"resolution\":3.28,\n"
828             + "        \"title\":\"Cryo-EM structure of the human nucleosome core particle in complex with BRCA1-BARD1-UbcH5c\"},\n"
829             + "      {\n"
830             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
831             + "        \"pdb_id\":\"7jzv\",\n"
832             + "        \"resolution\":3.9,\n"
833             + "        \"title\":\"Cryo-EM structure of the BRCA1-UbcH5c/BARD1 E3-E2 module bound to a nucleosome\"},\n"
834             + "      {\n"
835             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
836             + "        \"pdb_id\":\"7lyb\",\n"
837             + "        \"resolution\":3.28,\n"
838             + "        \"title\":\"Cryo-EM structure of the human nucleosome core particle in complex with BRCA1-BARD1-UbcH5c\"},\n"
839             + "      {\n"
840             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
841             + "        \"pdb_id\":\"7lyb\",\n"
842             + "        \"resolution\":3.28,\n"
843             + "        \"title\":\"Cryo-EM structure of the human nucleosome core particle in complex with BRCA1-BARD1-UbcH5c\"},\n"
844             + "      {\n"
845             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
846             + "        \"pdb_id\":\"7lyb\",\n"
847             + "        \"resolution\":3.28,\n"
848             + "        \"title\":\"Cryo-EM structure of the human nucleosome core particle in complex with BRCA1-BARD1-UbcH5c\"},\n"
849             + "      {\n"
850             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
851             + "        \"pdb_id\":\"7jzv\",\n"
852             + "        \"resolution\":3.9,\n"
853             + "        \"title\":\"Cryo-EM structure of the BRCA1-UbcH5c/BARD1 E3-E2 module bound to a nucleosome\"},\n"
854             + "      {\n"
855             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
856             + "        \"pdb_id\":\"6g2i\",\n"
857             + "        \"resolution\":5.9,\n"
858             + "        \"title\":\"Filament of acetyl-CoA carboxylase and BRCT domains of BRCA1 (ACC-BRCT) at 5.9 A resolution\"},\n"
859             + "      {\n"
860             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
861             + "        \"pdb_id\":\"7jzv\",\n"
862             + "        \"resolution\":3.9,\n"
863             + "        \"title\":\"Cryo-EM structure of the BRCA1-UbcH5c/BARD1 E3-E2 module bound to a nucleosome\"},\n"
864             + "      {\n"
865             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
866             + "        \"pdb_id\":\"7lyb\",\n"
867             + "        \"resolution\":3.28,\n"
868             + "        \"title\":\"Cryo-EM structure of the human nucleosome core particle in complex with BRCA1-BARD1-UbcH5c\"},\n"
869             + "      {\n"
870             + "        \"experimental_method\":[\"Electron Microscopy\"],\n"
871             + "        \"pdb_id\":\"7jzv\",\n"
872             + "        \"resolution\":3.9,\n"
873             + "        \"title\":\"Cryo-EM structure of the BRCA1-UbcH5c/BARD1 E3-E2 module bound to a nucleosome\"}]\n"
874             + "  }}";
875     createMockFTSRestClient((FTSRestClient)getInstance(), mockReq, mockResp);
876   }
877 }