JAL-4199 Test URL settings and basic service discovery
[jalview.git] / src / jalview / ws2 / client / slivka / SlivkaWSDiscoverer.java
1 package jalview.ws2.client.slivka;
2
3 import java.io.IOException;
4 import java.net.MalformedURLException;
5 import java.net.URI;
6 import java.net.URISyntaxException;
7 import java.net.URL;
8 import java.util.ArrayList;
9 import java.util.List;
10 import java.util.function.Function;
11
12 import jalview.bin.Cache;
13 import jalview.bin.Console;
14 import jalview.ws.params.ParamManager;
15 import jalview.ws2.actions.alignment.AlignmentAction;
16 import jalview.ws2.actions.annotation.AnnotationAction;
17 import jalview.ws2.api.WebService;
18 import jalview.ws2.client.api.AbstractWebServiceDiscoverer;
19 import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
20 import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
21
22 public class SlivkaWSDiscoverer extends AbstractWebServiceDiscoverer
23 {
24   private static final String SLIVKA_HOST_URLS = "SLIVKAHOSTURLS";
25
26   private static final URL DEFAULT_URL;
27   static
28   {
29     try
30     {
31       DEFAULT_URL = new URL("https://www.compbio.dundee.ac.uk/slivka/");
32     } catch (MalformedURLException e)
33     {
34       throw new AssertionError(e);
35     }
36   }
37
38   private static SlivkaWSDiscoverer instance = null;
39
40   private static ParamManager paramManager = null;
41   
42   private final Function<URI, SlivkaClient> clientFactory;
43
44   SlivkaWSDiscoverer(Function<URI, SlivkaClient> clientFactory)
45   {
46     this.clientFactory = clientFactory;
47   }
48
49   public static SlivkaWSDiscoverer getInstance()
50   {
51     if (instance == null)
52       instance = new SlivkaWSDiscoverer(SlivkaClient::newInstance);
53     return instance;
54   }
55
56   public static void setParamManager(ParamManager manager)
57   {
58     paramManager = manager;
59   }
60
61   @Override
62   public int getStatusForUrl(URL url)
63   {
64     try
65     {
66       List<?> services = clientFactory.apply(url.toURI()).getServices();
67       return services.isEmpty() ? STATUS_NO_SERVICES : STATUS_OK;
68     } catch (URISyntaxException e)
69     {
70       Console.error("invalid URL " + url, e);
71       return STATUS_INVALID;
72     } catch (IOException e)
73     {
74       Console.error("slivka could not retrieve services from " + url, e);
75       return STATUS_INVALID;
76     }
77   }
78
79   @Override
80   protected String getUrlsPropertyKey()
81   {
82     return SLIVKA_HOST_URLS;
83   }
84
85   @Override
86   protected URL getDefaultUrl()
87   {
88     return DEFAULT_URL;
89   }
90
91   @Override
92   protected List<WebService<?>> fetchServices(URL url) throws IOException
93   {
94     ArrayList<WebService<?>> allServices = new ArrayList<>();
95     SlivkaClient slivkaClient;
96     try
97     {
98       slivkaClient = clientFactory.apply(url.toURI());
99     } catch (URISyntaxException e)
100     {
101       throw new MalformedURLException(e.getMessage());
102     }
103     for (var slivkaService : slivkaClient.getServices())
104     {
105       int serviceClass = getServiceClass(slivkaService);
106       if (serviceClass == SERVICE_CLASS_MSA)
107       {
108         var wsb = WebService.<AlignmentAction> newBuilder();
109         initServiceBuilder(slivkaClient, slivkaService, wsb);
110         wsb.category("Alignment");
111         wsb.interactive(false);
112         wsb.actionClass(AlignmentAction.class);
113         var msaService = wsb.build();
114
115         boolean canRealign = msaService.getName().contains("lustal");
116         var client = new SlivkaAlignmentWSClient(slivkaClient, slivkaService);
117         var actionBuilder = AlignmentAction.newBuilder(client);
118         actionBuilder.name("Alignment");
119         actionBuilder.webService(msaService);
120         if (canRealign)
121           actionBuilder.subcategory("Align");
122         actionBuilder.minSequences(2);
123         msaService.addAction(actionBuilder.build());
124         if (canRealign)
125         {
126           actionBuilder.name("Re-alignment");
127           actionBuilder.subcategory("Realign");
128           actionBuilder.submitGaps(true);
129           msaService.addAction(actionBuilder.build());
130         }
131         allServices.add(msaService);
132       }
133       else if (serviceClass == SERVICE_CLASS_PROT_SEQ_ANALYSIS)
134       {
135         var wsb = WebService.<AnnotationAction> newBuilder();
136         initServiceBuilder(slivkaClient, slivkaService, wsb);
137         wsb.category("Protein Disorder");
138         wsb.interactive(false);
139         wsb.actionClass(AnnotationAction.class);
140         var psaService = wsb.build();
141         var client = new SlivkaAnnotationWSClient(slivkaClient, slivkaService);
142         var actionBuilder = AnnotationAction.newBuilder(client);
143         actionBuilder.webService(psaService);
144         actionBuilder.name("Analysis");
145         psaService.addAction(actionBuilder.build());
146         allServices.add(psaService);
147       }
148       else if (serviceClass == SERVICE_CLASS_CONSERVATION)
149       {
150         var wsb = WebService.<AnnotationAction> newBuilder();
151         initServiceBuilder(slivkaClient, slivkaService, wsb);
152         wsb.category("Conservation");
153         wsb.interactive(true);
154         wsb.actionClass(AnnotationAction.class);
155         var conService = wsb.build();
156         var client = new SlivkaAnnotationWSClient(slivkaClient, slivkaService);
157         var actionBuilder = AnnotationAction.newBuilder(client);
158         actionBuilder.webService(conService);
159         actionBuilder.name("");
160         actionBuilder.alignmentAnalysis(true);
161         actionBuilder.requireAlignedSequences(true);
162         actionBuilder.filterSymbols(true);
163         conService.addAction(actionBuilder.build());
164         allServices.add(conService);
165       }
166       else if (serviceClass == SERVICE_CLASS_RNA_SEC_STR_PRED)
167       {
168         var wsb = WebService.<AnnotationAction> newBuilder();
169         initServiceBuilder(slivkaClient, slivkaService, wsb);
170         wsb.category("Secondary Structure Prediction");
171         wsb.interactive(true);
172         wsb.actionClass(AnnotationAction.class);
173         var predService = wsb.build();
174         var client = new SlivkaAnnotationWSClient(slivkaClient, slivkaService);
175         var actionBuilder = AnnotationAction.newBuilder(client);
176         actionBuilder.webService(predService);
177         actionBuilder.name("Prediction");
178         actionBuilder.minSequences(2);
179         actionBuilder.allowNucleotide(true);
180         actionBuilder.allowProtein(false);
181         actionBuilder.alignmentAnalysis(true);
182         actionBuilder.requireAlignedSequences(true);
183         actionBuilder.filterSymbols(false);
184         predService.addAction(actionBuilder.build());
185         allServices.add(predService);
186       }
187       else
188       {
189         continue;
190       }
191     }
192     return allServices;
193   }
194
195   private void initServiceBuilder(SlivkaClient client, SlivkaService service, WebService.Builder<?> wsBuilder)
196   {
197     try
198     {
199       wsBuilder.url(client.getUrl().toURL());
200     } catch (MalformedURLException e)
201     {
202       e.printStackTrace();
203     }
204     wsBuilder.clientName("slivka");
205     wsBuilder.name(service.getName());
206     wsBuilder.description(service.getDescription());
207     var storeBuilder = new SlivkaParamStoreFactory(service, paramManager);
208     wsBuilder.paramDatastore(storeBuilder.createParamDatastore());
209   }
210
211   static final int SERVICE_CLASS_UNSUPPORTED = -1;
212
213   static final int SERVICE_CLASS_MSA = 1;
214
215   static final int SERVICE_CLASS_RNA_SEC_STR_PRED = 2;
216
217   static final int SERVICE_CLASS_CONSERVATION = 3;
218
219   static final int SERVICE_CLASS_PROT_SEQ_ANALYSIS = 4;
220
221   static final int SERVICE_CLASS_PROT_SEC_STR_PRED = 5;
222
223   /**
224    * Scan service classifiers starting with operation :: analysis to decide the
225    * operation class.
226    * 
227    * @return service class flag
228    */
229   private static int getServiceClass(SlivkaService service)
230   {
231     for (String classifier : service.getClassifiers())
232     {
233       String[] path = classifier.split("\\s*::\\s*");
234       if (path.length < 3 || !path[0].equalsIgnoreCase("operation"))
235         continue;
236       // classifier is operation :: *
237       var tail = path[path.length - 1].toLowerCase();
238       switch (tail)
239       {
240       case "multiple sequence alignment":
241         return SERVICE_CLASS_MSA;
242       case "rna secondary structure prediction":
243         return SERVICE_CLASS_RNA_SEC_STR_PRED;
244       case "sequence alignment analysis (conservation)":
245         return SERVICE_CLASS_CONSERVATION;
246       case "protein sequence analysis":
247         return SERVICE_CLASS_PROT_SEQ_ANALYSIS;
248       case "protein secondary structure prediction":
249         return SERVICE_CLASS_PROT_SEC_STR_PRED;
250       }
251     }
252     return SERVICE_CLASS_UNSUPPORTED;
253   }
254 }