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