JAL-1727 patch to make EBI mirror the default server
[jalview.git] / src / jalview / ws / dbsources / das / datamodel / DasSourceRegistry.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2b1)
3  * Copyright (C) 2014 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.ws.dbsources.das.datamodel;
22
23 import jalview.bin.Cache;
24 import jalview.ws.dbsources.das.api.DasSourceRegistryI;
25 import jalview.ws.dbsources.das.api.jalviewSourceI;
26
27 import java.net.HttpURLConnection;
28 import java.net.MalformedURLException;
29 import java.net.URL;
30 import java.util.ArrayList;
31 import java.util.Enumeration;
32 import java.util.HashMap;
33 import java.util.Hashtable;
34 import java.util.List;
35 import java.util.StringTokenizer;
36
37 import org.biodas.jdas.client.ConnectionPropertyProviderI;
38 import org.biodas.jdas.client.SourcesClient;
39 import org.biodas.jdas.client.threads.MultipleConnectionPropertyProviderI;
40 import org.biodas.jdas.dassources.Capabilities;
41 import org.biodas.jdas.schema.sources.CAPABILITY;
42 import org.biodas.jdas.schema.sources.SOURCE;
43 import org.biodas.jdas.schema.sources.SOURCES;
44 import org.biodas.jdas.schema.sources.VERSION;
45
46 /**
47  *
48  */
49 public class DasSourceRegistry implements DasSourceRegistryI,
50         MultipleConnectionPropertyProviderI
51 {
52   // private org.biodas.jdas.schema.sources.SOURCE[] dasSources = null;
53   private List<jalviewSourceI> dasSources = null;
54
55   private Hashtable<String, jalviewSourceI> sourceNames = null;
56
57   private Hashtable<String, jalviewSourceI> localSources = null;
58
59   // This is the EBI DAS registry archive - temporary fix
60   public static String DEFAULT_REGISTRY = "http://www.ebi.ac.uk/das-srv/registry/das";
61
62   private static String[] blackList = new String[]
63   { "http://www.dasregistry.org", "sanger.ac.uk/registry/das1/sources/" };
64   /**
65    * true if thread is running and we are talking to DAS registry service
66    */
67   private boolean loadingDasSources = false;
68
69   public boolean isLoadingDasSources()
70   {
71     return loadingDasSources;
72   }
73
74   public String getDasRegistryURL()
75   {
76     String registry = jalview.bin.Cache.getDefault("DAS_REGISTRY_URL",
77             DEFAULT_REGISTRY);
78     // replace and update old entries
79     for (String blacklisted : blackList)
80     {
81       if (registry.indexOf(blacklisted) > -1)
82       {
83         jalview.bin.Cache.setProperty(jalview.bin.Cache.DAS_REGISTRY_URL,
84                 DEFAULT_REGISTRY);
85         registry = DEFAULT_REGISTRY;
86         break;
87       }
88     }
89     if (registry.lastIndexOf("sources.xml") == registry.length() - 11)
90     {
91       // no trailing sources.xml document for registry in JDAS
92       jalview.bin.Cache.setProperty(
93               jalview.bin.Cache.DAS_REGISTRY_URL,
94               registry = registry.substring(0,
95                       registry.lastIndexOf("sources.xml")));
96     }
97     return registry;
98   }
99
100   /**
101    * query the default DAS Source Registry for sources. Uses value of jalview
102    * property DAS_REGISTRY_URL and the DasSourceBrowser.DEFAULT_REGISTRY if that
103    * doesn't exist.
104    * 
105    * @return list of sources
106    */
107   private List<jalviewSourceI> getDASSources()
108   {
109
110     return getDASSources(getDasRegistryURL(), this);
111   }
112
113   /**
114    * query the given URL for DasSources.
115    * 
116    * @param registryURL
117    *          return sources from registryURL
118    */
119   private static List<jalviewSourceI> getDASSources(String registryURL,
120           MultipleConnectionPropertyProviderI registry)
121   {
122     try
123     {
124       URL url = new URL(registryURL);
125       org.biodas.jdas.client.SourcesClientInterface client = new SourcesClient();
126
127       SOURCES sources = client.fetchDataRegistry(registryURL, null, null,
128               null, null, null, null);
129
130       List<SOURCE> dassources = sources.getSOURCE();
131       ArrayList<jalviewSourceI> dsrc = new ArrayList<jalviewSourceI>();
132       HashMap<String, Integer> latests = new HashMap<String, Integer>();
133       Integer latest;
134       for (SOURCE src : dassources)
135       {
136         JalviewSource jsrc = new JalviewSource(src, registry, false);
137         latest = latests.get(jsrc.getSourceURL());
138         if (latest != null)
139         {
140           if (jsrc.isNewerThan(dsrc.get(latest.intValue())))
141           {
142             dsrc.set(latest.intValue(), jsrc);
143           }
144           else
145           {
146             System.out.println("Debug: Ignored older source "
147                     + jsrc.getTitle());
148           }
149         }
150         else
151         {
152           latests.put(jsrc.getSourceURL(), Integer.valueOf(dsrc.size()));
153           dsrc.add(jsrc);
154         }
155       }
156       return dsrc;
157     } catch (Exception ex)
158     {
159       System.err.println("Failed to contact DAS1 registry at "
160               + registryURL);
161       ex.printStackTrace();
162       return new ArrayList<jalviewSourceI>();
163     }
164   }
165
166   public void run()
167   {
168     getSources();
169   }
170
171   @Override
172   public List<jalviewSourceI> getSources()
173   {
174     if (dasSources == null)
175     {
176       dasSources = getDASSources();
177     }
178     return appendLocalSources();
179   }
180
181   /**
182    * generate Sources from the local das source list
183    * 
184    */
185   private void addLocalDasSources()
186   {
187     if (localSources == null)
188     {
189       // get local sources from properties and initialise the local source list
190       String local = jalview.bin.Cache.getProperty("DAS_LOCAL_SOURCE");
191
192       if (local != null)
193       {
194         int n = 1;
195         StringTokenizer st = new StringTokenizer(local, "\t");
196         while (st.hasMoreTokens())
197         {
198           String token = st.nextToken();
199           int bar = token.indexOf("|");
200           if (bar == -1)
201           {
202             System.err
203                     .println("Warning: DAS user local source appears to have no nickname (expected a '|' followed by nickname)\nOffending definition: '"
204                             + token + "'");
205           }
206           String url = token.substring(bar + 1);
207           boolean features = true, sequence = false;
208           if (url.startsWith("sequence:"))
209           {
210             url = url.substring(9);
211             // this source also serves sequences as well as features
212             sequence = true;
213           }
214           try
215           {
216             if (bar > -1)
217             {
218               createLocalSource(url, token.substring(0, bar), sequence,
219                       features);
220             }
221             else
222             {
223               createLocalSource(url, "User Source" + n, sequence, features);
224             }
225           } catch (Exception q)
226           {
227             System.err
228                     .println("Unexpected exception when creating local source from '"
229                             + token + "'");
230             q.printStackTrace();
231           }
232           n++;
233         }
234       }
235     }
236   }
237
238   private List<jalviewSourceI> appendLocalSources()
239   {
240     List<jalviewSourceI> srclist = new ArrayList<jalviewSourceI>();
241     addLocalDasSources();
242     sourceNames = new Hashtable<String, jalviewSourceI>();
243     if (dasSources != null)
244     {
245       for (jalviewSourceI src : dasSources)
246       {
247         sourceNames.put(src.getTitle(), src);
248         srclist.add(src);
249       }
250     }
251
252     if (localSources == null)
253     {
254       return srclist;
255     }
256     Enumeration en = localSources.keys();
257     while (en.hasMoreElements())
258     {
259       String key = en.nextElement().toString();
260       jalviewSourceI jvsrc = localSources.get(key);
261       sourceNames.put(key, jvsrc);
262       srclist.add(jvsrc);
263     }
264     return srclist;
265   }
266
267   /*
268  * 
269  */
270
271   @Override
272   public jalviewSourceI createLocalSource(String url, String name,
273           boolean sequence, boolean features)
274   {
275     SOURCE local = _createLocalSource(url, name, sequence, features);
276
277     if (localSources == null)
278     {
279       localSources = new Hashtable<String, jalviewSourceI>();
280     }
281     jalviewSourceI src = new JalviewSource(local, this, true);
282     localSources.put(local.getTitle(), src);
283     return src;
284   }
285
286   private SOURCE _createLocalSource(String url, String name,
287           boolean sequence, boolean features)
288   {
289     SOURCE local = new SOURCE();
290
291     local.setUri(url);
292     local.setTitle(name);
293     local.setVERSION(new ArrayList<VERSION>());
294     VERSION v = new VERSION();
295     List<CAPABILITY> cp = new ArrayList<CAPABILITY>();
296     if (sequence)
297     {
298       /*
299        * Could try and synthesize a coordinate system for the source if needbe
300        * COORDINATES coord = new COORDINATES(); coord.setAuthority("NCBI");
301        * coord.setSource("Chromosome"); coord.setTaxid("9606");
302        * coord.setVersion("35"); version.getCOORDINATES().add(coord);
303        */
304       CAPABILITY cap = new CAPABILITY();
305       cap.setType("das1:" + Capabilities.SEQUENCE.getName());
306       cap.setQueryUri(url + "/sequence");
307       cp.add(cap);
308     }
309     if (features)
310     {
311       CAPABILITY cap = new CAPABILITY();
312       cap.setType("das1:" + Capabilities.FEATURES.getName());
313       cap.setQueryUri(url + "/features");
314       cp.add(cap);
315     }
316
317     v.getCAPABILITY().addAll(cp);
318     local.getVERSION().add(v);
319
320     return local;
321   }
322
323   @Override
324   public jalviewSourceI getSource(String nickname)
325   {
326     return sourceNames.get(nickname);
327   }
328
329   @Override
330   public boolean removeLocalSource(jalviewSourceI source)
331   {
332     if (localSources.containsValue(source))
333     {
334       localSources.remove(source.getTitle());
335       sourceNames.remove(source.getTitle());
336       dasSources.remove(source);
337       jalview.bin.Cache.setProperty("DAS_LOCAL_SOURCE",
338               getLocalSourceString());
339
340       return true;
341     }
342     return false;
343   }
344
345   @Override
346   public void refreshSources()
347   {
348     dasSources = null;
349     sourceNames = null;
350     run();
351   }
352
353   @Override
354   public List<jalviewSourceI> resolveSourceNicknames(List<String> sources)
355   {
356     ArrayList<jalviewSourceI> resolved = new ArrayList<jalviewSourceI>();
357     if (sourceNames != null)
358     {
359       for (String src : sources)
360       {
361         jalviewSourceI dsrc = sourceNames.get(src);
362         if (dsrc != null)
363         {
364           resolved.add(dsrc);
365         }
366       }
367     }
368     return resolved;
369   }
370
371   @Override
372   public String getLocalSourceString()
373   {
374     if (localSources != null)
375     {
376       StringBuffer sb = new StringBuffer();
377       Enumeration en = localSources.keys();
378       while (en.hasMoreElements())
379       {
380         String token = en.nextElement().toString();
381         jalviewSourceI srco = localSources.get(token);
382         sb.append(token + "|"
383                 + (srco.isSequenceSource() ? "sequence:" : "")
384                 + srco.getUri() + "\t");
385       }
386       return sb.toString();
387     }
388     return "";
389   }
390
391   private static final Hashtable<URL, String> authStash;
392   static
393   {
394     authStash = new Hashtable<URL, String>();
395
396     try
397     {
398       // TODO: allow same credentials for https and http
399       authStash.put(new URL(
400               "http://www.compbio.dundee.ac.uk/geneweb/das/myseq/"),
401               "Basic SmltOm1pSg==");
402     } catch (MalformedURLException e)
403     {
404       // TODO Auto-generated catch block
405       e.printStackTrace();
406     }
407   }
408
409   @Override
410   public MultipleConnectionPropertyProviderI getSessionHandler()
411   {
412     return this;
413   }
414
415   @Override
416   public ConnectionPropertyProviderI getConnectionPropertyProviderFor(
417           String arg0)
418   {
419
420     final ConnectionPropertyProviderI conprov = new ConnectionPropertyProviderI()
421     {
422       boolean authed = false;
423
424       @Override
425       public void setConnectionProperties(HttpURLConnection connection)
426       {
427         String auth = authStash.get(connection.getURL());
428         if (auth != null && auth.length() > 0)
429         {
430           connection.setRequestProperty("Authorisation", auth);
431           authed = true;
432         }
433         else
434         {
435           authed = false;
436         }
437       }
438
439       @Override
440       public boolean getResponseProperties(HttpURLConnection connection)
441       {
442         String auth = authStash.get(connection.getURL());
443         if (auth != null && auth.length() == 0)
444         {
445           // don't attempt to check if we authed or not - user entered empty
446           // password
447           return false;
448         }
449         if (!authed)
450         {
451           if (auth != null)
452           {
453             // try and pass credentials.
454             return true;
455           }
456           // see if we should try and create a new auth record.
457           String ameth = connection.getHeaderField("X-DAS-AuthMethods");
458           Cache.log.debug("Could authenticate to " + connection.getURL()
459                   + " with : " + ameth);
460           // TODO: search auth string and raise login box - return if auth was
461           // provided
462           return false;
463         }
464         else
465         {
466           // check to see if auth was successful
467           String asuc = connection
468                   .getHeaderField("X-DAS_AuthenticatedUser");
469           if (asuc != null && asuc.trim().length() > 0)
470           {
471             // authentication was successful
472             Cache.log.debug("Authenticated successfully to "
473                     + connection.getURL().toString());
474             return false;
475           }
476           // it wasn't - so we should tell the user it failed and ask if they
477           // want to attempt authentication again.
478           authStash.remove(connection.getURL());
479           // open a new login/password dialog with cancel button
480           // set new authStash content with password and return true
481           return true; //
482           // User cancelled auth - so put empty string in stash to indicate we
483           // don't want to auth with this server.
484           // authStash.put(connection.getURL(), "");
485           // return false;
486         }
487       }
488     };
489     return conprov;
490   }
491
492 }