1 package com.indexdata.mkjsf.pazpar2;
\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
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
21 import org.apache.log4j.Logger;
\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.CommandParameter;
\r
32 import com.indexdata.mkjsf.pazpar2.commands.Pazpar2Command;
\r
33 import com.indexdata.mkjsf.pazpar2.commands.Pazpar2Commands;
\r
34 import com.indexdata.mkjsf.pazpar2.data.RecordResponse;
\r
35 import com.indexdata.mkjsf.pazpar2.data.ResponseDataObject;
\r
36 import com.indexdata.mkjsf.pazpar2.data.ResponseParser;
\r
37 import com.indexdata.mkjsf.pazpar2.data.Responses;
\r
38 import com.indexdata.mkjsf.pazpar2.state.StateListener;
\r
39 import com.indexdata.mkjsf.pazpar2.state.StateManager;
\r
40 import com.indexdata.mkjsf.utils.Utils;
\r
42 @Named("pz2") @SessionScoped
\r
43 public class Pz2Bean implements Pz2Interface, StateListener, Configurable, Serializable {
\r
45 private static final String MODULE_NAME = "service";
\r
46 private static String SERVICE_TYPE_TBD = "TBD", SERVICE_TYPE_PZ2 = "PZ2", SERVICE_TYPE_SP = "SP";
\r
47 private static final List<String> serviceTypes =
\r
48 Arrays.asList(SERVICE_TYPE_PZ2,SERVICE_TYPE_SP,SERVICE_TYPE_TBD);
\r
49 private String serviceType = SERVICE_TYPE_TBD;
\r
50 private List<String> serviceProxyUrls = new ArrayList<String>();
\r
51 public static final String SERVICE_PROXY_URL_LIST = "SERVICE_PROXY_URL_LIST";
\r
52 private List<String> pazpar2Urls = new ArrayList<String>();
\r
53 public static final String PAZPAR2_URL_LIST = "PAZPAR2_URL_LIST";
\r
56 private static final long serialVersionUID = 3440277287081557861L;
\r
57 private static Logger logger = Logger.getLogger(Pz2Bean.class);
\r
58 private static Logger responseLogger = Logger.getLogger("com.indexdata.mkjsf.pazpar2.responses");
\r
59 protected Pz2Client pz2Client = null;
\r
60 protected ServiceProxyClient spClient = null;
\r
61 protected SearchClient searchClient = null;
\r
63 @Inject ConfigurationReader configurator;
\r
65 private StateManager stateMgr = null;
\r
66 private Pazpar2Commands pzreq = null;
\r
67 private Responses pzresp = null;
\r
68 private ErrorCentral errors = null;
\r
70 protected ResultsPager pager = null;
\r
72 protected ErrorHelper errorHelper = null;
\r
75 logger.info("Instantiating pz2 bean [" + Utils.objectId(this) + "]");
\r
78 public static Pz2Bean get() {
\r
79 FacesContext context = FacesContext.getCurrentInstance();
\r
80 return (Pz2Bean) context.getApplication().evaluateExpressionGet(context, "#{pz2}", Object.class);
\r
84 public void postConstruct() {
\r
85 logger.info("Pz2Bean PostConstruct of " + this);
\r
86 stateMgr = new StateManager();
\r
87 pzreq = new Pazpar2Commands();
\r
88 pzresp = new Responses();
\r
89 errors = new ErrorCentral();
\r
90 pzresp.setErrorHelper(errors.getHelper());
\r
92 logger.debug("Pz2Bean PostConstruct: Configurator is " + configurator);
\r
93 logger.debug(Utils.objectId(this) + " will instantiate a Pz2Client next.");
\r
94 pz2Client = new Pz2Client();
\r
95 configureClient(pz2Client,configurator);
\r
96 spClient = new ServiceProxyClient();
\r
97 configureClient(spClient,configurator);
\r
99 this.configure(configurator);
\r
100 } catch (ConfigurationException e) {
\r
101 logger.error("There was a problem configuring the Pz2Bean (\"pz2\")");
\r
102 e.printStackTrace();
\r
104 stateMgr.addStateListener(this);
\r
108 @Target({java.lang.annotation.ElementType.TYPE,
\r
109 java.lang.annotation.ElementType.METHOD,
\r
110 java.lang.annotation.ElementType.PARAMETER,
\r
111 java.lang.annotation.ElementType.FIELD})
\r
112 @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
\r
113 public @interface Preferred{}
\r
115 @Produces @Preferred @SessionScoped @Named("pzresp") public Responses getPzresp () {
\r
116 logger.info("Producing pzresp");
\r
120 @Produces @Preferred @SessionScoped @Named("pzreq") public Pazpar2Commands getPzreq () {
\r
121 logger.info("Producing pzreq");
\r
125 @Produces @Preferred @SessionScoped @Named("errors") public ErrorCentral getErrors() {
\r
126 logger.info("Producing errors");
\r
130 @Produces @Preferred @SessionScoped @Named("stateMgr") public StateManager getStateMgr() {
\r
131 logger.info("Produces stateMgr");
\r
136 public void configureClient(SearchClient client, ConfigurationReader configReader) {
\r
137 logger.debug(Utils.objectId(this) + " will configure search client for the session");
\r
139 client.configure(configReader);
\r
140 } catch (ConfigurationException e) {
\r
141 logger.debug("Pz2Bean adding configuration error");
\r
142 errors.addConfigurationError(new ConfigurationError("Search Client","Configuration",e.getMessage()));
\r
144 logger.info(configReader.document());
\r
145 pzresp.getSp().resetAuthAndBeyond(true);
\r
148 public void resetSearchAndRecordCommands () {
\r
149 pzreq.getRecord().removeParametersInState();
\r
150 pzreq.getSearch().removeParametersInState();
\r
153 public void doSearch(String query) {
\r
154 pzreq.getSearch().setParameter(new CommandParameter("query","=",query));
\r
158 public void doSearch() {
\r
160 if (errors.hasConfigurationErrors()) {
\r
161 logger.error("Ignoring search request due to configuration errors.");
\r
162 } else if (searchClient == null){
\r
163 logger.error("No search client defined. A client must either be pre-configured or selected before searching.");
\r
164 errors.addConfigurationError(new ConfigurationError("No client defined","Client is null","No search client defined. A client must be pre-configured or selected runtime, prior to searching."));
\r
166 stateMgr.hasPendingStateChange("search",false);
\r
167 pzresp.resetSearchAndBeyond();
\r
168 pzreq.getRecord().removeParametersInState();
\r
169 pzreq.getShow().setParameterInState(new CommandParameter("start","=",0));
\r
170 logger.debug(Utils.objectId(this) + " is searching using "+pzreq.getCommand("search").getUrlEncodedParameterValue("query"));
\r
171 searchClient.setSearchCommand(pzreq.getCommand("search"));
\r
172 doCommand("search");
\r
174 } catch (NullPointerException npe) {
\r
175 npe.printStackTrace();
\r
179 public String doRecord() {
\r
180 if (errors.hasConfigurationErrors()) {
\r
181 logger.error("Ignoring record request due to configuration errors.");
\r
183 } else if (!pzreq.getCommand("record").hasParameterValue("id")) {
\r
184 logger.debug("Ignoring record request due to no id parameter.");
\r
186 } else if (pzresp.getSearch().hasApplicationError()) {
\r
187 logger.debug("Ignoring record request due search error.");
\r
190 logger.debug("Executing record command");
\r
191 doCommand("record");
\r
192 return pzresp.getRecord().getActiveClients();
\r
197 * Refreshes 'show', 'stat', 'termlist', and 'bytarget' data object from pazpar2
\r
199 * @return Number of activeclients at the time of the 'show' command.
\r
201 public String update () {
\r
202 logger.debug("Updating show,stat,termlist,bytarget from pazpar2");
\r
203 if (errors.hasConfigurationErrors()) {
\r
204 logger.error("Ignoring show,stat,termlist,bytarget commands due to configuration errors.");
\r
206 } else if (pzresp.getSearch().hasApplicationError()) {
\r
207 logger.error("Ignoring show,stat,termlist,bytarget commands due to problem with most recent search.");
\r
209 } else if (!hasQuery()) {
\r
210 logger.debug("Ignoring show,stat,termlist,bytarget commands because there is not yet a query.");
\r
213 return update("show,stat,termlist,bytarget");
\r
218 * Refreshes the data objects listed in 'commands' from pazpar2
\r
221 * @return Number of activeclients at the time of the 'show' command,
\r
222 * or 'new' if search was just initiated.
\r
224 public String update (String commands) {
\r
225 logger.debug("Request to update: " + commands);
\r
227 if (commands.equals("search")) {
\r
230 } else if (commands.equals("record")) {
\r
232 } else if (pzresp.getSearch().isNew()) {
\r
233 // For returning notification of 'search started' quickly to UI
\r
234 logger.info("New search. Marking it old, then returning 'new' to trigger another round-trip.");
\r
235 pzresp.getSearch().setIsNew(false);
\r
238 handleQueryStateChanges(commands);
\r
239 if (pzresp.getSearch().hasApplicationError()) {
\r
240 logger.error("The command(s) " + commands + " cancelled because the latest search command had an error.");
\r
242 } else if (errors.hasConfigurationErrors()) {
\r
243 logger.error("The command(s) " + commands + " cancelled due to configuration errors.");
\r
246 logger.debug("Processing request for " + commands);
\r
247 List<CommandThread> threadList = new ArrayList<CommandThread>();
\r
248 StringTokenizer tokens = new StringTokenizer(commands,",");
\r
249 while (tokens.hasMoreElements()) {
\r
250 threadList.add(new CommandThread(pzreq.getCommand(tokens.nextToken()),searchClient));
\r
252 for (CommandThread thread : threadList) {
\r
255 for (CommandThread thread : threadList) {
\r
258 } catch (InterruptedException e) {
\r
259 e.printStackTrace();
\r
262 for (CommandThread thread : threadList) {
\r
263 String commandName = thread.getCommand().getCommandName();
\r
264 ClientCommandResponse response = (ClientCommandResponse) thread.getCommandResponse();
\r
265 responseLogger.debug("Response was: " + response.getResponseString());
\r
266 ResponseDataObject responseObject = ResponseParser.getParser().getDataObject(response);
\r
267 if (ResponseParser.docTypes.contains(responseObject.getType())) {
\r
268 pzresp.put(commandName, responseObject);
\r
270 logger.info("Unknown doc type [" + responseObject.getType() + "]. Was not cached.");
\r
273 return pzresp.getActiveClients();
\r
276 } catch (ClassCastException cce) {
\r
277 cce.printStackTrace();
\r
279 } catch (NullPointerException npe) {
\r
280 npe.printStackTrace();
\r
282 } catch (Exception e) {
\r
283 e.printStackTrace();
\r
289 public String toggleRecord (String recId) {
\r
290 if (hasRecord(recId)) {
\r
291 pzreq.getRecord().removeParameters();
\r
292 pzresp.put("record", new RecordResponse());
\r
295 pzreq.getRecord().setId(recId);
\r
296 doCommand("record");
\r
297 return pzresp.getRecord().getActiveClients();
\r
302 public boolean hasRecord (String recId) {
\r
303 return pzreq.getCommand("record").hasParameters() && pzresp.getRecord().getRecId().equals(recId);
\r
306 public String getCurrentStateKey () {
\r
307 return stateMgr.getCurrentState().getKey();
\r
310 public void setCurrentStateKey(String key) {
\r
311 stateMgr.setCurrentStateKey(key);
\r
314 protected boolean hasQuery() {
\r
315 return pzreq.getCommand("search").hasParameterValue("query");
\r
320 public ResultsPager getPager () {
\r
321 if (pager == null) {
\r
322 pager = new ResultsPager(pzresp);
\r
328 public ResultsPager setPager (int pageRange) {
\r
329 pager = new ResultsPager(pzresp,pageRange,pzreq);
\r
334 * This methods main purpose is to support browser history.
\r
336 * When the browsers back or forward buttons are pressed, a
\r
337 * re-search /might/ be required - namely if the query changes.
\r
338 * So, as the UI requests updates of the page (show,facets,
\r
339 * etc) this method checks if a search must be executed
\r
340 * before those updates are performed.
\r
342 * @see {@link com.indexdata.mkjsf.pazpar2.state.StateManager#setCurrentStateKey}
\r
345 protected void handleQueryStateChanges (String commands) {
\r
346 if (stateMgr.hasPendingStateChange("search") && hasQuery()) {
\r
347 logger.info("Triggered search: Found pending search change [" + pzreq.getCommand("search").toString() + "], doing search before updating " + commands);
\r
350 if (stateMgr.hasPendingStateChange("record") && ! commands.equals("record")) {
\r
351 logger.debug("Found pending record ID change. Doing record before updating " + commands);
\r
352 stateMgr.hasPendingStateChange("record",false);
\r
353 if (pzreq.getCommand("record").hasParameterValue("id")) {
\r
356 pzresp.put("record", new RecordResponse());
\r
362 * Executes the command and parses the response to create data objects.
\r
363 * If the parsed response is of a known type it will be cached in 'pzresp'
\r
365 * @param commandName The command to be executed
\r
366 * @return An XML response parsed to form a response data object
\r
368 protected ResponseDataObject doCommand(String commandName) {
\r
369 Pazpar2Command command = pzreq.getCommand(commandName);
\r
370 if (command.spOnly() && isPazpar2Service()) {
\r
371 logger.warn("Skipping " + commandName + " - SP-only command, not supported by Pazpar2");
\r
372 return new ResponseDataObject();
\r
374 logger.info("Request "+commandName + ". Search command is: "+ pzreq.getCommand("search").toString());
\r
375 long start = System.currentTimeMillis();
\r
376 ResponseDataObject responseObject = command.run();
\r
377 long end = System.currentTimeMillis();
\r
378 logger.debug("Executed " + command.getCommandName() + " in " + (end-start) + " ms." );
\r
379 return responseObject;
\r
384 public void stateUpdated(String commandName) {
\r
385 logger.debug("State change reported for [" + commandName + "]");
\r
386 if (commandName.equals("show")) {
\r
387 logger.debug("Updating show");
\r
388 update(commandName);
\r
392 public void setServiceProxyUrl(String url) {
\r
393 searchClient = spClient;
\r
394 setServiceType(SERVICE_TYPE_SP);
\r
395 setServiceUrl(url);
\r
398 public String getServiceProxyUrl () {
\r
399 if (isServiceProxyService()) {
\r
400 return spClient.getServiceUrl();
\r
406 public void setPazpar2Url(String url) {
\r
407 searchClient = pz2Client;
\r
408 setServiceType(SERVICE_TYPE_PZ2);
\r
409 setServiceUrl(url);
\r
412 public String getPazpar2Url() {
\r
413 if (isPazpar2Service()) {
\r
414 return pz2Client.getServiceUrl();
\r
422 public void setServiceUrl(String url) {
\r
423 if (url!=null && searchClient != null && !url.equals(searchClient.getServiceUrl())) {
\r
424 pzreq.getRecord().removeParametersInState();
\r
425 pzreq.getSearch().removeParametersInState();
\r
426 pzresp.getSp().resetAuthAndBeyond(true);
\r
427 searchClient.setServiceUrl(url);
\r
428 // pzreq.setService(this);
\r
432 public String getServiceUrl() {
\r
433 return (searchClient!=null ? searchClient.getServiceUrl() : "");
\r
436 public void setServiceId () {
\r
437 pzreq.getRecord().removeParametersInState();
\r
438 pzreq.getSearch().removeParametersInState();
\r
439 pzresp.resetSearchAndBeyond();
\r
440 pz2Client.setServiceId(pzreq.getInit().getService());
\r
443 public String getServiceId () {
\r
444 return pzreq.getInit().getService();
\r
447 public boolean getServiceUrlIsDefined() {
\r
448 return (searchClient != null && searchClient.hasServiceUrl());
\r
451 public List<String> getServiceProxyUrls() {
\r
452 List<String> urls = new ArrayList<String>();
\r
454 urls.addAll(serviceProxyUrls);
\r
458 public List<String> getPazpar2Urls () {
\r
459 List<String> urls = new ArrayList<String>();
\r
461 urls.addAll(pazpar2Urls);
\r
465 public String getServiceType () {
\r
466 return serviceType;
\r
469 public boolean isPazpar2Service () {
\r
470 return serviceType.equals(SERVICE_TYPE_PZ2);
\r
473 public boolean isServiceProxyService() {
\r
474 return serviceType.equals(SERVICE_TYPE_SP);
\r
477 public boolean serviceIsToBeDecided () {
\r
478 return serviceType.equals(SERVICE_TYPE_TBD);
\r
481 public ServiceProxyClient getSpClient () {
\r
486 public boolean getAuthenticationRequired () {
\r
487 return spClient.isAuthenticatingClient();
\r
491 public String getCheckHistory () {
\r
492 return ":pz2watch:stateForm:windowlocationhash";
\r
496 public String getWatchActiveclients () {
\r
497 return ":pz2watch:activeclientsForm:activeclientsField";
\r
501 public String getWatchActiveclientsRecord () {
\r
502 return ":pz2watch:activeclientsForm:activeclientsFieldRecord";
\r
506 public void configure(ConfigurationReader reader)
\r
507 throws ConfigurationException {
\r
508 Configuration config = reader.getConfiguration(this);
\r
509 if (config == null) {
\r
510 serviceType = SERVICE_TYPE_TBD;
\r
512 String service = config.get("TYPE");
\r
513 if (service == null || service.length()==0) {
\r
514 serviceType = SERVICE_TYPE_TBD;
\r
515 } else if (serviceTypes.contains(service.toUpperCase())) {
\r
516 setServiceType(service.toUpperCase());
\r
518 logger.error("Unknown serviceType type in configuration [" + service + "], can be one of " + serviceTypes);
\r
519 serviceType = SERVICE_TYPE_TBD;
\r
521 serviceProxyUrls = config.getMultiProperty(SERVICE_PROXY_URL_LIST,",");
\r
522 pazpar2Urls = config.getMultiProperty(PAZPAR2_URL_LIST, ",");
\r
524 logger.info(reader.document());
\r
525 logger.info("Service Type is configured to " + serviceType);
\r
530 public Map<String, String> getDefaults() {
\r
531 return new HashMap<String,String>();
\r
535 public String getModuleName() {
\r
536 return MODULE_NAME;
\r
540 public List<String> documentConfiguration() {
\r
541 return new ArrayList<String>();
\r
545 public void setServiceTypePZ2() {
\r
546 setServiceType(SERVICE_TYPE_PZ2);
\r
550 public void setServiceTypeSP() {
\r
551 setServiceType(SERVICE_TYPE_SP);
\r
555 public void setServiceTypeTBD() {
\r
556 setServiceType(SERVICE_TYPE_TBD);
\r
559 private void setServiceType(String type) {
\r
560 if (!serviceType.equals(type) &&
\r
561 !serviceType.equals(SERVICE_TYPE_TBD)) {
\r
562 resetSearchAndRecordCommands();
\r
563 pzresp.getSp().resetAuthAndBeyond(true);
\r
565 serviceType = type;
\r
566 if (serviceType.equals(SERVICE_TYPE_PZ2)) {
\r
567 searchClient = pz2Client;
\r
568 logger.info("Setting a Pazpar2 client to serve requests.");
\r
569 } else if (serviceType.equals(SERVICE_TYPE_SP)) {
\r
570 searchClient = spClient;
\r
571 logger.info("Setting a Service Proxy client to serve requests.");
\r
573 logger.info("Clearing search client. No client defined to serve requests at this point.");
\r
574 searchClient = null;
\r
578 public SearchClient getSearchClient() {
\r
579 return searchClient;
\r