X-Git-Url: http://lists.indexdata.dk/cgi-bin?a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fcom%2Findexdata%2Fmkjsf%2Fpazpar2%2FPz2Service.java;h=8b86fd6faad35fb3c9e99c8bae649bdb3a09920d;hb=5211ca6a4525491499ac763373a703e35bdce37f;hp=f38d90db49e4109637fa27b08ecded61c7ed840a;hpb=45780ca51a3f1a967acda7c4d7d71aebe7081a67;p=mkjsf-moved-to-github.git diff --git a/src/main/java/com/indexdata/mkjsf/pazpar2/Pz2Service.java b/src/main/java/com/indexdata/mkjsf/pazpar2/Pz2Service.java index f38d90d..8b86fd6 100644 --- a/src/main/java/com/indexdata/mkjsf/pazpar2/Pz2Service.java +++ b/src/main/java/com/indexdata/mkjsf/pazpar2/Pz2Service.java @@ -28,17 +28,47 @@ import com.indexdata.mkjsf.errors.ConfigurationError; import com.indexdata.mkjsf.errors.ConfigurationException; import com.indexdata.mkjsf.errors.ErrorCentral; import com.indexdata.mkjsf.errors.ErrorHelper; -import com.indexdata.mkjsf.pazpar2.commands.Pazpar2Command; +import com.indexdata.mkjsf.errors.MissingConfigurationContextException; import com.indexdata.mkjsf.pazpar2.commands.Pazpar2Commands; import com.indexdata.mkjsf.pazpar2.data.RecordResponse; -import com.indexdata.mkjsf.pazpar2.data.ResponseDataObject; import com.indexdata.mkjsf.pazpar2.data.Responses; import com.indexdata.mkjsf.pazpar2.state.StateListener; import com.indexdata.mkjsf.pazpar2.state.StateManager; import com.indexdata.mkjsf.utils.Utils; +/** + * Pz2Service is the main controller of the search logic, used for selecting the service + * type (which can be done by configuration and/or run-time), selecting which search client + * to use, and performing high-level control of request cycles and state management. + *

+ * Command and response beans are also obtained through Pz2Service - although it is + * transparent to the UI that they are retrieved through this object. + *

+ *

+ * Pz2Service is exposed to the UI as pz2. However, if the service is pre-configured, + * the Faces pages might never need to reference pz2 explicitly. Indirectly they will, + * though, if the polling mechanism in the tag <pz2utils:pz2watch> is used. + * + * + *

Configuration

+ * + * Configuration name: service + * + *

When configuring the client using the Mk2Config scheme, this is the prefix to + * use in the .properties file. When using web.xml context parameters for configuration + * the configuration name has no effect.

+ * + *

Pz2Service will acknowledge following configuration parameters: + * + *

+ * + * Possible values for service TYPE are: PZ2, SP, TBD. "TBD", meaning "to-be-decided runtime", is the default. + * + **/ @Named("pz2") @SessionScoped -public class Pz2Service implements Pz2Interface, StateListener, Configurable, Serializable { +public class Pz2Service implements StateListener, Configurable, Serializable { private static final String MODULE_NAME = "service"; private static String SERVICE_TYPE_TBD = "TBD", SERVICE_TYPE_PZ2 = "PZ2", SERVICE_TYPE_SP = "SP"; @@ -50,9 +80,8 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se private List pazpar2Urls = new ArrayList(); public static final String PAZPAR2_URL_LIST = "PAZPAR2_URL_LIST"; - private static final long serialVersionUID = 3440277287081557861L; - private static Logger logger = Logger.getLogger(Pz2Service.class); + private static Logger logger = Logger.getLogger(Pz2Service.class); protected Pz2Client pz2Client = null; protected ServiceProxyClient spClient = null; protected SearchClient searchClient = null; @@ -76,9 +105,9 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se FacesContext context = FacesContext.getCurrentInstance(); return (Pz2Service) context.getApplication().evaluateExpressionGet(context, "#{pz2}", Object.class); } - + @PostConstruct - public void postConstruct() { + public void postConstruct() throws MissingConfigurationContextException { logger.info("Pz2Service PostConstruct of " + this); stateMgr = new StateManager(); pzreq = new Pazpar2Commands(); @@ -87,18 +116,22 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se pzresp.setErrorHelper(errors.getHelper()); logger.debug("Pz2Service PostConstruct: Configurator is " + configurator); - logger.debug(Utils.objectId(this) + " will instantiate a Pz2Client next."); + logger.debug(Utils.objectId(this) + " will instantiate a Pz2Client next."); pz2Client = new Pz2Client(); - configureClient(pz2Client,configurator); spClient = new ServiceProxyClient(); - configureClient(spClient,configurator); + stateMgr.addStateListener(this); try { + configureClient(pz2Client,configurator); + configureClient(spClient,configurator); this.configure(configurator); + } catch (MissingConfigurationContextException mcc) { + logger.info("No configuration context available at this point"); + logger.debug("Configuration invoked from a Servlet filter before application start?"); + throw mcc; } catch (ConfigurationException e) { - logger.error("There was a problem configuring the Pz2Service (\"pz2\")"); + logger.warn("There was a problem configuring the Pz2Service and/or clients (\"pz2\")"); e.printStackTrace(); - } - stateMgr.addStateListener(this); + } } @Qualifier @@ -129,14 +162,27 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se return stateMgr; } - public void configureClient(SearchClient client, ConfigurationReader configReader) { + /** + * Configures the selected search client using the selected configuration reader. + * + * The configuration reader is select deploy-time - by configuration in the application's beans.xml. + * + * @param client search client to use + * @param configReader the selected configuration mechanism + * @throws MissingConfigurationContextException if this object is injected before there is a Faces context + * for example in a Servlet filter. + */ + public void configureClient(SearchClient client, ConfigurationReader configReader) throws MissingConfigurationContextException { logger.debug(Utils.objectId(this) + " will configure search client for the session"); try { - client.configure(configReader); + client.configure(configReader); + } catch (MissingConfigurationContextException mcce) { + logger.info("No Faces context is available to the configurator at this time of invocation"); + throw mcce; } catch (ConfigurationException e) { logger.debug("Pz2Service adding configuration error"); errors.addConfigurationError(new ConfigurationError("Search Client","Configuration",e.getMessage())); - } + } logger.info(configReader.document()); pzresp.getSp().resetAuthAndBeyond(true); } @@ -145,12 +191,43 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se pzreq.getRecord().removeParametersInState(); pzreq.getSearch().removeParametersInState(); } - + + /** - * Refreshes 'show', 'stat', 'termlist', and 'bytarget' data object from pazpar2 + * Updates display data objects by simultaneously issuing the following Pazpar2 commands: + * 'show', 'stat', 'termlist' and 'bytarget'. + *

+ * If there are outstanding changes to the search command, a search + * will be issued before the updates are performed. Outstanding changes could come + * from the UI changing a search parameter and not executing search before starting + * the update cycle - OR - it could come from the user clicking the browsers back/forward + * buttons. + *

+ *

+ * This method is invoked from the composite 'pz2watch', which uses Ajax + * to keep invoking this method until it returns '0' (for zero active clients). + *

+ *

+ * UI components that display data from show, stat, termlist or bytarget, + * should be re-rendered after each update. + *

+ * Example of invocation in UI: + *
+   *    <pz2utils:pz2watch id="pz2watch"
+   *       renderWhileActiveclients="myshowui mystatui mytermsui" /< 
+   *       
+   *    <h:form>
+   *     <h:inputText id="query" value="#{pzreq.search.query}" size="50"/>                            
+   *      <h:commandButton id="button" value="Search">              
+   *       <f:ajax execute="query" render="${pz2.watchActiveclients}"/>
+   *      </h:commandButton>
+   *     </h:form>
+   * 
+ * The expression pz2.watchActiveClients will invoke the method repeatedly, and the + * UI sections myshowui, mystatui, and mytermsui will be rendered on each poll. * - * @return Number of activeclients at the time of the 'show' command. - */ + * @return a count of the remaining active clients from the most recent search. + */ public String update () { logger.debug("Updating show,stat,termlist,bytarget from pazpar2"); if (errors.hasConfigurationErrors()) { @@ -168,9 +245,11 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se } /** - * Refreshes the data objects listed in 'commands' from pazpar2 + * Simultaneously refreshes the data objects listed in 'commands' from pazpar2, potentially running a + * search or a record command first if any of these two commands have outstanding parameter changes. + * + * @param commands, a comma-separated list of Pazpar2 commands to execute * - * @param commands * @return Number of activeclients at the time of the 'show' command, * or 'new' if search was just initiated. */ @@ -179,6 +258,7 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se try { if (commands.equals("search")) { pzreq.getSearch().run(); + pzresp.getSearch().setIsNew(false); return "new"; } else if (commands.equals("record")) { pzreq.getRecord().run(); @@ -228,7 +308,59 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se } } - + + /** + * This methods main purpose is to support browser history. + * + * When the browsers back or forward buttons are pressed, a + * re-search /might/ be required - namely if the query changes. + * So, as the UI requests updates of the page (show,facets, + * etc) this method checks if a search must be executed + * before those updates are performed. + * + * It will consequently also run a search if the UI updates a + * search parameter without actually explicitly executing the search + * before setting of the polling. + * + * @see {@link com.indexdata.mkjsf.pazpar2.state.StateManager#setCurrentStateKey} + * @param commands + */ + protected void handleQueryStateChanges (String commands) { + if (stateMgr.hasPendingStateChange("search") && hasQuery()) { + logger.info("Triggered search: Found pending search change [" + pzreq.getCommand("search").toString() + "], doing search before updating " + commands); + pzreq.getSearch().run(); + pzresp.getSearch().setIsNew(false); + } + if (stateMgr.hasPendingStateChange("record") && ! commands.equals("record")) { + logger.debug("Found pending record ID change. Doing record before updating " + commands); + stateMgr.hasPendingStateChange("record",false); + pzreq.getRecord().run(); + } + } + + /** + * Used by the state manager to notify Pz2Service about state changes + */ + @Override + public void stateUpdated(String commandName) { + logger.debug("State change reported for [" + commandName + "]"); + if (commandName.equals("show")) { + logger.debug("Updating show"); + update(commandName); + } + } + + + /** + * Will retrieve -- or alternatively remove -- the record with the given + * recid from memory. + * + * A pazpar2 'record' command will then be issued. The part of the UI + * showing record data should thus be re-rendered. + * + * @param recid + * @return + */ public String toggleRecord (String recId) { if (hasRecord(recId)) { pzreq.getRecord().removeParameters(); @@ -236,20 +368,39 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se return ""; } else { pzreq.getRecord().setId(recId); - doCommand("record"); + pzreq.getRecord().run(); return pzresp.getRecord().getActiveClients(); } } - @Override + /** + * Resolves whether the back-end has a record with the given recid in memory + * + * @return true if the bean currently holds the record with recid + */ public boolean hasRecord (String recId) { return pzreq.getCommand("record").hasParameters() && pzresp.getRecord().getRecId().equals(recId); } + /** + * Returns the current hash key, used for internal session state tracking + * and potentially for browser history entries + * + * A UI author would not normally be concerned with retrieving this. It's used by the + * framework internally + * + * @return string that can be used for browsers window.location.hash + */ public String getCurrentStateKey () { return stateMgr.getCurrentState().getKey(); } + /** + * Sets the current state key, i.e. when user clicks back or forward in browser history. + * Would normally be automatically handled by the frameworks components. + * + * @param key corresponding to browsers hash string + */ public void setCurrentStateKey(String key) { stateMgr.setCurrentStateKey(key); } @@ -258,8 +409,10 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se return pzreq.getCommand("search").hasParameterValue("query"); } - - @Override + /** + * Returns a component for drawing a pager to navigate by. + * @return ResultsPager pager component + */ public ResultsPager getPager () { if (pager == null) { pager = new ResultsPager(pzresp); @@ -267,73 +420,33 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se return pager; } - @Override + /** + * Initiates a pager object, a component holding the data to draw a sequence + * of page numbers to navigate by and mechanisms to navigate with + * + * @param pageRange number of pages to display in the pager + * @return ResultsPager the initiated pager component + */ public ResultsPager setPager (int pageRange) { pager = new ResultsPager(pzresp,pageRange,pzreq); return pager; } - - /** - * This methods main purpose is to support browser history. - * - * When the browsers back or forward buttons are pressed, a - * re-search /might/ be required - namely if the query changes. - * So, as the UI requests updates of the page (show,facets, - * etc) this method checks if a search must be executed - * before those updates are performed. - * - * @see {@link com.indexdata.mkjsf.pazpar2.state.StateManager#setCurrentStateKey} - * @param commands - */ - protected void handleQueryStateChanges (String commands) { - if (stateMgr.hasPendingStateChange("search") && hasQuery()) { - logger.info("Triggered search: Found pending search change [" + pzreq.getCommand("search").toString() + "], doing search before updating " + commands); - pzreq.getSearch().run(); - } - if (stateMgr.hasPendingStateChange("record") && ! commands.equals("record")) { - logger.debug("Found pending record ID change. Doing record before updating " + commands); - stateMgr.hasPendingStateChange("record",false); - pzreq.getRecord().run(); - } - } - + /** - * Executes the command and parses the response to create data objects. - * If the parsed response is of a known type it will be cached in 'pzresp' + * Sets the URL of the Service Proxy to use for requests * - * @param commandName The command to be executed - * @return An XML response parsed to form a response data object + * @param url */ - protected ResponseDataObject doCommand(String commandName) { - Pazpar2Command command = pzreq.getCommand(commandName); - if (command.spOnly() && isPazpar2Service()) { - logger.warn("Skipping " + commandName + " - SP-only command, not supported by Pazpar2"); - return new ResponseDataObject(); - } else { - logger.info("Request "+commandName + ". Search command is: "+ pzreq.getCommand("search").toString()); - long start = System.currentTimeMillis(); - ResponseDataObject responseObject = command.run(); - long end = System.currentTimeMillis(); - logger.debug("Executed " + command.getCommandName() + " in " + (end-start) + " ms." ); - return responseObject; - } - } - - @Override - public void stateUpdated(String commandName) { - logger.debug("State change reported for [" + commandName + "]"); - if (commandName.equals("show")) { - logger.debug("Updating show"); - update(commandName); - } - } - public void setServiceProxyUrl(String url) { searchClient = spClient; setServiceType(SERVICE_TYPE_SP); setServiceUrl(url); } + /** + * Returns the Service Proxy URL currently defined for servicing requests + * + */ public String getServiceProxyUrl () { if (isServiceProxyService()) { return spClient.getServiceUrl(); @@ -341,13 +454,22 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se return ""; } } - + + /** + * Sets the URL of the Pazpar2 to use for requests + * + * @param url + */ public void setPazpar2Url(String url) { searchClient = pz2Client; setServiceType(SERVICE_TYPE_PZ2); setServiceUrl(url); } + /** + * Returns the Pazpar2 URL currently defined for servicing requests + * + */ public String getPazpar2Url() { if (isPazpar2Service()) { return pz2Client.getServiceUrl(); @@ -356,8 +478,12 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se } } - - @Override + /** + * Sets the URL to be used by the currently selected search client + * when running requests. + * + * @param url + */ public void setServiceUrl(String url) { if (url!=null && searchClient != null && !url.equals(searchClient.getServiceUrl())) { pzreq.getRecord().removeParametersInState(); @@ -367,6 +493,10 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se } } + /** + * Gets the currently selected URL used for executing requests. + * @return + */ public String getServiceUrl() { return (searchClient!=null ? searchClient.getServiceUrl() : ""); } @@ -420,22 +550,18 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se return spClient; } - @Override public boolean getAuthenticationRequired () { return spClient.isAuthenticatingClient(); } - @Override public String getCheckHistory () { return ":pz2watch:stateForm:windowlocationhash"; } - @Override public String getWatchActiveclients () { return ":pz2watch:activeclientsForm:activeclientsField"; } - @Override public String getWatchActiveclientsRecord () { return ":pz2watch:activeclientsForm:activeclientsFieldRecord"; } @@ -479,19 +605,16 @@ public class Pz2Service implements Pz2Interface, StateListener, Configurable, Se return new ArrayList(); } - @Override public void setServiceTypePZ2() { - setServiceType(SERVICE_TYPE_PZ2); + setServiceType(SERVICE_TYPE_PZ2); } - @Override public void setServiceTypeSP() { - setServiceType(SERVICE_TYPE_SP); + setServiceType(SERVICE_TYPE_SP); } - @Override public void setServiceTypeTBD() { - setServiceType(SERVICE_TYPE_TBD); + setServiceType(SERVICE_TYPE_TBD); } private void setServiceType(String type) {