4d7ac5f8d14a1193f255883a7edb109308551789
[mkjsf-moved-to-github.git] / src / main / java / com / indexdata / pz2utils4jsf / pazpar2 / Pz2Session.java
1 package com.indexdata.pz2utils4jsf.pazpar2;\r
2 \r
3 import java.util.ArrayList;\r
4 import java.util.List;\r
5 import java.util.Map;\r
6 import java.util.StringTokenizer;\r
7 import java.util.concurrent.ConcurrentHashMap;\r
8 \r
9 import javax.annotation.PostConstruct;\r
10 import javax.inject.Inject;\r
11 \r
12 import org.apache.log4j.Logger;\r
13 \r
14 import com.indexdata.pz2utils4jsf.config.ConfigurationReader;\r
15 import com.indexdata.pz2utils4jsf.controls.ResultsPager;\r
16 import com.indexdata.pz2utils4jsf.errors.ConfigurationError;\r
17 import com.indexdata.pz2utils4jsf.errors.ConfigurationException;\r
18 import com.indexdata.pz2utils4jsf.errors.ErrorHelper;\r
19 import com.indexdata.pz2utils4jsf.errors.ErrorInterface;\r
20 import com.indexdata.pz2utils4jsf.pazpar2.commands.CommandParameter;\r
21 import com.indexdata.pz2utils4jsf.pazpar2.commands.CommandReadOnly;\r
22 import com.indexdata.pz2utils4jsf.pazpar2.commands.Pazpar2Command;\r
23 import com.indexdata.pz2utils4jsf.pazpar2.commands.Pazpar2Commands;\r
24 import com.indexdata.pz2utils4jsf.pazpar2.data.ByTarget;\r
25 import com.indexdata.pz2utils4jsf.pazpar2.data.CommandError;\r
26 import com.indexdata.pz2utils4jsf.pazpar2.data.Pazpar2ResponseData;\r
27 import com.indexdata.pz2utils4jsf.pazpar2.data.Pazpar2ResponseParser;\r
28 import com.indexdata.pz2utils4jsf.pazpar2.data.RecordResponse;\r
29 import com.indexdata.pz2utils4jsf.pazpar2.data.SearchResponse;\r
30 import com.indexdata.pz2utils4jsf.pazpar2.data.ShowResponse;\r
31 import com.indexdata.pz2utils4jsf.pazpar2.data.StatResponse;\r
32 import com.indexdata.pz2utils4jsf.pazpar2.data.TermListsResponse;\r
33 import com.indexdata.pz2utils4jsf.pazpar2.data.TermResponse;\r
34 import com.indexdata.pz2utils4jsf.pazpar2.state.StateListener;\r
35 import com.indexdata.pz2utils4jsf.pazpar2.state.StateManager;\r
36 import com.indexdata.pz2utils4jsf.utils.Utils;\r
37 \r
38 @ForStraightPz2\r
39 public class Pz2Session implements Pz2Interface, StateListener {\r
40     \r
41   private static final long serialVersionUID = 3947514708343320514L;\r
42   private static Logger logger = Logger.getLogger(Pz2Session.class);\r
43   \r
44   protected Map<String,Pazpar2ResponseData> dataObjects = new ConcurrentHashMap<String,Pazpar2ResponseData>();\r
45   \r
46   @Inject StateManager stateMgr;\r
47   @Inject Pazpar2Commands req;\r
48   \r
49   protected ErrorHelper errorHelper = null;\r
50   \r
51   protected List<ErrorInterface> configurationErrors = null;\r
52   protected SearchClient searchClient = null;       \r
53   protected ResultsPager pager = null; \r
54     \r
55   public Pz2Session () {\r
56     logger.info("Instantiating pz2 session object [" + Utils.objectId(this) + "]");        \r
57   }\r
58   \r
59   @PostConstruct\r
60   public void listenToStateManager() {\r
61     logger.debug("in post-construct of Pz2Session stateMgr is " + stateMgr);\r
62     logger.debug("in post-construct req is " + req);\r
63     stateMgr.addStateListener(this);\r
64   }\r
65     \r
66   public void configureClient(SearchClient searchClient, ConfigurationReader configReader) {\r
67     configurationErrors = new ArrayList<ErrorInterface>();\r
68     errorHelper = new ErrorHelper(configReader);    \r
69     logger.debug(Utils.objectId(this) + " will configure search client for the session");\r
70     try {\r
71       searchClient.configure(configReader);            \r
72       // At the time of writing this search client is injected using Weld. \r
73       // However, the client is used for asynchronously sending off requests\r
74       // to the server AND propagation of context to threads is currently \r
75       // not supported. Trying to do so throws a WELD-001303 error. \r
76       // To avoid that, a context free client is cloned from the context \r
77       // dependent one. \r
78       // If propagation to threads gets supported, the cloning can go. \r
79       this.searchClient = searchClient.cloneMe();         \r
80     } catch (ConfigurationException e) {\r
81       configurationErrors.add(new ConfigurationError("Search Client","Configuration",e.getMessage(),new ErrorHelper(configReader)));          \r
82     } \r
83     logger.info(configReader.document());\r
84     resetDataObjects();\r
85   }\r
86       \r
87   public void doSearch(String query) {\r
88     setCommandParameter("search",new CommandParameter("query","=",query));     \r
89     doSearch();\r
90   }\r
91 \r
92   public void doSearch() { \r
93     stateMgr.hasPendingStateChange("search",false);\r
94     resetDataObjects();\r
95     removeCommand("record");\r
96     setCommandParameter("show",new CommandParameter("start","=",0));    \r
97     logger.debug(Utils.objectId(this) + " is searching using "+req.getCommandReadOnly("search").getUrlEncodedParameterValue("query"));\r
98     doCommand("search");    \r
99   }\r
100       \r
101   /**\r
102    * Refreshes 'show', 'stat', 'termlist', and 'bytarget' data object from pazpar2\r
103    * \r
104    * @return Number of activeclients at the time of the 'show' command.\r
105    */\r
106   public String update () {\r
107     logger.debug("Updating show,stat,termlist,bytarget from pazpar2");\r
108     return update("show,stat,termlist,bytarget");\r
109   }\r
110    \r
111   /**\r
112    * Refreshes the data objects listed in 'commands' from pazpar2\r
113    * \r
114    * @param commands\r
115    * @return Number of activeclients at the time of the 'show' command\r
116    */\r
117   public String update (String commands) {\r
118     if (! hasConfigurationErrors()) {\r
119       if (hasQuery()) {\r
120         handleQueryStateChanges(commands);\r
121         logger.debug("Processing request for " + commands); \r
122         List<CommandThread> threadList = new ArrayList<CommandThread>();\r
123         StringTokenizer tokens = new StringTokenizer(commands,",");\r
124         while (tokens.hasMoreElements()) {          \r
125           threadList.add(new CommandThread(req.getCommandReadOnly(tokens.nextToken()),searchClient));            \r
126         }\r
127         for (CommandThread thread : threadList) {\r
128           thread.start();\r
129         }\r
130         for (CommandThread thread : threadList) {\r
131           try {\r
132             thread.join();\r
133           } catch (InterruptedException e) {\r
134             e.printStackTrace();\r
135           }\r
136         }\r
137         for (CommandThread thread : threadList) {\r
138            String commandName = thread.getCommand().getName();\r
139            String response = thread.getResponse();\r
140            logger.debug("Response was: " + response);\r
141            Pazpar2ResponseData responseObject = Pazpar2ResponseParser.getParser().getDataObject(response);\r
142            dataObjects.put(commandName, responseObject);        \r
143         }\r
144         if (commands.equals("record")) {\r
145           logger.debug("Record: Active clients: "+getRecord().getActiveClients());\r
146           return getRecord().getActiveClients();\r
147         } else {\r
148           return getActiveClients();\r
149         }  \r
150       } else {\r
151         logger.debug("Skipped requests for " + commands + " as there's not yet a query."); \r
152         resetDataObjects();\r
153         return "0";\r
154       }\r
155     } else {\r
156       logger.error("Did not attempt to execute query since there are configuration errors.");\r
157       return "0";\r
158     }\r
159     \r
160   }\r
161             \r
162                     \r
163   public String toggleRecord (String recId) {\r
164     if (hasRecord(recId)) {\r
165       removeCommand("record");  \r
166       dataObjects.put("record", new RecordResponse());\r
167       return "";\r
168     } else {\r
169       req.getRecord().setRecordId(recId);\r
170       return doCommand("record");\r
171     }\r
172   }\r
173   \r
174   @Override\r
175   public boolean hasRecord (String recId) {\r
176     return req.getCommandReadOnly("record").hasParameters() && getRecord().getRecId().equals(recId);\r
177   }\r
178       \r
179   public ShowResponse getShow () {\r
180     return ((ShowResponse) dataObjects.get("show"));\r
181   }\r
182   \r
183   public StatResponse getStat () {\r
184     return ((StatResponse) dataObjects.get("stat"));\r
185   }\r
186   \r
187   public RecordResponse getRecord() {\r
188     return ((RecordResponse) dataObjects.get("record"));\r
189   }\r
190   \r
191   public TermListsResponse getTermLists () {\r
192     return ((TermListsResponse) dataObjects.get("termlist"));\r
193   }\r
194   \r
195   public List<TermResponse> getFacetTerms (String facet, int count) {\r
196     return (getTermLists().getTermList(facet).getTerms(count));\r
197   }\r
198     \r
199   public List<TermResponse> getFacetTerms (String facet) {\r
200     return (getTermLists().getTermList(facet).getTerms());\r
201   }\r
202   \r
203   public ByTarget getByTarget() {\r
204     return ((ByTarget) dataObjects.get("bytarget"));\r
205   }\r
206   \r
207   \r
208   public String getCurrentStateKey () {    \r
209     return stateMgr.getCurrentState().getKey();\r
210   }\r
211       \r
212   public void setCurrentStateKey(String key) {       \r
213     stateMgr.setCurrentStateKey(key);\r
214   }\r
215   \r
216   public boolean hasConfigurationErrors () {\r
217       return (configurationErrors.size()>0);      \r
218   }\r
219   \r
220   public boolean hasCommandErrors () {\r
221     if (dataObjects.get("search").hasApplicationError()) {\r
222       logger.info("Error detected in search");\r
223       return true;\r
224     }\r
225     for (String name : dataObjects.keySet()) {\r
226       if (dataObjects.get(name).hasApplicationError()) {\r
227         logger.info("Error detected in " + name);\r
228         return true;\r
229       }\r
230     }    \r
231     return false;    \r
232   }\r
233   \r
234   /**\r
235    * Returns true if application error found in any response data objects \r
236    */\r
237   public boolean hasErrors () {\r
238     return hasConfigurationErrors() || hasCommandErrors();\r
239   }\r
240 \r
241   public List<ErrorInterface> getConfigurationErrors() {    \r
242     return configurationErrors;\r
243   }\r
244   \r
245   /**\r
246    * Returns a search command error, if any, otherwise the first\r
247    * error found for an arbitrary command, if any, otherwise\r
248    * an empty dummy error. \r
249    */    \r
250   public ErrorInterface getCommandError() {\r
251     CommandError error = new CommandError();    \r
252     if (dataObjects.get("search").hasApplicationError()) {\r
253       error = dataObjects.get("search").getApplicationError();                        \r
254     } else {\r
255       for (String name : dataObjects.keySet()) {     \r
256         if (dataObjects.get(name).hasApplicationError()) {     \r
257           error = dataObjects.get(name).getApplicationError(); \r
258           break;\r
259         } \r
260       }\r
261     }\r
262     error.setErrorHelper(errorHelper);\r
263     return error;         \r
264   }\r
265   \r
266   protected boolean hasQuery() {    \r
267     return req.getSearch().getParameter("query") != null && req.getSearch().getParameter("query").getValueWithExpressions().length()>0;\r
268   }\r
269     \r
270   public boolean hasRecords () {\r
271     return getStat().getRecords() > 0            \r
272            && getShow().getHits() != null \r
273            && getShow().getHits().size()>0;\r
274   }\r
275     \r
276   public ResultsPager getPager () {\r
277     if (pager == null) {\r
278       pager = new ResultsPager(this);      \r
279     } \r
280     return pager;      \r
281   }\r
282   \r
283   public ResultsPager setPager (int pageRange) {\r
284     pager =  new ResultsPager(this,pageRange,req);\r
285     return pager;\r
286   }\r
287   \r
288   protected ErrorHelper getTroubleshooter() {\r
289     return errorHelper;\r
290   }\r
291   \r
292   protected void handleQueryStateChanges (String commands) {\r
293     if (stateMgr.hasPendingStateChange("search") && hasQuery()) { \r
294       logger.debug("Found pending search change. Doing search before updating " + commands);      \r
295       doSearch();\r
296     } \r
297     if (stateMgr.hasPendingStateChange("record") && ! commands.equals("record")) {        \r
298       logger.debug("Found pending record ID change. Doing record before updating " + commands);\r
299       stateMgr.hasPendingStateChange("record",false);\r
300       if (req.getCommandReadOnly("record").hasParameters()) {\r
301         update("record");\r
302       } else {\r
303         removeCommand("record");  \r
304         dataObjects.put("record", new RecordResponse());\r
305       }\r
306     }\r
307   }\r
308 \r
309   protected String getActiveClients() {    \r
310     if (getShow()!=null) {\r
311       logger.debug("Active clients: "+getShow().getActiveClients());\r
312       return getShow().getActiveClients();\r
313     } else {\r
314       return "";\r
315     }\r
316   }\r
317 \r
318   /**\r
319    * Returns a Pazpar2 command 'detached' from the current Pazpar2 state.\r
320    * \r
321    * 'Detached' is meant to imply that this is a copy of a command in the \r
322    * current state, detached so as to NOT change the current state if \r
323    * modified. It can be viewed and executed, however. \r
324    * \r
325    * In order to modify the command with effect for subsequent searches,\r
326    * it must be checked back into the StateManager, which will\r
327    * then create a new current Pazpar2 state as needed.\r
328    *  \r
329    * @param name\r
330    * @return\r
331    */\r
332   protected Pazpar2Command getCommand(String name) {\r
333     return req.getCommand(name);\r
334   }\r
335   \r
336   /** \r
337    * Returns an interface to a Pazpar2Command with only String getters.\r
338    * \r
339    * Since the command cannot be modified (unless it is cast) we can avoid \r
340    * cloning it before returning it from the current state. \r
341    * It can be used for log statements, checks and for performing the \r
342    * actual pazpar2 request. \r
343    * \r
344    * @param name\r
345    * @return\r
346    */\r
347   protected CommandReadOnly getCommandReadOnly(String name) {\r
348     return req.getCommandReadOnly(name);\r
349   }\r
350 \r
351   \r
352   protected void setCommandParameter(String commandName, CommandParameter parameter) {\r
353     logger.debug("Setting parameter for " + commandName + ": " + parameter);\r
354     Pazpar2Command command = req.getCommand(commandName);\r
355     command.setParameter(parameter);\r
356     stateMgr.checkIn(command);    \r
357   }\r
358   \r
359   \r
360   protected void removeCommandParameter(String commandName, String parameterName) {\r
361     Pazpar2Command command = req.getCommand(commandName);\r
362     command.removeParameter(parameterName);\r
363     stateMgr.checkIn(command);    \r
364   }\r
365   \r
366   protected void removeCommand (String commandName) {\r
367     Pazpar2Command command = req.getCommand(commandName);\r
368     command.removeParameters();\r
369     stateMgr.checkIn(command);\r
370   }\r
371     \r
372   protected String getCommandParameterValue (String commandName, String parameterName, String defaultValue) {    \r
373     CommandReadOnly command = req.getCommandReadOnly(commandName);\r
374     if (command != null) {\r
375       String parameter = command.getParameterValue(parameterName);\r
376       if (parameter != null) {\r
377         return parameter;\r
378       }\r
379     }\r
380     return defaultValue;    \r
381   }\r
382     \r
383   protected int getCommandParameterValue (String commandName, String parameterName, int defaultValue) {\r
384     CommandReadOnly command = req.getCommandReadOnly(commandName);\r
385     if (command != null) {\r
386       String parameter = command.getParameterValue(parameterName);\r
387       if (parameter != null) {\r
388         return Integer.parseInt(parameter);\r
389       }\r
390     }\r
391     return defaultValue;    \r
392   }\r
393 \r
394   protected String doCommand(String commandName) {             \r
395     logger.debug(req.getCommandReadOnly(commandName).getEncodedQueryString() + ": Results for "+ req.getCommandReadOnly("search").getEncodedQueryString());\r
396     return update(commandName);\r
397   }\r
398   \r
399   protected void resetDataObjects() {\r
400     logger.debug("Resetting show,stat,termlist,bytarget,search response objects.");\r
401     dataObjects = new ConcurrentHashMap<String,Pazpar2ResponseData>();\r
402     dataObjects.put("show", new ShowResponse());\r
403     dataObjects.put("stat", new StatResponse());\r
404     dataObjects.put("termlist", new TermListsResponse());\r
405     dataObjects.put("bytarget", new ByTarget());\r
406     dataObjects.put("record", new RecordResponse());\r
407     dataObjects.put("search", new SearchResponse());\r
408   }\r
409   \r
410 \r
411   @Override\r
412   public void stateUpdated(String commandName) {\r
413     logger.debug("State change reported for [" + commandName + "]");\r
414     if (commandName.equals("show")) {\r
415       logger.debug("Updating show");\r
416       update(commandName);\r
417     } \r
418   }\r
419   \r
420 }\r