Cleans up SP extension commands
[mkjsf-moved-to-github.git] / src / main / java / com / indexdata / mkjsf / pazpar2 / Pz2Bean.java
1 package com.indexdata.mkjsf.pazpar2;\r
2 \r
3 import java.io.Serializable;\r
4 import java.lang.annotation.Retention;\r
5 import java.lang.annotation.Target;\r
6 import java.util.ArrayList;\r
7 import java.util.Arrays;\r
8 import java.util.HashMap;\r
9 import java.util.List;\r
10 import java.util.Map;\r
11 import java.util.StringTokenizer;\r
12 \r
13 import javax.annotation.PostConstruct;\r
14 import javax.enterprise.context.SessionScoped;\r
15 import javax.enterprise.inject.Produces;\r
16 import javax.faces.context.FacesContext;\r
17 import javax.inject.Inject;\r
18 import javax.inject.Named;\r
19 import javax.inject.Qualifier;\r
20 \r
21 import org.apache.log4j.Logger;\r
22 \r
23 import com.indexdata.mkjsf.config.Configurable;\r
24 import com.indexdata.mkjsf.config.Configuration;\r
25 import com.indexdata.mkjsf.config.ConfigurationReader;\r
26 import com.indexdata.mkjsf.controls.ResultsPager;\r
27 import com.indexdata.mkjsf.errors.ConfigurationError;\r
28 import com.indexdata.mkjsf.errors.ConfigurationException;\r
29 import com.indexdata.mkjsf.errors.ErrorCentral;\r
30 import com.indexdata.mkjsf.errors.ErrorHelper;\r
31 import com.indexdata.mkjsf.pazpar2.commands.Pazpar2Command;\r
32 import com.indexdata.mkjsf.pazpar2.commands.Pazpar2Commands;\r
33 import com.indexdata.mkjsf.pazpar2.data.RecordResponse;\r
34 import com.indexdata.mkjsf.pazpar2.data.ResponseDataObject;\r
35 import com.indexdata.mkjsf.pazpar2.data.Responses;\r
36 import com.indexdata.mkjsf.pazpar2.state.StateListener;\r
37 import com.indexdata.mkjsf.pazpar2.state.StateManager;\r
38 import com.indexdata.mkjsf.utils.Utils;\r
39 \r
40 @Named("pz2") @SessionScoped\r
41 public class Pz2Bean implements Pz2Interface, StateListener, Configurable, Serializable {\r
42 \r
43   private static final String MODULE_NAME = "service";\r
44   private static String SERVICE_TYPE_TBD = "TBD", SERVICE_TYPE_PZ2 = "PZ2", SERVICE_TYPE_SP = "SP";\r
45   private static final List<String> serviceTypes = \r
46                 Arrays.asList(SERVICE_TYPE_PZ2,SERVICE_TYPE_SP,SERVICE_TYPE_TBD);\r
47   private String serviceType = SERVICE_TYPE_TBD;\r
48   private List<String> serviceProxyUrls = new ArrayList<String>();\r
49   public static final String SERVICE_PROXY_URL_LIST = "SERVICE_PROXY_URL_LIST";\r
50   private List<String> pazpar2Urls = new ArrayList<String>();\r
51   public static final String PAZPAR2_URL_LIST = "PAZPAR2_URL_LIST";\r
52 \r
53 \r
54   private static final long serialVersionUID = 3440277287081557861L;\r
55   private static Logger logger = Logger.getLogger(Pz2Bean.class);     \r
56   protected Pz2Client pz2Client = null;\r
57   protected ServiceProxyClient spClient = null;\r
58   protected SearchClient searchClient = null;  \r
59     \r
60   @Inject ConfigurationReader configurator;\r
61   \r
62   private StateManager stateMgr = null;\r
63   private Pazpar2Commands pzreq = null;\r
64   private Responses pzresp = null;\r
65   private ErrorCentral errors = null;  \r
66   \r
67   protected ResultsPager pager = null; \r
68   \r
69   protected ErrorHelper errorHelper = null;\r
70               \r
71   public Pz2Bean () {\r
72     logger.info("Instantiating pz2 bean [" + Utils.objectId(this) + "]");    \r
73   }\r
74   \r
75   public static Pz2Bean get() {\r
76     FacesContext context = FacesContext.getCurrentInstance();\r
77     return (Pz2Bean) context.getApplication().evaluateExpressionGet(context, "#{pz2}", Object.class); \r
78   }\r
79   \r
80   @PostConstruct\r
81   public void postConstruct() {\r
82     logger.info("Pz2Bean PostConstruct of " + this);\r
83     stateMgr = new StateManager();\r
84     pzreq = new Pazpar2Commands();\r
85     pzresp = new Responses();    \r
86     errors = new ErrorCentral(); \r
87     pzresp.setErrorHelper(errors.getHelper());\r
88     \r
89     logger.debug("Pz2Bean PostConstruct: Configurator is " + configurator);\r
90     logger.debug(Utils.objectId(this) + " will instantiate a Pz2Client next.");\r
91     pz2Client = new Pz2Client();\r
92     configureClient(pz2Client,configurator);\r
93     spClient = new ServiceProxyClient();\r
94     configureClient(spClient,configurator);\r
95     try {\r
96       this.configure(configurator);\r
97     } catch (ConfigurationException e) {\r
98       logger.error("There was a problem configuring the Pz2Bean (\"pz2\")");\r
99       e.printStackTrace();\r
100     }    \r
101     stateMgr.addStateListener(this);    \r
102   }\r
103   \r
104   @Qualifier\r
105   @Target({java.lang.annotation.ElementType.TYPE,\r
106            java.lang.annotation.ElementType.METHOD,\r
107            java.lang.annotation.ElementType.PARAMETER,\r
108            java.lang.annotation.ElementType.FIELD})\r
109   @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)\r
110   public  @interface Preferred{}\r
111   \r
112   @Produces @Preferred @SessionScoped @Named("pzresp") public Responses getPzresp () {\r
113     logger.trace("Producing pzresp");\r
114     return pzresp;\r
115   }\r
116   \r
117   @Produces @Preferred @SessionScoped @Named("pzreq") public Pazpar2Commands getPzreq () {\r
118     logger.trace("Producing pzreq");\r
119     return pzreq;\r
120   }\r
121   \r
122   @Produces @Preferred @SessionScoped @Named("errors") public ErrorCentral getErrors() {\r
123     logger.trace("Producing errors");\r
124     return errors;\r
125   }\r
126   \r
127   @Produces @Preferred @SessionScoped @Named("stateMgr") public StateManager getStateMgr() {\r
128     logger.trace("Producing stateMgr");\r
129     return stateMgr;\r
130   }\r
131   \r
132   public void configureClient(SearchClient client, ConfigurationReader configReader) {\r
133     logger.debug(Utils.objectId(this) + " will configure search client for the session");\r
134     try {\r
135       client.configure(configReader);            \r
136     } catch (ConfigurationException e) {\r
137       logger.debug("Pz2Bean adding configuration error");\r
138       errors.addConfigurationError(new ConfigurationError("Search Client","Configuration",e.getMessage()));                \r
139     } \r
140     logger.info(configReader.document());\r
141     pzresp.getSp().resetAuthAndBeyond(true);    \r
142   }\r
143   \r
144   public void resetSearchAndRecordCommands () {\r
145     pzreq.getRecord().removeParametersInState();\r
146     pzreq.getSearch().removeParametersInState();   \r
147   }\r
148       \r
149   /**\r
150    * Refreshes 'show', 'stat', 'termlist', and 'bytarget' data object from pazpar2\r
151    * \r
152    * @return Number of activeclients at the time of the 'show' command.\r
153    */\r
154   public String update () {\r
155     logger.debug("Updating show,stat,termlist,bytarget from pazpar2");\r
156     if (errors.hasConfigurationErrors()) {\r
157       logger.error("Ignoring show,stat,termlist,bytarget commands due to configuration errors.");\r
158       return "";\r
159     } else if (pzresp.getSearch().hasApplicationError()) {\r
160       logger.error("Ignoring show,stat,termlist,bytarget commands due to problem with most recent search.");\r
161       return "";\r
162     } else if (!hasQuery()) {\r
163       logger.debug("Ignoring show,stat,termlist,bytarget commands because there is not yet a query.");\r
164       return "";\r
165     } else {\r
166       return update("show,stat,termlist,bytarget");\r
167     }\r
168   }\r
169      \r
170   /**\r
171    * Refreshes the data objects listed in 'commands' from pazpar2\r
172    * \r
173    * @param commands\r
174    * @return Number of activeclients at the time of the 'show' command,\r
175    *         or 'new' if search was just initiated.\r
176    */\r
177   public String update (String commands) {\r
178     logger.debug("Request to update: " + commands);\r
179     try {\r
180       if (commands.equals("search")) {\r
181         pzreq.getSearch().run();\r
182         return "new";\r
183       } else if (commands.equals("record")) {\r
184         pzreq.getRecord().run();\r
185         return pzresp.getRecord().getActiveClients();\r
186       } else if (pzresp.getSearch().isNew()) {\r
187         // For returning notification of 'search started' quickly to UI\r
188         logger.info("New search. Marking it old, then returning 'new' to trigger another round-trip.");\r
189         pzresp.getSearch().setIsNew(false);\r
190         return "new";\r
191       } else {\r
192         handleQueryStateChanges(commands);\r
193         if (pzresp.getSearch().hasApplicationError()) {\r
194           logger.error("The command(s) " + commands + " cancelled because the latest search command had an error.");\r
195           return "0";\r
196         } else if (errors.hasConfigurationErrors()) {\r
197           logger.error("The command(s) " + commands + " cancelled due to configuration errors.");\r
198           return "0";\r
199         } else {\r
200           logger.debug("Processing request for " + commands); \r
201           List<CommandThread> threadList = new ArrayList<CommandThread>();\r
202           StringTokenizer tokens = new StringTokenizer(commands,",");\r
203           while (tokens.hasMoreElements()) {          \r
204             threadList.add(new CommandThread(pzreq.getCommand(tokens.nextToken()),searchClient,Pz2Bean.get().getPzresp()));            \r
205           }\r
206           for (CommandThread thread : threadList) {\r
207             thread.start();\r
208           }\r
209           for (CommandThread thread : threadList) {\r
210             try {\r
211               thread.join();\r
212             } catch (InterruptedException e) {\r
213               e.printStackTrace();\r
214             }\r
215           }\r
216           return pzresp.getActiveClients();\r
217         }\r
218       }  \r
219     } catch (ClassCastException cce) {\r
220       cce.printStackTrace();    \r
221       return "";\r
222     } catch (NullPointerException npe) {\r
223       npe.printStackTrace();\r
224       return "";\r
225     } catch (Exception e) {\r
226       e.printStackTrace();\r
227       return "";\r
228     }\r
229     \r
230   }\r
231                                   \r
232   public String toggleRecord (String recId) {\r
233     if (hasRecord(recId)) {\r
234       pzreq.getRecord().removeParameters();  \r
235       pzresp.put("record", new RecordResponse());\r
236       return "";\r
237     } else {\r
238       pzreq.getRecord().setId(recId);\r
239       doCommand("record");\r
240       return pzresp.getRecord().getActiveClients();\r
241     }\r
242   }\r
243   \r
244   @Override\r
245   public boolean hasRecord (String recId) {\r
246     return pzreq.getCommand("record").hasParameters() && pzresp.getRecord().getRecId().equals(recId);\r
247   }\r
248         \r
249   public String getCurrentStateKey () {    \r
250     return stateMgr.getCurrentState().getKey();\r
251   }\r
252       \r
253   public void setCurrentStateKey(String key) {       \r
254     stateMgr.setCurrentStateKey(key);\r
255   }\r
256       \r
257   protected boolean hasQuery() {        \r
258     return pzreq.getCommand("search").hasParameterValue("query"); \r
259   }\r
260     \r
261     \r
262   @Override\r
263   public ResultsPager getPager () {\r
264     if (pager == null) {\r
265       pager = new ResultsPager(pzresp);      \r
266     } \r
267     return pager;      \r
268   }\r
269   \r
270   @Override\r
271   public ResultsPager setPager (int pageRange) {\r
272     pager =  new ResultsPager(pzresp,pageRange,pzreq);\r
273     return pager;\r
274   }\r
275    \r
276   /**\r
277    * This methods main purpose is to support browser history.\r
278    *  \r
279    * When the browsers back or forward buttons are pressed, a  \r
280    * re-search /might/ be required - namely if the query changes.\r
281    * So, as the UI requests updates of the page (show,facets,\r
282    * etc) this method checks if a search must be executed\r
283    * before those updates are performed.\r
284    *  \r
285    * @see {@link com.indexdata.mkjsf.pazpar2.state.StateManager#setCurrentStateKey} \r
286    * @param commands\r
287    */\r
288   protected void handleQueryStateChanges (String commands) {\r
289     if (stateMgr.hasPendingStateChange("search") && hasQuery()) { \r
290       logger.info("Triggered search: Found pending search change [" + pzreq.getCommand("search").toString() + "], doing search before updating " + commands);      \r
291       pzreq.getSearch().run();\r
292     } \r
293     if (stateMgr.hasPendingStateChange("record") && ! commands.equals("record")) {        \r
294       logger.debug("Found pending record ID change. Doing record before updating " + commands);\r
295       stateMgr.hasPendingStateChange("record",false);\r
296       pzreq.getRecord().run();\r
297     }\r
298   }\r
299   \r
300   /**\r
301    * Executes the command and parses the response to create data objects.\r
302    * If the parsed response is of a known type it will be cached in 'pzresp'\r
303    * \r
304    * @param commandName The command to be executed\r
305    * @return An XML response parsed to form a response data object\r
306    */\r
307   protected ResponseDataObject doCommand(String commandName) {\r
308     Pazpar2Command command = pzreq.getCommand(commandName);\r
309     if (command.spOnly() && isPazpar2Service()) {\r
310       logger.warn("Skipping " + commandName + " - SP-only command, not supported by Pazpar2");\r
311       return new ResponseDataObject();\r
312     } else {\r
313       logger.info("Request "+commandName + ". Search command is: "+ pzreq.getCommand("search").toString());      \r
314       long start = System.currentTimeMillis();\r
315       ResponseDataObject responseObject = command.run();      \r
316       long end = System.currentTimeMillis();\r
317       logger.debug("Executed " + command.getCommandName() + " in " + (end-start) + " ms." );\r
318       return responseObject;\r
319     }\r
320   }\r
321     \r
322   @Override\r
323   public void stateUpdated(String commandName) {\r
324     logger.debug("State change reported for [" + commandName + "]");\r
325     if (commandName.equals("show")) {\r
326       logger.debug("Updating show");\r
327       update(commandName);\r
328     } \r
329   }\r
330   \r
331   public void setServiceProxyUrl(String url) {\r
332     searchClient = spClient;\r
333     setServiceType(SERVICE_TYPE_SP);\r
334     setServiceUrl(url);\r
335   }\r
336   \r
337   public String getServiceProxyUrl () {\r
338     if (isServiceProxyService()) {\r
339       return spClient.getServiceUrl();\r
340     } else {\r
341       return "";\r
342     }\r
343   }\r
344   \r
345   public void setPazpar2Url(String url) {\r
346     searchClient = pz2Client;\r
347     setServiceType(SERVICE_TYPE_PZ2);\r
348     setServiceUrl(url);\r
349   }\r
350   \r
351   public String getPazpar2Url() {\r
352     if (isPazpar2Service()) {\r
353       return pz2Client.getServiceUrl();\r
354     } else {\r
355       return "";\r
356     }\r
357   }\r
358 \r
359   \r
360   @Override\r
361   public void setServiceUrl(String url) {\r
362     if (url!=null && searchClient != null && !url.equals(searchClient.getServiceUrl())) {\r
363       pzreq.getRecord().removeParametersInState();\r
364       pzreq.getSearch().removeParametersInState();\r
365       pzresp.getSp().resetAuthAndBeyond(true);      \r
366       searchClient.setServiceUrl(url);\r
367     }    \r
368   }\r
369   \r
370   public String getServiceUrl() {\r
371     return (searchClient!=null ? searchClient.getServiceUrl() : "");\r
372   }\r
373   \r
374   public void setServiceId () {\r
375     pzreq.getRecord().removeParametersInState();\r
376     pzreq.getSearch().removeParametersInState();\r
377     pzresp.resetSearchAndBeyond();\r
378     pz2Client.setServiceId(pzreq.getInit().getService());\r
379   }\r
380   \r
381   public String getServiceId () {\r
382     return pzreq.getInit().getService();\r
383   }\r
384   \r
385   public boolean getServiceUrlIsDefined() {\r
386     return (searchClient != null && searchClient.hasServiceUrl());\r
387   }\r
388   \r
389   public List<String> getServiceProxyUrls() {\r
390     List<String> urls = new ArrayList<String>();\r
391     urls.add("");\r
392     urls.addAll(serviceProxyUrls);\r
393     return urls;\r
394   }\r
395   \r
396   public List<String> getPazpar2Urls () {\r
397     List<String> urls = new ArrayList<String>();\r
398     urls.add("");\r
399     urls.addAll(pazpar2Urls);\r
400     return urls;\r
401   }\r
402   \r
403   public String getServiceType () {\r
404     return serviceType;\r
405   }\r
406   \r
407   public boolean isPazpar2Service () {\r
408     return serviceType.equals(SERVICE_TYPE_PZ2);\r
409   }\r
410   \r
411   public boolean isServiceProxyService() {\r
412     return serviceType.equals(SERVICE_TYPE_SP);\r
413   }\r
414   \r
415   public boolean serviceIsToBeDecided () {\r
416     return serviceType.equals(SERVICE_TYPE_TBD);\r
417   }\r
418   \r
419   public ServiceProxyClient getSpClient () {\r
420     return spClient;\r
421   }  \r
422   \r
423   @Override\r
424   public boolean getAuthenticationRequired () {\r
425     return spClient.isAuthenticatingClient();\r
426   }\r
427 \r
428   @Override\r
429   public String getCheckHistory () {\r
430     return ":pz2watch:stateForm:windowlocationhash";\r
431   }\r
432     \r
433   @Override\r
434   public String getWatchActiveclients () {\r
435     return ":pz2watch:activeclientsForm:activeclientsField";\r
436   }\r
437   \r
438   @Override\r
439   public String getWatchActiveclientsRecord () {\r
440     return ":pz2watch:activeclientsForm:activeclientsFieldRecord";\r
441   }\r
442 \r
443   @Override\r
444   public void configure(ConfigurationReader reader)\r
445       throws ConfigurationException {\r
446     Configuration config = reader.getConfiguration(this);\r
447     if (config == null) {\r
448       serviceType = SERVICE_TYPE_TBD;\r
449     } else {\r
450       String service = config.get("TYPE");\r
451       if (service == null || service.length()==0) {\r
452         serviceType = SERVICE_TYPE_TBD;\r
453       } else if (serviceTypes.contains(service.toUpperCase())) {        \r
454         setServiceType(service.toUpperCase());\r
455       } else {\r
456         logger.error("Unknown serviceType type in configuration [" + service + "], can be one of " + serviceTypes);\r
457         serviceType = SERVICE_TYPE_TBD;\r
458       }\r
459       serviceProxyUrls = config.getMultiProperty(SERVICE_PROXY_URL_LIST,",");\r
460       pazpar2Urls = config.getMultiProperty(PAZPAR2_URL_LIST, ",");\r
461     }\r
462     logger.info(reader.document());\r
463     logger.info("Service Type is configured to " + serviceType);\r
464     \r
465   }\r
466 \r
467   @Override\r
468   public Map<String, String> getDefaults() {\r
469     return new HashMap<String,String>();\r
470   }\r
471 \r
472   @Override\r
473   public String getModuleName() {\r
474     return MODULE_NAME;\r
475   }\r
476 \r
477   @Override\r
478   public List<String> documentConfiguration() {\r
479     return new ArrayList<String>();\r
480   }\r
481 \r
482   @Override\r
483   public void setServiceTypePZ2() {\r
484     setServiceType(SERVICE_TYPE_PZ2);    \r
485   }\r
486 \r
487   @Override\r
488   public void setServiceTypeSP() {\r
489     setServiceType(SERVICE_TYPE_SP);        \r
490   }\r
491 \r
492   @Override\r
493   public void setServiceTypeTBD() {\r
494     setServiceType(SERVICE_TYPE_TBD);    \r
495   }\r
496   \r
497   private void setServiceType(String type) {\r
498     if (!serviceType.equals(type)  &&\r
499         !serviceType.equals(SERVICE_TYPE_TBD)) {\r
500       resetSearchAndRecordCommands();\r
501       pzresp.getSp().resetAuthAndBeyond(true);\r
502     }\r
503     serviceType = type;\r
504     if (serviceType.equals(SERVICE_TYPE_PZ2)) {\r
505       searchClient = pz2Client;\r
506       logger.info("Setting a Pazpar2 client to serve requests.");\r
507     } else if (serviceType.equals(SERVICE_TYPE_SP)) {\r
508       searchClient = spClient;\r
509       logger.info("Setting a Service Proxy client to serve requests.");\r
510     } else {\r
511       logger.info("Clearing search client. No client defined to serve requests at this point.");\r
512       searchClient = null;\r
513     }\r
514   }\r
515   \r
516   public SearchClient getSearchClient() {\r
517     return searchClient;\r
518   }\r
519   \r
520 }\r