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