From 808bc6fc5d169f4996523c20a101a30e3e8c6a43 Mon Sep 17 00:00:00 2001 From: KevinVdV Date: Tue, 25 Sep 2012 15:58:23 +0200 Subject: [PATCH] [DS-1243] @mire solr statistics contribution --- .../java/org/dspace/search/DSIndexer.java | 7 +- .../main/java/org/dspace/search/DSQuery.java | 2 +- .../usage/LoggerUsageEventListener.java | 4 +- .../java/org/dspace/usage/UsageEvent.java | 22 +- .../org/dspace/usage/UsageSearchEvent.java | 86 + .../org/dspace/usage/UsageWorkflowEvent.java | 80 + .../org/dspace/workflow/WorkflowManager.java | 27 + .../xmlworkflow/XmlWorkflowManager.java | 177 +- .../dspace-discovery-solr/pom.xml | 6 +- .../DiscoverySearchLoggerAction.java | 39 + .../resources/aspects/Discovery/sitemap.xmap | 5 + .../static/js/discovery/discovery-results.js | 32 + .../webui/search/LuceneSearchProcessor.java | 1431 ++++----- .../servlet/DisplayStatisticsServlet.java | 676 ++--- .../webui/servlet/SearchResultLogServlet.java | 74 + .../src/main/webapp/WEB-INF/web.xml | 10 + .../src/main/webapp/search/results.jsp | 22 + .../static/js/jquery/jquery-1.6.2.min.js | 25 + .../main/webapp/static/js/search-results.js | 32 + dspace-stats/pom.xml | 9 +- .../java/org/dspace/statistics/Dataset.java | 45 +- .../org/dspace/statistics/SolrLogger.java | 2609 +++++++++-------- .../SolrLoggerUsageEventListener.java | 22 +- .../content/DatasetSearchGenerator.java | 56 + .../content/StatisticsDataSearches.java | 238 ++ .../content/StatisticsDataVisits.java | 15 +- .../content/StatisticsDataWorkflow.java | 202 ++ .../statistics/util/StatisticsClient.java | 315 +- .../artifactbrowser/AdvancedSearch.java | 196 +- .../AdvancedSearchLoggerAction.java | 44 + .../artifactbrowser/AdvancedSearchUtils.java | 233 ++ .../SimpleSearchLoggerAction.java | 36 + .../AbstractStatisticsDataTransformer.java | 155 + .../xmlui/aspect/statistics/Navigation.java | 42 +- .../statistics/SearchResultLogAction.java | 80 + .../StatisticsAuthorizedMatcher.java | 206 +- .../StatisticsSearchResultTransformer.java | 114 + .../StatisticsSearchTransformer.java | 136 + .../statistics/StatisticsTransformer.java | 12 +- .../StatisticsWorkflowTransformer.java | 126 + .../app/xmlui/cocoon/SearchLoggerAction.java | 104 + .../aspects/SearchArtifacts/sitemap.xmap | 34 +- .../resources/aspects/Statistics/sitemap.xmap | 370 ++- .../src/main/webapp/i18n/messages.xml | 53 +- .../main/webapp/static/js/search-results.js | 32 + .../main/webapp/static/js/usage-statistics.js | 20 + .../webapp/themes/Mirage/lib/css/style.css | 14 + dspace/config/modules/usage-statistics.cfg | 7 +- dspace/modules/solr/pom.xml | 38 +- dspace/solr/search/conf/solrconfig.xml | 435 ++- dspace/solr/statistics/conf/schema.xml | 33 +- pom.xml | 2 +- 52 files changed, 5796 insertions(+), 2994 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/usage/UsageSearchEvent.java create mode 100644 dspace-api/src/main/java/org/dspace/usage/UsageWorkflowEvent.java create mode 100644 dspace-discovery/dspace-discovery-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/discovery/DiscoverySearchLoggerAction.java create mode 100644 dspace-discovery/dspace-discovery-xmlui-webapp/src/main/webapp/static/js/discovery/discovery-results.js create mode 100644 dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/SearchResultLogServlet.java create mode 100644 dspace-jspui/dspace-jspui-webapp/src/main/webapp/static/js/jquery/jquery-1.6.2.min.js create mode 100644 dspace-jspui/dspace-jspui-webapp/src/main/webapp/static/js/search-results.js create mode 100644 dspace-stats/src/main/java/org/dspace/statistics/content/DatasetSearchGenerator.java create mode 100644 dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataSearches.java create mode 100644 dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataWorkflow.java create mode 100644 dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearchLoggerAction.java create mode 100644 dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearchUtils.java create mode 100644 dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/SimpleSearchLoggerAction.java create mode 100644 dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/AbstractStatisticsDataTransformer.java create mode 100644 dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/SearchResultLogAction.java create mode 100644 dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsSearchResultTransformer.java create mode 100644 dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsSearchTransformer.java create mode 100644 dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsWorkflowTransformer.java create mode 100644 dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/cocoon/SearchLoggerAction.java create mode 100644 dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/static/js/search-results.js create mode 100644 dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/static/js/usage-statistics.js diff --git a/dspace-api/src/main/java/org/dspace/search/DSIndexer.java b/dspace-api/src/main/java/org/dspace/search/DSIndexer.java index d104e95505..2341af5311 100644 --- a/dspace-api/src/main/java/org/dspace/search/DSIndexer.java +++ b/dspace-api/src/main/java/org/dspace/search/DSIndexer.java @@ -103,7 +103,8 @@ public class DSIndexer private static int batchFlushAfterDocuments = ConfigurationManager.getIntProperty("search.batch.documents", 20); private static boolean batchProcessingMode = false; - + static final Version luceneVersion = Version.LUCENE_35; + // Class to hold the index configuration (one instance per config line) private static class IndexConfig { @@ -637,7 +638,7 @@ public class DSIndexer Class analyzerClass = Class.forName(analyzerClassName); Constructor constructor = analyzerClass.getDeclaredConstructor(Version.class); constructor.setAccessible(true); - analyzer = (Analyzer) constructor.newInstance(Version.LUCENE_33); + analyzer = (Analyzer) constructor.newInstance(luceneVersion); } catch (Exception e) { @@ -914,7 +915,7 @@ public class DSIndexer throws IOException { Directory dir = FSDirectory.open(new File(indexDirectory)); - IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_33, getAnalyzer()); + IndexWriterConfig iwc = new IndexWriterConfig(luceneVersion, getAnalyzer()); if(wipeExisting){ iwc.setOpenMode(IndexWriterConfig.OpenMode.CREATE); }else{ diff --git a/dspace-api/src/main/java/org/dspace/search/DSQuery.java b/dspace-api/src/main/java/org/dspace/search/DSQuery.java index 7bc6f8b27d..ae4831f9ed 100644 --- a/dspace-api/src/main/java/org/dspace/search/DSQuery.java +++ b/dspace-api/src/main/java/org/dspace/search/DSQuery.java @@ -122,7 +122,7 @@ public class DSQuery // grab a searcher, and do the search IndexSearcher searcher = getSearcher(c); - QueryParser qp = new QueryParser(Version.LUCENE_33, "default", DSIndexer.getAnalyzer()); + QueryParser qp = new QueryParser(DSIndexer.luceneVersion, "default", DSIndexer.getAnalyzer()); log.debug("Final query string: " + querystring); if (operator == null || operator.equals("OR")) diff --git a/dspace-api/src/main/java/org/dspace/usage/LoggerUsageEventListener.java b/dspace-api/src/main/java/org/dspace/usage/LoggerUsageEventListener.java index 09f36774e5..575a2e428c 100644 --- a/dspace-api/src/main/java/org/dspace/usage/LoggerUsageEventListener.java +++ b/dspace-api/src/main/java/org/dspace/usage/LoggerUsageEventListener.java @@ -28,7 +28,9 @@ public class LoggerUsageEventListener extends AbstractUsageEventListener{ public void receiveEvent(Event event) { - if(event instanceof UsageEvent) + //Search events are already logged + //UsageSearchEvent is already logged in the search classes, no need to repeat this logging + if(event instanceof UsageEvent && !(event instanceof UsageSearchEvent)) { UsageEvent ue = (UsageEvent)event; diff --git a/dspace-api/src/main/java/org/dspace/usage/UsageEvent.java b/dspace-api/src/main/java/org/dspace/usage/UsageEvent.java index f6710bdcde..154fbdcad8 100644 --- a/dspace-api/src/main/java/org/dspace/usage/UsageEvent.java +++ b/dspace-api/src/main/java/org/dspace/usage/UsageEvent.java @@ -30,6 +30,7 @@ public class UsageEvent extends Event { REMOVE ("remove"), BROWSE ("browse"), SEARCH ("search"), + WORKFLOW ("workflow"), LOGIN ("login"), SUBSCRIBE ("subscribe"), UNSUBSCRIBE ("unsubscribe"), @@ -59,12 +60,13 @@ public class UsageEvent extends Event { private static String checkParams(Action action, HttpServletRequest request, Context context, DSpaceObject object) { + StringBuilder eventName = new StringBuilder(); if(action == null) { throw new IllegalStateException("action cannot be null"); } - if(request == null) + if(action != Action.WORKFLOW && request == null) { throw new IllegalStateException("request cannot be null"); } @@ -75,21 +77,17 @@ public class UsageEvent extends Event { throw new IllegalStateException("context cannot be null"); } - if(object == null) + if(action != Action.WORKFLOW && action != Action.SEARCH && object == null) { throw new IllegalStateException("object cannot be null"); + }else + if(object != null){ + String objText = Constants.typeText[object.getType()].toLowerCase(); + eventName.append(objText).append(":"); } - - try - { - String objText = Constants.typeText[object.getType()].toLowerCase(); - return objText + ":" + action.text(); - }catch(Exception e) - { + eventName.append(action.text()); - } - return ""; - + return eventName.toString(); } public UsageEvent(Action action, HttpServletRequest request, Context context, DSpaceObject object) diff --git a/dspace-api/src/main/java/org/dspace/usage/UsageSearchEvent.java b/dspace-api/src/main/java/org/dspace/usage/UsageSearchEvent.java new file mode 100644 index 0000000000..30b99d72aa --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/usage/UsageSearchEvent.java @@ -0,0 +1,86 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.usage; + +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * Extends the standard usage event to contain search information + * search information includes the query(s) used & the scope + * + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class UsageSearchEvent extends UsageEvent{ + + private List queries; + private DSpaceObject scope; + + /** Optional search parameters **/ + private int rpp; + private String sortBy; + private String sortOrder; + private int page; + + + public UsageSearchEvent(Action action, HttpServletRequest request, Context context, DSpaceObject object, List queries, DSpaceObject scope) { + super(action, request, context, object); + + this.queries = queries; + this.scope = scope; + this.rpp = -1; + this.sortBy = null; + this.sortOrder = null; + this.page = -1; + } + + public List getQueries() { + return queries; + } + + public DSpaceObject getScope() { + return scope; + } + + public int getRpp() { + return rpp; + } + + public void setRpp(int rpp) { + this.rpp = rpp; + } + + public String getSortBy() { + return sortBy; + } + + public void setSortBy(String sortBy) { + this.sortBy = sortBy; + } + + public String getSortOrder() { + return sortOrder; + } + + public void setSortOrder(String sortOrder) { + this.sortOrder = sortOrder; + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } +} diff --git a/dspace-api/src/main/java/org/dspace/usage/UsageWorkflowEvent.java b/dspace-api/src/main/java/org/dspace/usage/UsageWorkflowEvent.java new file mode 100644 index 0000000000..2d9f2bbcc7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/usage/UsageWorkflowEvent.java @@ -0,0 +1,80 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.usage; + +import org.dspace.content.Collection; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; + +/** + * Extends the standard usage event to contain workflow information + * + * + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class UsageWorkflowEvent extends UsageEvent { + + private String workflowStep; + private String oldState; + private EPerson[] epersonOwners; + private Group[] groupOwners; + private Collection scope; + private EPerson actor; + private InProgressSubmission workflowItem; + + public UsageWorkflowEvent(Context context, Item item, InProgressSubmission workflowItem, String workflowStep, String oldState, Collection scope, EPerson actor) { + super(Action.WORKFLOW, null, context, item); + this.workflowItem = workflowItem; + this.workflowStep = workflowStep; + this.oldState = oldState; + this.scope = scope; + this.actor = actor; + } + + public String getWorkflowStep() { + return workflowStep; + } + + + public String getOldState() { + return oldState; + } + + public Collection getScope() { + return scope; + } + + public EPerson[] getEpersonOwners() { + return epersonOwners; + } + + public void setEpersonOwners(EPerson... epersonOwners) { + this.epersonOwners = epersonOwners; + } + + public Group[] getGroupOwners() { + return groupOwners; + } + + public void setGroupOwners(Group... newGroupOwner) { + this.groupOwners = newGroupOwner; + } + + public EPerson getActor() { + return actor; + } + + public InProgressSubmission getWorkflowItem() { + return workflowItem; + } +} diff --git a/dspace-api/src/main/java/org/dspace/workflow/WorkflowManager.java b/dspace-api/src/main/java/org/dspace/workflow/WorkflowManager.java index 15a863fb6b..350dacf80a 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/WorkflowManager.java +++ b/dspace-api/src/main/java/org/dspace/workflow/WorkflowManager.java @@ -41,6 +41,8 @@ import org.dspace.handle.HandleManager; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; +import org.dspace.usage.UsageWorkflowEvent; +import org.dspace.utils.DSpace; /** * Workflow state machine @@ -518,6 +520,9 @@ public class WorkflowManager Group mygroup = null; boolean archived = false; + //Gather our old data for launching the workflow event + int oldState = wi.getState(); + wi.setState(newstate); switch (newstate) @@ -657,6 +662,8 @@ public class WorkflowManager break; } + logWorkflowEvent(c, wi.getItem(), wi, c.getCurrentUser(), newstate, newowner, mycollection, oldState, mygroup); + if (!archived) { wi.update(); @@ -665,6 +672,22 @@ public class WorkflowManager return archived; } + private static void logWorkflowEvent(Context c, Item item, WorkflowItem workflowItem, EPerson actor, int newstate, EPerson newOwner, Collection mycollection, int oldState, Group newOwnerGroup) { + if(newstate == WFSTATE_ARCHIVE || newstate == WFSTATE_STEP1POOL || newstate == WFSTATE_STEP2POOL || newstate == WFSTATE_STEP3POOL){ + //Clear the newowner variable since this one isn't owned anymore ! + newOwner = null; + } + + UsageWorkflowEvent usageWorkflowEvent = new UsageWorkflowEvent(c, item, workflowItem, workflowText[newstate], workflowText[oldState], mycollection, actor); + if(newOwner != null){ + usageWorkflowEvent.setEpersonOwners(newOwner); + } + if(newOwnerGroup != null){ + usageWorkflowEvent.setGroupOwners(newOwnerGroup); + } + new DSpace().getEventService().fireEvent(usageWorkflowEvent); + } + /** * Get the text representing the given workflow state * @@ -816,6 +839,8 @@ public class WorkflowManager String rejection_message) throws SQLException, AuthorizeException, IOException { + + int oldState = wi.getState(); // authorize a DSpaceActions.REJECT // stop workflow deleteTasks(c, wi); @@ -848,6 +873,8 @@ public class WorkflowManager + "collection_id=" + wi.getCollection().getID() + "eperson_id=" + e.getID())); + logWorkflowEvent(c, wsi.getItem(), wi, e, WFSTATE_SUBMIT, null, wsi.getCollection(), oldState, null); + return wsi; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowManager.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowManager.java index 0548f47c69..ad42112961 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowManager.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowManager.java @@ -19,6 +19,8 @@ import org.dspace.eperson.Group; import org.dspace.handle.HandleManager; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; +import org.dspace.usage.UsageWorkflowEvent; +import org.dspace.utils.DSpace; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.Workflow; import org.dspace.xmlworkflow.state.actions.*; @@ -144,6 +146,9 @@ public class XmlWorkflowManager { // record the start of the workflow w/provenance message recordStart(wfi.getItem(), firstActionConfig.getProcessingAction()); + //Fire an event ! + logWorkflowEvent(context, firstStep.getWorkflow().getID(), null, null, wfi, null, firstStep, firstActionConfig); + //If we don't have a UI activate it if(!firstActionConfig.requiresUI()){ ActionResult outcome = firstActionConfig.getProcessingAction().execute(context, wfi, firstStep, null); @@ -185,55 +190,81 @@ public class XmlWorkflowManager { return null; }else if (currentOutcome.getType() == ActionResult.TYPE.TYPE_OUTCOME) { - //We have completed our action search & retrieve the next action + Step nextStep = null; WorkflowActionConfig nextActionConfig = null; - if(currentOutcome.getResult() == ActionResult.OUTCOME_COMPLETE){ - nextActionConfig = currentStep.getNextAction(currentActionConfig); - } - - if (nextActionConfig != null) { - nextActionConfig.getProcessingAction().activate(c, wfi); - if (nextActionConfig.requiresUI() && !enteredNewStep) { - createOwnedTask(c, wfi, currentStep, nextActionConfig, user); - return nextActionConfig; - } else if( nextActionConfig.requiresUI() && enteredNewStep){ - //We have entered a new step and have encountered a UI, return null since the current user doesn't have anything to do with this - c.restoreAuthSystemState(); - return null; - } else { - ActionResult newOutcome = nextActionConfig.getProcessingAction().execute(c, wfi, currentStep, null); - return processOutcome(c, user, workflow, currentStep, nextActionConfig, newOutcome, wfi, enteredNewStep); + try { + //We have completed our action search & retrieve the next action + if(currentOutcome.getResult() == ActionResult.OUTCOME_COMPLETE){ + nextActionConfig = currentStep.getNextAction(currentActionConfig); } - }else - if(enteredNewStep){ - // If the user finished his/her step, we keep processing until there is a UI step action or no step at all - Step nextStep = workflow.getNextStep(c, wfi, currentStep, currentOutcome.getResult()); - c.turnOffAuthorisationSystem(); - return processNextStep(c, user, workflow, currentOutcome, wfi, nextStep); - } else { - // - ClaimedTask task = ClaimedTask.findByWorkflowIdAndEPerson(c, wfi.getID(), user.getID()); - //Check if we have a task for this action (might not be the case with automatic steps) - //First add it to our list of finished users, since no more actions remain - WorkflowRequirementsManager.addFinishedUser(c, wfi, user); - c.turnOffAuthorisationSystem(); - //Check if our requirements have been met - if((currentStep.isFinished(c, wfi) && currentOutcome.getResult() == ActionResult.OUTCOME_COMPLETE) || currentOutcome.getResult() != ActionResult.OUTCOME_COMPLETE){ - //Delete all the table rows containing the users who performed this task - WorkflowRequirementsManager.clearInProgressUsers(c, wfi); - //Remove all the tasks - XmlWorkflowManager.deleteAllTasks(c, wfi); + if (nextActionConfig != null) { + //We remain in the current step since an action is found + nextStep = currentStep; + nextActionConfig.getProcessingAction().activate(c, wfi); + if (nextActionConfig.requiresUI() && !enteredNewStep) { + createOwnedTask(c, wfi, currentStep, nextActionConfig, user); + return nextActionConfig; + } else if( nextActionConfig.requiresUI() && enteredNewStep){ + //We have entered a new step and have encountered a UI, return null since the current user doesn't have anything to do with this + c.restoreAuthSystemState(); + return null; + } else { + ActionResult newOutcome = nextActionConfig.getProcessingAction().execute(c, wfi, currentStep, null); + return processOutcome(c, user, workflow, currentStep, nextActionConfig, newOutcome, wfi, enteredNewStep); + } + }else + if(enteredNewStep){ + // If the user finished his/her step, we keep processing until there is a UI step action or no step at all + nextStep = workflow.getNextStep(c, wfi, currentStep, currentOutcome.getResult()); + c.turnOffAuthorisationSystem(); + nextActionConfig = processNextStep(c, user, workflow, currentOutcome, wfi, nextStep); + //If we require a user interface return null so that the user is redirected to the "submissions page" + if(nextActionConfig == null || nextActionConfig.requiresUI()){ + return null; + }else{ + return nextActionConfig; + } + } else { + ClaimedTask task = ClaimedTask.findByWorkflowIdAndEPerson(c, wfi.getID(), user.getID()); + + //Check if we have a task for this action (might not be the case with automatic steps) + //First add it to our list of finished users, since no more actions remain + WorkflowRequirementsManager.addFinishedUser(c, wfi, user); + c.turnOffAuthorisationSystem(); + //Check if our requirements have been met + if((currentStep.isFinished(c, wfi) && currentOutcome.getResult() == ActionResult.OUTCOME_COMPLETE) || currentOutcome.getResult() != ActionResult.OUTCOME_COMPLETE){ + //Delete all the table rows containing the users who performed this task + WorkflowRequirementsManager.clearInProgressUsers(c, wfi); + //Remove all the tasks + XmlWorkflowManager.deleteAllTasks(c, wfi); - Step nextStep = workflow.getNextStep(c, wfi, currentStep, currentOutcome.getResult()); + nextStep = workflow.getNextStep(c, wfi, currentStep, currentOutcome.getResult()); - return processNextStep(c, user, workflow, currentOutcome, wfi, nextStep); - }else{ - //We are done with our actions so go to the submissions page but remove action ClaimedAction first - deleteClaimedTask(c, wfi, task); - c.restoreAuthSystemState(); - return null; + nextActionConfig = processNextStep(c, user, workflow, currentOutcome, wfi, nextStep); + //If we require a user interface return null so that the user is redirected to the "submissions page" + if(nextActionConfig == null || nextActionConfig.requiresUI()){ + return null; + }else{ + return nextActionConfig; + } + }else{ + //We are done with our actions so go to the submissions page but remove action ClaimedAction first + deleteClaimedTask(c, wfi, task); + c.restoreAuthSystemState(); + nextStep = currentStep; + nextActionConfig = currentActionConfig; + return null; + } + } + }catch (Exception e){ + log.error("error while processing workflow outcome", e); + e.printStackTrace(); + } + finally { + if((nextStep != null && nextActionConfig != null) || wfi.getItem().isArchived()){ + logWorkflowEvent(c, currentStep.getWorkflow().getID(), currentStep.getId(), currentActionConfig.getId(), wfi, user, nextStep, nextActionConfig); } } @@ -243,6 +274,51 @@ public class XmlWorkflowManager { throw new WorkflowException("Invalid step outcome"); } + protected static void logWorkflowEvent(Context c, String workflowId, String previousStepId, String previousActionConfigId, XmlWorkflowItem wfi, EPerson actor, Step newStep, WorkflowActionConfig newActionConfig) throws SQLException { + try { + //Fire an event so we can log our action ! + Item item = wfi.getItem(); + Collection myCollection = wfi.getCollection(); + String workflowStepString = null; + + List currentEpersonOwners = new ArrayList(); + List currentGroupOwners = new ArrayList(); + //These are only null if our item is sent back to the submission + if(newStep != null && newActionConfig != null){ + workflowStepString = workflowId + "." + newStep.getId() + "." + newActionConfig.getId(); + + //Retrieve the current owners of the task + List claimedTasks = ClaimedTask.find(c, wfi.getID(), newStep.getId()); + List pooledTasks = PoolTask.find(c, wfi); + for (PoolTask poolTask : pooledTasks){ + if(poolTask.getEpersonID() != -1){ + currentEpersonOwners.add(EPerson.find(c, poolTask.getEpersonID())); + }else{ + currentGroupOwners.add(Group.find(c, poolTask.getGroupID())); + } + } + for (ClaimedTask claimedTask : claimedTasks) { + currentEpersonOwners.add(EPerson.find(c, claimedTask.getOwnerID())); + } + } + String previousWorkflowStepString = null; + if(previousStepId != null && previousActionConfigId != null){ + previousWorkflowStepString = workflowId + "." + previousStepId + "." + previousActionConfigId; + } + + //Fire our usage event ! + UsageWorkflowEvent usageWorkflowEvent = new UsageWorkflowEvent(c, item, wfi, workflowStepString, previousWorkflowStepString, myCollection, actor); + + usageWorkflowEvent.setEpersonOwners(currentEpersonOwners.toArray(new EPerson[currentEpersonOwners.size()])); + usageWorkflowEvent.setGroupOwners(currentGroupOwners.toArray(new Group[currentGroupOwners.size()])); + + new DSpace().getEventService().fireEvent(usageWorkflowEvent); + } catch (Exception e) { + //Catch all errors we do not want our workflow to crash because the logging threw an exception + log.error(LogManager.getHeader(c, "Error while logging workflow event", "Workflow Item: " + wfi.getID()), e); + } + } + private static WorkflowActionConfig processNextStep(Context c, EPerson user, Workflow workflow, ActionResult currentOutcome, XmlWorkflowItem wfi, Step nextStep) throws SQLException, IOException, AuthorizeException, WorkflowException, WorkflowConfigurationException { WorkflowActionConfig nextActionConfig; if(nextStep!=null){ @@ -253,7 +329,7 @@ public class XmlWorkflowManager { if (nextActionConfig.requiresUI()) { //Since a new step has been started, stop executing actions once one with a user interface is present. c.restoreAuthSystemState(); - return null; + return nextActionConfig; } else { ActionResult newOutcome = nextActionConfig.getProcessingAction().execute(c, wfi, nextStep, null); c.restoreAuthSystemState(); @@ -581,6 +657,18 @@ public class XmlWorkflowManager { String rejection_message) throws SQLException, AuthorizeException, IOException { + + String workflowID = null; + String currentStepId = null; + String currentActionConfigId = null; + ClaimedTask claimedTask = ClaimedTask.findByWorkflowIdAndEPerson(c, wi.getID(), e.getID()); + if(claimedTask != null){ + //Log it + workflowID = claimedTask.getWorkflowID(); + currentStepId = claimedTask.getStepID(); + currentActionConfigId = claimedTask.getActionID(); + } + // authorize a DSpaceActions.REJECT // stop workflow deleteAllTasks(c, wi); @@ -627,6 +715,7 @@ public class XmlWorkflowManager { + "collection_id=" + wi.getCollection().getID() + "eperson_id=" + e.getID())); + logWorkflowEvent(c, workflowID, currentStepId, currentActionConfigId, wi, e, null, null); c.restoreAuthSystemState(); return wsi; diff --git a/dspace-discovery/dspace-discovery-solr/pom.xml b/dspace-discovery/dspace-discovery-solr/pom.xml index 279f22eb85..53348b6a9c 100644 --- a/dspace-discovery/dspace-discovery-solr/pom.xml +++ b/dspace-discovery/dspace-discovery-solr/pom.xml @@ -29,12 +29,16 @@ org.apache.solr solr-solrj - 3.3.0 + 3.5.0 org.slf4j slf4j-api + + org.slf4j + jcl-over-slf4j + diff --git a/dspace-discovery/dspace-discovery-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/discovery/DiscoverySearchLoggerAction.java b/dspace-discovery/dspace-discovery-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/discovery/DiscoverySearchLoggerAction.java new file mode 100644 index 0000000000..25a6ac11ad --- /dev/null +++ b/dspace-discovery/dspace-discovery-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/discovery/DiscoverySearchLoggerAction.java @@ -0,0 +1,39 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.xmlui.aspect.discovery; + +import org.apache.cocoon.environment.Request; +import org.dspace.app.xmlui.cocoon.SearchLoggerAction; +import org.dspace.app.xmlui.utils.ContextUtil; +import org.dspace.core.Context; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class DiscoverySearchLoggerAction extends SearchLoggerAction { + + @Override + protected List getQueries(Request request) throws SQLException { + Context context = ContextUtil.obtainContext(request); + List queries = new ArrayList(); + if(request.getParameter("query") != null){ + queries.add(request.getParameter("query")); + } + + queries.addAll(Arrays.asList(DiscoveryUIUtils.getFilterQueries(request, context))); + + return queries; + } +} diff --git a/dspace-discovery/dspace-discovery-xmlui-api/src/main/resources/aspects/Discovery/sitemap.xmap b/dspace-discovery/dspace-discovery-xmlui-api/src/main/resources/aspects/Discovery/sitemap.xmap index 0b85acdfab..d44220d847 100644 --- a/dspace-discovery/dspace-discovery-xmlui-api/src/main/resources/aspects/Discovery/sitemap.xmap +++ b/dspace-discovery/dspace-discovery-xmlui-api/src/main/resources/aspects/Discovery/sitemap.xmap @@ -44,6 +44,9 @@ and searching the repository. + + + @@ -97,6 +100,7 @@ and searching the repository. + @@ -133,6 +137,7 @@ and searching the repository. + diff --git a/dspace-discovery/dspace-discovery-xmlui-webapp/src/main/webapp/static/js/discovery/discovery-results.js b/dspace-discovery/dspace-discovery-xmlui-webapp/src/main/webapp/static/js/discovery/discovery-results.js new file mode 100644 index 0000000000..f47584ebe0 --- /dev/null +++ b/dspace-discovery/dspace-discovery-xmlui-webapp/src/main/webapp/static/js/discovery/discovery-results.js @@ -0,0 +1,32 @@ +/* + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +(function ($) { + + /** + * Function ensures that all the links clicked in our results pass through the internal logging mechanism + */ + $(document).ready(function() { + //Retrieve all links with handles attached (comm/coll/item links) + var urls = $('div#aspect_discovery_SimpleSearch_div_search-results').find('a'); + + urls.click(function(){ + var $this = $(this); + //Instead of redirecting us to the page, first send us to the statistics logger + //By doing this we ensure that we register the query to the result + var form = $('form#aspect_discovery_SimpleSearch_div_main-form'); + form.attr('action', form.attr('action').replace('/discover', '') + '/dso-display'); + //Manipulate the fq boxes to all switch to query since the logging doesn't take into account filter queries + form.find('input[name="fq"]').attr('name', 'query'); + form.find('input[name="redirectUrl"]').val($this.attr('href')); + form.submit(); + return false; + }); + + }); + +})(jQuery); diff --git a/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/search/LuceneSearchProcessor.java b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/search/LuceneSearchProcessor.java index 20e5ba9732..57143ee9b8 100644 --- a/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/search/LuceneSearchProcessor.java +++ b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/search/LuceneSearchProcessor.java @@ -1,697 +1,734 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.webui.search; - -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URLEncoder; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.ResourceBundle; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; - -import org.apache.log4j.Logger; -import org.dspace.app.bulkedit.DSpaceCSV; -import org.dspace.app.bulkedit.MetadataExport; -import org.dspace.app.util.OpenSearch; -import org.dspace.app.util.SyndicationFeed; -import org.dspace.app.util.Util; -import org.dspace.app.webui.servlet.SimpleSearchServlet; -import org.dspace.app.webui.util.JSPManager; -import org.dspace.app.webui.util.UIUtil; -import org.dspace.authorize.AuthorizeManager; -import org.dspace.content.Collection; -import org.dspace.content.Community; -import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; -import org.dspace.content.ItemIterator; -import org.dspace.core.Constants; -import org.dspace.core.Context; -import org.dspace.core.I18nUtil; -import org.dspace.core.LogManager; -import org.dspace.handle.HandleManager; -import org.dspace.search.DSQuery; -import org.dspace.search.QueryArgs; -import org.dspace.search.QueryResults; -import org.dspace.sort.SortOption; -import org.w3c.dom.Document; - -public class LuceneSearchProcessor implements SearchRequestProcessor -{ - /** log4j category */ - private static Logger log = Logger.getLogger(SimpleSearchServlet.class); - - // locale-sensitive metadata labels - private Map> localeLabels = null; - - private static String msgKey = "org.dspace.app.webui.servlet.FeedServlet"; - - public synchronized void init() - { - if (localeLabels == null) - { - localeLabels = new HashMap>(); - } - } - - /** - *

- * All metadata is search for the value contained in the "query" parameter. - * If the "location" parameter is present, the user's location is switched - * to that location using a redirect. Otherwise, the user's current location - * is used to constrain the query; i.e., if the user is "in" a collection, - * only results from the collection will be returned. - *

- * The value of the "location" parameter should be ALL (which means no - * location), a the ID of a community (e.g. "123"), or a community ID, then - * a slash, then a collection ID, e.g. "123/456". - * - * @author Robert Tansley - */ - @Override - public void doSimpleSearch(Context context, HttpServletRequest request, - HttpServletResponse response) throws SearchProcessorException, IOException, ServletException - { - try - { - // Get the query - String query = request.getParameter("query"); - int start = UIUtil.getIntParameter(request, "start"); - String advanced = request.getParameter("advanced"); - String fromAdvanced = request.getParameter("from_advanced"); - int sortBy = UIUtil.getIntParameter(request, "sort_by"); - String order = request.getParameter("order"); - int rpp = UIUtil.getIntParameter(request, "rpp"); - String advancedQuery = ""; - - // can't start earlier than 0 in the results! - if (start < 0) - { - start = 0; - } - - int collCount = 0; - int commCount = 0; - int itemCount = 0; - - Item[] resultsItems; - Collection[] resultsCollections; - Community[] resultsCommunities; - - QueryResults qResults = null; - QueryArgs qArgs = new QueryArgs(); - SortOption sortOption = null; - - if (request.getParameter("etal") != null) - { - qArgs.setEtAl(UIUtil.getIntParameter(request, "etal")); - } - - try - { - if (sortBy > 0) - { - sortOption = SortOption.getSortOption(sortBy); - qArgs.setSortOption(sortOption); - } - - if (SortOption.ASCENDING.equalsIgnoreCase(order)) - { - qArgs.setSortOrder(SortOption.ASCENDING); - } - else - { - qArgs.setSortOrder(SortOption.DESCENDING); - } - } - catch (Exception e) - { - } - - // Override the page setting if exporting metadata - if ("submit_export_metadata".equals(UIUtil.getSubmitButton(request, "submit"))) - { - qArgs.setPageSize(Integer.MAX_VALUE); - } - else if (rpp > 0) - { - qArgs.setPageSize(rpp); - } - - // if the "advanced" flag is set, build the query string from the - // multiple query fields - if (advanced != null) - { - query = qArgs.buildQuery(request); - advancedQuery = qArgs.buildHTTPQuery(request); - } - - // Ensure the query is non-null - if (query == null) - { - query = ""; - } - - // Get the location parameter, if any - String location = request.getParameter("location"); - - // If there is a location parameter, we should redirect to - // do the search with the correct location. - if ((location != null) && !location.equals("")) - { - String url = ""; - - if (!location.equals("/")) - { - // Location is a Handle - url = "/handle/" + location; - } - - // Encode the query - query = URLEncoder.encode(query, Constants.DEFAULT_ENCODING); - - if (advancedQuery.length() > 0) - { - query = query + "&from_advanced=true&" + advancedQuery; - } - - // Do the redirect - response.sendRedirect(response.encodeRedirectURL(request - .getContextPath() - + url + "/simple-search?query=" + query)); - - return; - } - - // Build log information - String logInfo = ""; - - // Get our location - Community community = UIUtil.getCommunityLocation(request); - Collection collection = UIUtil.getCollectionLocation(request); - - // get the start of the query results page - // List resultObjects = null; - qArgs.setQuery(query); - qArgs.setStart(start); - - // Perform the search - if (collection != null) - { - logInfo = "collection_id=" + collection.getID() + ","; - - // Values for drop-down box - request.setAttribute("community", community); - request.setAttribute("collection", collection); - - qResults = DSQuery.doQuery(context, qArgs, collection); - } - else if (community != null) - { - logInfo = "community_id=" + community.getID() + ","; - - request.setAttribute("community", community); - - // Get the collections within the community for the dropdown box - request - .setAttribute("collection.array", community - .getCollections()); - - qResults = DSQuery.doQuery(context, qArgs, community); - } - else - { - // Get all communities for dropdown box - Community[] communities = Community.findAll(context); - request.setAttribute("community.array", communities); - - qResults = DSQuery.doQuery(context, qArgs); - } - - // now instantiate the results and put them in their buckets - for (int i = 0; i < qResults.getHitTypes().size(); i++) - { - Integer myType = qResults.getHitTypes().get(i); - - // add the handle to the appropriate lists - switch (myType.intValue()) - { - case Constants.ITEM: - itemCount++; - break; - - case Constants.COLLECTION: - collCount++; - break; - - case Constants.COMMUNITY: - commCount++; - break; - } - } - - // Make objects from the handles - make arrays, fill them out - resultsCommunities = new Community[commCount]; - resultsCollections = new Collection[collCount]; - resultsItems = new Item[itemCount]; - - collCount = 0; - commCount = 0; - itemCount = 0; - - for (int i = 0; i < qResults.getHitTypes().size(); i++) - { - Integer myId = qResults.getHitIds().get(i); - String myHandle = qResults.getHitHandles().get(i); - Integer myType = qResults.getHitTypes().get(i); - - // add the handle to the appropriate lists - switch (myType.intValue()) - { - case Constants.ITEM: - if (myId != null) - { - resultsItems[itemCount] = Item.find(context, myId); - } - else - { - resultsItems[itemCount] = (Item)HandleManager.resolveToObject(context, myHandle); - } - - if (resultsItems[itemCount] == null) - { - throw new SQLException("Query \"" + query - + "\" returned unresolvable item"); - } - itemCount++; - break; - - case Constants.COLLECTION: - if (myId != null) - { - resultsCollections[collCount] = Collection.find(context, myId); - } - else - { - resultsCollections[collCount] = (Collection)HandleManager.resolveToObject(context, myHandle); - } - - if (resultsCollections[collCount] == null) - { - throw new SQLException("Query \"" + query - + "\" returned unresolvable collection"); - } - - collCount++; - break; - - case Constants.COMMUNITY: - if (myId != null) - { - resultsCommunities[commCount] = Community.find(context, myId); - } - else - { - resultsCommunities[commCount] = (Community)HandleManager.resolveToObject(context, myHandle); - } - - if (resultsCommunities[commCount] == null) - { - throw new SQLException("Query \"" + query - + "\" returned unresolvable community"); - } - - commCount++; - break; - } - } - - // Log - log.info(LogManager.getHeader(context, "search", logInfo + "query=\"" - + query + "\",results=(" + resultsCommunities.length + "," - + resultsCollections.length + "," + resultsItems.length + ")")); - - // Pass in some page qualities - // total number of pages - int pageTotal = 1 + ((qResults.getHitCount() - 1) / qResults - .getPageSize()); - - // current page being displayed - int pageCurrent = 1 + (qResults.getStart() / qResults.getPageSize()); - - // pageLast = min(pageCurrent+9,pageTotal) - int pageLast = ((pageCurrent + 9) > pageTotal) ? pageTotal - : (pageCurrent + 9); - - // pageFirst = max(1,pageCurrent-9) - int pageFirst = ((pageCurrent - 9) > 1) ? (pageCurrent - 9) : 1; - - // Pass the results to the display JSP - request.setAttribute("items", resultsItems); - request.setAttribute("communities", resultsCommunities); - request.setAttribute("collections", resultsCollections); - - request.setAttribute("pagetotal", Integer.valueOf(pageTotal)); - request.setAttribute("pagecurrent", Integer.valueOf(pageCurrent)); - request.setAttribute("pagelast", Integer.valueOf(pageLast)); - request.setAttribute("pagefirst", Integer.valueOf(pageFirst)); - - request.setAttribute("queryresults", qResults); - - // And the original query string - request.setAttribute("query", query); - - request.setAttribute("order", qArgs.getSortOrder()); - request.setAttribute("sortedBy", sortOption); - - if (AuthorizeManager.isAdmin(context)) - { - // Set a variable to create admin buttons - request.setAttribute("admin_button", Boolean.TRUE); - } - - if ((fromAdvanced != null) && (qResults.getHitCount() == 0)) - { - // send back to advanced form if no results - Community[] communities = Community.findAll(context); - request.setAttribute("communities", communities); - request.setAttribute("no_results", "yes"); - - Map queryHash = qArgs.buildQueryMap(request); - - if (queryHash != null) - { - for (Map.Entry entry : queryHash.entrySet()) - { - request.setAttribute(entry.getKey(), entry.getValue()); - } - } - - JSPManager.showJSP(request, response, "/search/advanced.jsp"); - } - else if ("submit_export_metadata".equals(UIUtil.getSubmitButton(request, "submit"))) - { - exportMetadata(context, response, resultsItems); - } - else - { - JSPManager.showJSP(request, response, "/search/results.jsp"); - } - } - catch (IllegalStateException e) - { - throw new SearchProcessorException(e.getMessage(), e); - } - catch (SQLException e) - { - throw new SearchProcessorException(e.getMessage(), e); - } - } - - /** - * Method for constructing the advanced search form - * - * @author gam - */ - @Override - public void doAdvancedSearch(Context context, HttpServletRequest request, - HttpServletResponse response) throws SearchProcessorException, ServletException, IOException - { - // just build a list of top-level communities and pass along to the jsp - Community[] communities; - try - { - communities = Community.findAllTop(context); - } - catch (SQLException e) - { - throw new SearchProcessorException(e.getMessage(), e); - } - - request.setAttribute("communities", communities); - - JSPManager.showJSP(request, response, "/search/advanced.jsp"); - } - - /** - * Method for producing OpenSearch-compliant search results, and the - * OpenSearch description document. - *

- * The value of the "scope" parameter should be absent (which means no scope - * restriction), or the handle of a community or collection, otherwise - * parameters exactly match those of the SearchServlet. - *

- * - * @author Richard Rodgers - */ - @Override - public void doOpenSearch(Context context, HttpServletRequest request, - HttpServletResponse response) throws SearchProcessorException, IOException, ServletException - { - init(); - - // dispense with simple service document requests - String scope = request.getParameter("scope"); - if (scope !=null && "".equals(scope)) - { - scope = null; - } - String path = request.getPathInfo(); - if (path != null && path.endsWith("description.xml")) - { - String svcDescrip = OpenSearch.getDescription(scope); - response.setContentType(OpenSearch.getContentType("opensearchdescription")); - response.setContentLength(svcDescrip.length()); - response.getWriter().write(svcDescrip); - return; - } - - // get enough request parameters to decide on action to take - String format = request.getParameter("format"); - if (format == null || "".equals(format)) - { - // default to atom - format = "atom"; - } - - // do some sanity checking - if (! OpenSearch.getFormats().contains(format)) - { - response.sendError(HttpServletResponse.SC_BAD_REQUEST); - return; - } - - // then the rest - we are processing the query - String query = request.getParameter("query"); - int start = Util.getIntParameter(request, "start"); - int rpp = Util.getIntParameter(request, "rpp"); - int sort = Util.getIntParameter(request, "sort_by"); - String order = request.getParameter("order"); - String sortOrder = (order == null || order.length() == 0 || order.toLowerCase().startsWith("asc")) ? - SortOption.ASCENDING : SortOption.DESCENDING; - - QueryArgs qArgs = new QueryArgs(); - // can't start earlier than 0 in the results! - if (start < 0) - { - start = 0; - } - qArgs.setStart(start); - - if (rpp > 0) - { - qArgs.setPageSize(rpp); - } - qArgs.setSortOrder(sortOrder); - - if (sort > 0) - { - try - { - qArgs.setSortOption(SortOption.getSortOption(sort)); - } - catch(Exception e) - { - // invalid sort id - do nothing - } - } - qArgs.setSortOrder(sortOrder); - - // Ensure the query is non-null - if (query == null) - { - query = ""; - } - - // If there is a scope parameter, attempt to dereference it - // failure will only result in its being ignored - DSpaceObject container; - try - { - container = (scope != null) ? HandleManager.resolveToObject(context, scope) : null; - } - catch (IllegalStateException e) - { - throw new SearchProcessorException(e.getMessage(), e); - } - catch (SQLException e) - { - throw new SearchProcessorException(e.getMessage(), e); - } - - // Build log information - String logInfo = ""; - - // get the start of the query results page - qArgs.setQuery(query); - - // Perform the search - QueryResults qResults = null; - if (container == null) - { - qResults = DSQuery.doQuery(context, qArgs); - } - else if (container instanceof Collection) - { - logInfo = "collection_id=" + container.getID() + ","; - qResults = DSQuery.doQuery(context, qArgs, (Collection)container); - } - else if (container instanceof Community) - { - logInfo = "community_id=" + container.getID() + ","; - qResults = DSQuery.doQuery(context, qArgs, (Community)container); - } - else - { - throw new IllegalStateException("Invalid container for search context"); - } - - // now instantiate the results - DSpaceObject[] results = new DSpaceObject[qResults.getHitHandles().size()]; - for (int i = 0; i < qResults.getHitHandles().size(); i++) - { - String myHandle = qResults.getHitHandles().get(i); - DSpaceObject dso; - try - { - dso = HandleManager.resolveToObject(context, myHandle); - } - catch (IllegalStateException e) - { - throw new SearchProcessorException(e.getMessage(), e); - } - catch (SQLException e) - { - throw new SearchProcessorException(e.getMessage(), e); - } - if (dso == null) - { - throw new SearchProcessorException("Query \"" + query - + "\" returned unresolvable handle: " + myHandle); - } - results[i] = dso; - } - - // Log - log.info(LogManager.getHeader(context, "search", logInfo + "query=\"" + query + "\",results=(" + results.length + ")")); - - // format and return results - Map labelMap = getLabels(request); - Document resultsDoc = OpenSearch.getResultsDoc(format, query, - qResults.getHitCount(), qResults.getStart(), - qResults.getPageSize(), container, results, labelMap); - try - { - Transformer xf = TransformerFactory.newInstance().newTransformer(); - response.setContentType(OpenSearch.getContentType(format)); - xf.transform(new DOMSource(resultsDoc), new StreamResult(response.getWriter())); - } - catch (TransformerException e) - { - log.error(e); - throw new ServletException(e.toString(), e); - } - } - - /** - * Export the search results as a csv file - * - * @param context The DSpace context - * @param response The request object - * @param items The result items - * @throws IOException - * @throws ServletException - */ - protected void exportMetadata(Context context, HttpServletResponse response, Item[] items) - throws IOException, ServletException - { - // Log the attempt - log.info(LogManager.getHeader(context, "metadataexport", "exporting_search")); - - // Export a search view - List iids = new ArrayList(); - for (Item item : items) - { - iids.add(item.getID()); - } - ItemIterator ii = new ItemIterator(context, iids); - MetadataExport exporter = new MetadataExport(context, ii, false); - - // Perform the export - DSpaceCSV csv = exporter.export(); - - // Return the csv file - response.setContentType("text/csv; charset=UTF-8"); - response.setHeader("Content-Disposition", "attachment; filename=search-results.csv"); - PrintWriter out = response.getWriter(); - out.write(csv.toString()); - out.flush(); - out.close(); - log.info(LogManager.getHeader(context, "metadataexport", "exported_file:search-results.csv")); - return; - } - - private Map getLabels(HttpServletRequest request) - { - // Get access to the localized resource bundle - Locale locale = UIUtil.getSessionLocale(request); - Map labelMap = localeLabels.get(locale.toString()); - if (labelMap == null) - { - labelMap = getLocaleLabels(locale); - localeLabels.put(locale.toString(), labelMap); - } - return labelMap; - } - - private Map getLocaleLabels(Locale locale) - { - Map labelMap = new HashMap(); - labelMap.put(SyndicationFeed.MSG_UNTITLED, I18nUtil.getMessage(msgKey + ".notitle", locale)); - labelMap.put(SyndicationFeed.MSG_LOGO_TITLE, I18nUtil.getMessage(msgKey + ".logo.title", locale)); - labelMap.put(SyndicationFeed.MSG_FEED_DESCRIPTION, I18nUtil.getMessage(msgKey + ".general-feed.description", locale)); - labelMap.put(SyndicationFeed.MSG_UITYPE, SyndicationFeed.UITYPE_JSPUI); - for (String selector : SyndicationFeed.getDescriptionSelectors()) - { - labelMap.put("metadata." + selector, I18nUtil.getMessage(SyndicationFeed.MSG_METADATA + selector, locale)); - } - return labelMap; - } -} +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.webui.search; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URLEncoder; +import java.sql.SQLException; +import java.util.*; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.bulkedit.DSpaceCSV; +import org.dspace.app.bulkedit.MetadataExport; +import org.dspace.app.util.OpenSearch; +import org.dspace.app.util.SyndicationFeed; +import org.dspace.app.util.Util; +import org.dspace.app.webui.servlet.SimpleSearchServlet; +import org.dspace.app.webui.util.JSPManager; +import org.dspace.app.webui.util.UIUtil; +import org.dspace.authorize.AuthorizeManager; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.ItemIterator; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.core.I18nUtil; +import org.dspace.core.LogManager; +import org.dspace.handle.HandleManager; +import org.dspace.search.DSQuery; +import org.dspace.search.QueryArgs; +import org.dspace.search.QueryResults; +import org.dspace.sort.SortOption; +import org.dspace.usage.UsageEvent; +import org.dspace.usage.UsageSearchEvent; +import org.dspace.utils.DSpace; +import org.w3c.dom.Document; + +public class LuceneSearchProcessor implements SearchRequestProcessor +{ + /** log4j category */ + private static Logger log = Logger.getLogger(SimpleSearchServlet.class); + + // locale-sensitive metadata labels + private Map> localeLabels = null; + + private static String msgKey = "org.dspace.app.webui.servlet.FeedServlet"; + + public synchronized void init() + { + if (localeLabels == null) + { + localeLabels = new HashMap>(); + } + } + + /** + *

+ * All metadata is search for the value contained in the "query" parameter. + * If the "location" parameter is present, the user's location is switched + * to that location using a redirect. Otherwise, the user's current location + * is used to constrain the query; i.e., if the user is "in" a collection, + * only results from the collection will be returned. + *

+ * The value of the "location" parameter should be ALL (which means no + * location), a the ID of a community (e.g. "123"), or a community ID, then + * a slash, then a collection ID, e.g. "123/456". + * + * @author Robert Tansley + */ + @Override + public void doSimpleSearch(Context context, HttpServletRequest request, + HttpServletResponse response) throws SearchProcessorException, IOException, ServletException + { + try + { + // Get the query + String query = request.getParameter("query"); + int start = UIUtil.getIntParameter(request, "start"); + String advanced = request.getParameter("advanced"); + String fromAdvanced = request.getParameter("from_advanced"); + int sortBy = UIUtil.getIntParameter(request, "sort_by"); + String order = request.getParameter("order"); + int rpp = UIUtil.getIntParameter(request, "rpp"); + String advancedQuery = ""; + + // can't start earlier than 0 in the results! + if (start < 0) + { + start = 0; + } + + int collCount = 0; + int commCount = 0; + int itemCount = 0; + + Item[] resultsItems; + Collection[] resultsCollections; + Community[] resultsCommunities; + + QueryResults qResults = null; + QueryArgs qArgs = new QueryArgs(); + SortOption sortOption = null; + + if (request.getParameter("etal") != null) + { + qArgs.setEtAl(UIUtil.getIntParameter(request, "etal")); + } + + try + { + if (sortBy > 0) + { + sortOption = SortOption.getSortOption(sortBy); + qArgs.setSortOption(sortOption); + } + + if (SortOption.ASCENDING.equalsIgnoreCase(order)) + { + qArgs.setSortOrder(SortOption.ASCENDING); + } + else + { + qArgs.setSortOrder(SortOption.DESCENDING); + } + } + catch (Exception e) + { + } + + // Override the page setting if exporting metadata + if ("submit_export_metadata".equals(UIUtil.getSubmitButton(request, "submit"))) + { + qArgs.setPageSize(Integer.MAX_VALUE); + } + else if (rpp > 0) + { + qArgs.setPageSize(rpp); + } + + // if the "advanced" flag is set, build the query string from the + // multiple query fields + if (advanced != null) + { + query = qArgs.buildQuery(request); + advancedQuery = qArgs.buildHTTPQuery(request); + } + + // Ensure the query is non-null + if (query == null) + { + query = ""; + } + + // Get the location parameter, if any + String location = request.getParameter("location"); + + // If there is a location parameter, we should redirect to + // do the search with the correct location. + if ((location != null) && !location.equals("")) + { + String url = ""; + + if (!location.equals("/")) + { + // Location is a Handle + url = "/handle/" + location; + } + + // Encode the query + query = URLEncoder.encode(query, Constants.DEFAULT_ENCODING); + + if (advancedQuery.length() > 0) + { + query = query + "&from_advanced=true&" + advancedQuery; + } + + // Do the redirect + response.sendRedirect(response.encodeRedirectURL(request + .getContextPath() + + url + "/simple-search?query=" + query)); + + return; + } + + // Build log information + String logInfo = ""; + + // Get our location + Community community = UIUtil.getCommunityLocation(request); + Collection collection = UIUtil.getCollectionLocation(request); + + // get the start of the query results page + // List resultObjects = null; + qArgs.setQuery(query); + qArgs.setStart(start); + + // Perform the search + if (collection != null) + { + logInfo = "collection_id=" + collection.getID() + ","; + + // Values for drop-down box + request.setAttribute("community", community); + request.setAttribute("collection", collection); + + qResults = DSQuery.doQuery(context, qArgs, collection); + } + else if (community != null) + { + logInfo = "community_id=" + community.getID() + ","; + + request.setAttribute("community", community); + + // Get the collections within the community for the dropdown box + request + .setAttribute("collection.array", community + .getCollections()); + + qResults = DSQuery.doQuery(context, qArgs, community); + } + else + { + // Get all communities for dropdown box + Community[] communities = Community.findAll(context); + request.setAttribute("community.array", communities); + + qResults = DSQuery.doQuery(context, qArgs); + } + + // now instantiate the results and put them in their buckets + for (int i = 0; i < qResults.getHitTypes().size(); i++) + { + Integer myType = qResults.getHitTypes().get(i); + + // add the handle to the appropriate lists + switch (myType.intValue()) + { + case Constants.ITEM: + itemCount++; + break; + + case Constants.COLLECTION: + collCount++; + break; + + case Constants.COMMUNITY: + commCount++; + break; + } + } + + // Make objects from the handles - make arrays, fill them out + resultsCommunities = new Community[commCount]; + resultsCollections = new Collection[collCount]; + resultsItems = new Item[itemCount]; + + collCount = 0; + commCount = 0; + itemCount = 0; + + for (int i = 0; i < qResults.getHitTypes().size(); i++) + { + Integer myId = qResults.getHitIds().get(i); + String myHandle = qResults.getHitHandles().get(i); + Integer myType = qResults.getHitTypes().get(i); + + // add the handle to the appropriate lists + switch (myType.intValue()) + { + case Constants.ITEM: + if (myId != null) + { + resultsItems[itemCount] = Item.find(context, myId); + } + else + { + resultsItems[itemCount] = (Item)HandleManager.resolveToObject(context, myHandle); + } + + if (resultsItems[itemCount] == null) + { + throw new SQLException("Query \"" + query + + "\" returned unresolvable item"); + } + itemCount++; + break; + + case Constants.COLLECTION: + if (myId != null) + { + resultsCollections[collCount] = Collection.find(context, myId); + } + else + { + resultsCollections[collCount] = (Collection)HandleManager.resolveToObject(context, myHandle); + } + + if (resultsCollections[collCount] == null) + { + throw new SQLException("Query \"" + query + + "\" returned unresolvable collection"); + } + + collCount++; + break; + + case Constants.COMMUNITY: + if (myId != null) + { + resultsCommunities[commCount] = Community.find(context, myId); + } + else + { + resultsCommunities[commCount] = (Community)HandleManager.resolveToObject(context, myHandle); + } + + if (resultsCommunities[commCount] == null) + { + throw new SQLException("Query \"" + query + + "\" returned unresolvable community"); + } + + commCount++; + break; + } + } + + // Log + log.info(LogManager.getHeader(context, "search", logInfo + "query=\"" + + query + "\",results=(" + resultsCommunities.length + "," + + resultsCollections.length + "," + resultsItems.length + ")")); + + // Pass in some page qualities + // total number of pages + int pageTotal = 1 + ((qResults.getHitCount() - 1) / qResults + .getPageSize()); + + // current page being displayed + int pageCurrent = 1 + (qResults.getStart() / qResults.getPageSize()); + + // pageLast = min(pageCurrent+9,pageTotal) + int pageLast = ((pageCurrent + 9) > pageTotal) ? pageTotal + : (pageCurrent + 9); + + // pageFirst = max(1,pageCurrent-9) + int pageFirst = ((pageCurrent - 9) > 1) ? (pageCurrent - 9) : 1; + + + //Fire an event to log our search + DSpaceObject scope = null; + if(collection != null){ + scope = collection; + }else + if(community != null){ + scope = community; + } + logSearch(context, request, query, pageCurrent, scope); + + + // Pass the results to the display JSP + request.setAttribute("items", resultsItems); + request.setAttribute("communities", resultsCommunities); + request.setAttribute("collections", resultsCollections); + + request.setAttribute("pagetotal", Integer.valueOf(pageTotal)); + request.setAttribute("pagecurrent", Integer.valueOf(pageCurrent)); + request.setAttribute("pagelast", Integer.valueOf(pageLast)); + request.setAttribute("pagefirst", Integer.valueOf(pageFirst)); + + request.setAttribute("queryresults", qResults); + + // And the original query string + request.setAttribute("query", query); + + request.setAttribute("order", qArgs.getSortOrder()); + request.setAttribute("sortedBy", sortOption); + + if (AuthorizeManager.isAdmin(context)) + { + // Set a variable to create admin buttons + request.setAttribute("admin_button", Boolean.TRUE); + } + + if ((fromAdvanced != null) && (qResults.getHitCount() == 0)) + { + // send back to advanced form if no results + Community[] communities = Community.findAll(context); + request.setAttribute("communities", communities); + request.setAttribute("no_results", "yes"); + + Map queryHash = qArgs.buildQueryMap(request); + + if (queryHash != null) + { + for (Map.Entry entry : queryHash.entrySet()) + { + request.setAttribute(entry.getKey(), entry.getValue()); + } + } + + JSPManager.showJSP(request, response, "/search/advanced.jsp"); + } + else if ("submit_export_metadata".equals(UIUtil.getSubmitButton(request, "submit"))) + { + exportMetadata(context, response, resultsItems); + } + else + { + JSPManager.showJSP(request, response, "/search/results.jsp"); + } + } + catch (IllegalStateException e) + { + throw new SearchProcessorException(e.getMessage(), e); + } + catch (SQLException e) + { + throw new SearchProcessorException(e.getMessage(), e); + } + } + + protected void logSearch(Context context, HttpServletRequest request, String query, int start, DSpaceObject scope) { + UsageSearchEvent searchEvent = new UsageSearchEvent( + UsageEvent.Action.SEARCH, + request, + context, + null, Arrays.asList(query), scope); + + + if(!StringUtils.isBlank(request.getParameter("rpp"))){ + searchEvent.setRpp(Integer.parseInt(request.getParameter("rpp"))); + } + if(!StringUtils.isBlank(request.getParameter("sort_by"))){ + searchEvent.setSortBy(request.getParameter("sort_by")); + } + if(!StringUtils.isBlank(request.getParameter("order"))){ + searchEvent.setSortOrder(request.getParameter("order")); + } + if(!StringUtils.isBlank(request.getParameter("start"))){ + searchEvent.setPage(start); + } + + //Fire our event + new DSpace().getEventService().fireEvent(searchEvent); + } + + + /** + * Method for constructing the advanced search form + * + * @author gam + */ + @Override + public void doAdvancedSearch(Context context, HttpServletRequest request, + HttpServletResponse response) throws SearchProcessorException, ServletException, IOException + { + // just build a list of top-level communities and pass along to the jsp + Community[] communities; + try + { + communities = Community.findAllTop(context); + } + catch (SQLException e) + { + throw new SearchProcessorException(e.getMessage(), e); + } + + request.setAttribute("communities", communities); + + JSPManager.showJSP(request, response, "/search/advanced.jsp"); + } + + /** + * Method for producing OpenSearch-compliant search results, and the + * OpenSearch description document. + *

+ * The value of the "scope" parameter should be absent (which means no scope + * restriction), or the handle of a community or collection, otherwise + * parameters exactly match those of the SearchServlet. + *

+ * + * @author Richard Rodgers + */ + @Override + public void doOpenSearch(Context context, HttpServletRequest request, + HttpServletResponse response) throws SearchProcessorException, IOException, ServletException + { + init(); + + // dispense with simple service document requests + String scope = request.getParameter("scope"); + if (scope !=null && "".equals(scope)) + { + scope = null; + } + String path = request.getPathInfo(); + if (path != null && path.endsWith("description.xml")) + { + String svcDescrip = OpenSearch.getDescription(scope); + response.setContentType(OpenSearch.getContentType("opensearchdescription")); + response.setContentLength(svcDescrip.length()); + response.getWriter().write(svcDescrip); + return; + } + + // get enough request parameters to decide on action to take + String format = request.getParameter("format"); + if (format == null || "".equals(format)) + { + // default to atom + format = "atom"; + } + + // do some sanity checking + if (! OpenSearch.getFormats().contains(format)) + { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + // then the rest - we are processing the query + String query = request.getParameter("query"); + int start = Util.getIntParameter(request, "start"); + int rpp = Util.getIntParameter(request, "rpp"); + int sort = Util.getIntParameter(request, "sort_by"); + String order = request.getParameter("order"); + String sortOrder = (order == null || order.length() == 0 || order.toLowerCase().startsWith("asc")) ? + SortOption.ASCENDING : SortOption.DESCENDING; + + QueryArgs qArgs = new QueryArgs(); + // can't start earlier than 0 in the results! + if (start < 0) + { + start = 0; + } + qArgs.setStart(start); + + if (rpp > 0) + { + qArgs.setPageSize(rpp); + } + qArgs.setSortOrder(sortOrder); + + if (sort > 0) + { + try + { + qArgs.setSortOption(SortOption.getSortOption(sort)); + } + catch(Exception e) + { + // invalid sort id - do nothing + } + } + qArgs.setSortOrder(sortOrder); + + // Ensure the query is non-null + if (query == null) + { + query = ""; + } + + // If there is a scope parameter, attempt to dereference it + // failure will only result in its being ignored + DSpaceObject container; + try + { + container = (scope != null) ? HandleManager.resolveToObject(context, scope) : null; + } + catch (IllegalStateException e) + { + throw new SearchProcessorException(e.getMessage(), e); + } + catch (SQLException e) + { + throw new SearchProcessorException(e.getMessage(), e); + } + + // Build log information + String logInfo = ""; + + // get the start of the query results page + qArgs.setQuery(query); + + // Perform the search + QueryResults qResults = null; + if (container == null) + { + qResults = DSQuery.doQuery(context, qArgs); + } + else if (container instanceof Collection) + { + logInfo = "collection_id=" + container.getID() + ","; + qResults = DSQuery.doQuery(context, qArgs, (Collection)container); + } + else if (container instanceof Community) + { + logInfo = "community_id=" + container.getID() + ","; + qResults = DSQuery.doQuery(context, qArgs, (Community)container); + } + else + { + throw new IllegalStateException("Invalid container for search context"); + } + + // now instantiate the results + DSpaceObject[] results = new DSpaceObject[qResults.getHitHandles().size()]; + for (int i = 0; i < qResults.getHitHandles().size(); i++) + { + String myHandle = qResults.getHitHandles().get(i); + DSpaceObject dso; + try + { + dso = HandleManager.resolveToObject(context, myHandle); + } + catch (IllegalStateException e) + { + throw new SearchProcessorException(e.getMessage(), e); + } + catch (SQLException e) + { + throw new SearchProcessorException(e.getMessage(), e); + } + if (dso == null) + { + throw new SearchProcessorException("Query \"" + query + + "\" returned unresolvable handle: " + myHandle); + } + results[i] = dso; + } + + // Log + log.info(LogManager.getHeader(context, "search", logInfo + "query=\"" + query + "\",results=(" + results.length + ")")); + + // format and return results + Map labelMap = getLabels(request); + Document resultsDoc = OpenSearch.getResultsDoc(format, query, + qResults.getHitCount(), qResults.getStart(), + qResults.getPageSize(), container, results, labelMap); + try + { + Transformer xf = TransformerFactory.newInstance().newTransformer(); + response.setContentType(OpenSearch.getContentType(format)); + xf.transform(new DOMSource(resultsDoc), new StreamResult(response.getWriter())); + } + catch (TransformerException e) + { + log.error(e); + throw new ServletException(e.toString(), e); + } + } + + /** + * Export the search results as a csv file + * + * @param context The DSpace context + * @param response The request object + * @param items The result items + * @throws IOException + * @throws ServletException + */ + protected void exportMetadata(Context context, HttpServletResponse response, Item[] items) + throws IOException, ServletException + { + // Log the attempt + log.info(LogManager.getHeader(context, "metadataexport", "exporting_search")); + + // Export a search view + List iids = new ArrayList(); + for (Item item : items) + { + iids.add(item.getID()); + } + ItemIterator ii = new ItemIterator(context, iids); + MetadataExport exporter = new MetadataExport(context, ii, false); + + // Perform the export + DSpaceCSV csv = exporter.export(); + + // Return the csv file + response.setContentType("text/csv; charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment; filename=search-results.csv"); + PrintWriter out = response.getWriter(); + out.write(csv.toString()); + out.flush(); + out.close(); + log.info(LogManager.getHeader(context, "metadataexport", "exported_file:search-results.csv")); + return; + } + + private Map getLabels(HttpServletRequest request) + { + // Get access to the localized resource bundle + Locale locale = UIUtil.getSessionLocale(request); + Map labelMap = localeLabels.get(locale.toString()); + if (labelMap == null) + { + labelMap = getLocaleLabels(locale); + localeLabels.put(locale.toString(), labelMap); + } + return labelMap; + } + + private Map getLocaleLabels(Locale locale) + { + Map labelMap = new HashMap(); + labelMap.put(SyndicationFeed.MSG_UNTITLED, I18nUtil.getMessage(msgKey + ".notitle", locale)); + labelMap.put(SyndicationFeed.MSG_LOGO_TITLE, I18nUtil.getMessage(msgKey + ".logo.title", locale)); + labelMap.put(SyndicationFeed.MSG_FEED_DESCRIPTION, I18nUtil.getMessage(msgKey + ".general-feed.description", locale)); + labelMap.put(SyndicationFeed.MSG_UITYPE, SyndicationFeed.UITYPE_JSPUI); + for (String selector : SyndicationFeed.getDescriptionSelectors()) + { + labelMap.put("metadata." + selector, I18nUtil.getMessage(SyndicationFeed.MSG_METADATA + selector, locale)); + } + return labelMap; + } +} diff --git a/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/DisplayStatisticsServlet.java b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/DisplayStatisticsServlet.java index 44b887b4ee..6caeee43e4 100644 --- a/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/DisplayStatisticsServlet.java +++ b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/DisplayStatisticsServlet.java @@ -1,338 +1,338 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.webui.servlet; - -import java.io.IOException; -import java.util.List; -import java.sql.SQLException; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.ServletException; -import org.dspace.authorize.AuthorizeException; - -import org.apache.log4j.Logger; -import org.dspace.core.ConfigurationManager; -import org.dspace.core.Constants; -import org.dspace.core.Context; -import org.dspace.eperson.Group; - -import org.dspace.content.DSpaceObject; -import org.dspace.handle.HandleManager; - -import org.dspace.statistics.Dataset; -import org.dspace.statistics.content.DatasetDSpaceObjectGenerator; -import org.dspace.statistics.content.DatasetTimeGenerator; -import org.dspace.statistics.content.DatasetTypeGenerator; -import org.dspace.statistics.content.StatisticsDataVisits; -import org.dspace.statistics.content.StatisticsListing; -import org.dspace.statistics.content.StatisticsTable; - -import org.dspace.app.webui.components.StatisticsBean; -import org.dspace.app.webui.util.JSPManager; - - -/** - * - * - * @author Kim Shepherd - * @version $Revision: 4386 $ - */ -public class DisplayStatisticsServlet extends DSpaceServlet -{ - /** log4j logger */ - private static Logger log = Logger.getLogger(DisplayStatisticsServlet.class); - - - protected void doDSGet(Context context, HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException, - SQLException, AuthorizeException - { - - // is the statistics data publically viewable? - boolean privatereport = ConfigurationManager.getBooleanProperty("usage-statistics", "authorization.admin"); - - // is the user a member of the Administrator (1) group? - boolean admin = Group.isMember(context, 1); - - if (!privatereport || admin) - { - displayStatistics(context, request, response); - } - else - { - throw new AuthorizeException(); - } - } - - protected void displayStatistics(Context context, HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException, - SQLException, AuthorizeException - { - - DSpaceObject dso = null; - String handle = request.getParameter("handle"); - - if("".equals(handle) || handle == null) - { - // We didn't get passed a handle parameter. - // That means we're looking at /handle/*/*/statistics - // with handle injected as attribute from HandleServlet - handle = (String) request.getAttribute("handle"); - - } - - if(handle != null) - { - dso = HandleManager.resolveToObject(context, handle); - } - - if(dso == null) - { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - JSPManager.showJSP(request, response, "/error/404.jsp"); - return; - } - - - - boolean isItem = false; - - StatisticsBean statsVisits = new StatisticsBean(); - StatisticsBean statsMonthlyVisits = new StatisticsBean(); - StatisticsBean statsFileDownloads = new StatisticsBean(); - StatisticsBean statsCountryVisits = new StatisticsBean(); - StatisticsBean statsCityVisits = new StatisticsBean(); - - try - { - StatisticsListing statListing = new StatisticsListing( - new StatisticsDataVisits(dso)); - - statListing.setTitle("Total Visits"); - statListing.setId("list1"); - - DatasetDSpaceObjectGenerator dsoAxis = new DatasetDSpaceObjectGenerator(); - dsoAxis.addDsoChild(dso.getType(), 10, false, -1); - statListing.addDatasetGenerator(dsoAxis); - Dataset dataset = statListing.getDataset(context); - - dataset = statListing.getDataset(); - - if (dataset == null) - { - - dataset = statListing.getDataset(context); - } - - if (dataset != null) - { - String[][] matrix = dataset.getMatrixFormatted(); - List colLabels = dataset.getColLabels(); - List rowLabels = dataset.getRowLabels(); - - statsVisits.setMatrix(matrix); - statsVisits.setColLabels(colLabels); - statsVisits.setRowLabels(rowLabels); - } - - - } catch (Exception e) - { - log.error( - "Error occured while creating statistics for dso with ID: " - + dso.getID() + " and type " + dso.getType() - + " and handle: " + dso.getHandle(), e); - } - - - try - { - - StatisticsTable statisticsTable = new StatisticsTable(new StatisticsDataVisits(dso)); - - statisticsTable.setTitle("Total Visits Per Month"); - statisticsTable.setId("tab1"); - - DatasetTimeGenerator timeAxis = new DatasetTimeGenerator(); - timeAxis.setDateInterval("month", "-6", "+1"); - statisticsTable.addDatasetGenerator(timeAxis); - - DatasetDSpaceObjectGenerator dsoAxis = new DatasetDSpaceObjectGenerator(); - dsoAxis.addDsoChild(dso.getType(), 10, false, -1); - statisticsTable.addDatasetGenerator(dsoAxis); - Dataset dataset = statisticsTable.getDataset(context); - - dataset = statisticsTable.getDataset(); - - if (dataset == null) - { - - dataset = statisticsTable.getDataset(context); - } - - if (dataset != null) - { - String[][] matrix = dataset.getMatrixFormatted(); - List colLabels = dataset.getColLabels(); - List rowLabels = dataset.getRowLabels(); - - statsMonthlyVisits.setMatrix(matrix); - statsMonthlyVisits.setColLabels(colLabels); - statsMonthlyVisits.setRowLabels(rowLabels); - } - } catch (Exception e) - { - log.error( - "Error occured while creating statistics for dso with ID: " - + dso.getID() + " and type " + dso.getType() - + " and handle: " + dso.getHandle(), e); - } - - if(dso instanceof org.dspace.content.Item) - { - isItem = true; - try - { - - StatisticsListing statisticsTable = new StatisticsListing(new StatisticsDataVisits(dso)); - - statisticsTable.setTitle("File Downloads"); - statisticsTable.setId("tab1"); - - DatasetDSpaceObjectGenerator dsoAxis = new DatasetDSpaceObjectGenerator(); - dsoAxis.addDsoChild(Constants.BITSTREAM, 10, false, -1); - statisticsTable.addDatasetGenerator(dsoAxis); - - Dataset dataset = statisticsTable.getDataset(context); - - dataset = statisticsTable.getDataset(); - - if (dataset == null) - { - - dataset = statisticsTable.getDataset(context); - } - - if (dataset != null) - { - String[][] matrix = dataset.getMatrixFormatted(); - List colLabels = dataset.getColLabels(); - List rowLabels = dataset.getRowLabels(); - - statsFileDownloads.setMatrix(matrix); - statsFileDownloads.setColLabels(colLabels); - statsFileDownloads.setRowLabels(rowLabels); - } - } - catch (Exception e) - { - log.error( - "Error occured while creating statistics for dso with ID: " - + dso.getID() + " and type " + dso.getType() - + " and handle: " + dso.getHandle(), e); - } - } - - try - { - - StatisticsListing statisticsTable = new StatisticsListing(new StatisticsDataVisits(dso)); - - statisticsTable.setTitle("Top country views"); - statisticsTable.setId("tab1"); - - DatasetTypeGenerator typeAxis = new DatasetTypeGenerator(); - typeAxis.setType("countryCode"); - typeAxis.setMax(10); - statisticsTable.addDatasetGenerator(typeAxis); - - Dataset dataset = statisticsTable.getDataset(context); - - dataset = statisticsTable.getDataset(); - - if (dataset == null) - { - - dataset = statisticsTable.getDataset(context); - } - - if (dataset != null) - { - String[][] matrix = dataset.getMatrixFormatted(); - List colLabels = dataset.getColLabels(); - List rowLabels = dataset.getRowLabels(); - - statsCountryVisits.setMatrix(matrix); - statsCountryVisits.setColLabels(colLabels); - statsCountryVisits.setRowLabels(rowLabels); - } - } - catch (Exception e) - { - log.error( - "Error occured while creating statistics for dso with ID: " - + dso.getID() + " and type " + dso.getType() - + " and handle: " + dso.getHandle(), e); - } - - try - { - - StatisticsListing statisticsTable = new StatisticsListing(new StatisticsDataVisits(dso)); - - statisticsTable.setTitle("Top city views"); - statisticsTable.setId("tab1"); - - DatasetTypeGenerator typeAxis = new DatasetTypeGenerator(); - typeAxis.setType("city"); - typeAxis.setMax(10); - statisticsTable.addDatasetGenerator(typeAxis); - - Dataset dataset = statisticsTable.getDataset(context); - - dataset = statisticsTable.getDataset(); - - if (dataset == null) - { - - dataset = statisticsTable.getDataset(context); - } - - if (dataset != null) - { - String[][] matrix = dataset.getMatrixFormatted(); - List colLabels = dataset.getColLabels(); - List rowLabels = dataset.getRowLabels(); - - statsCityVisits.setMatrix(matrix); - statsCityVisits.setColLabels(colLabels); - statsCityVisits.setRowLabels(rowLabels); - } - } - catch (Exception e) - { - log.error( - "Error occured while creating statistics for dso with ID: " - + dso.getID() + " and type " + dso.getType() - + " and handle: " + dso.getHandle(), e); - } - - - request.setAttribute("statsVisits", statsVisits); - request.setAttribute("statsMonthlyVisits", statsMonthlyVisits); - request.setAttribute("statsFileDownloads", statsFileDownloads); - request.setAttribute("statsCountryVisits",statsCountryVisits); - request.setAttribute("statsCityVisits", statsCityVisits); - request.setAttribute("isItem", isItem); - - JSPManager.showJSP(request, response, "display-statistics.jsp"); - - } - -} +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.webui.servlet; + +import java.io.IOException; +import java.util.List; +import java.sql.SQLException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletException; +import org.dspace.authorize.AuthorizeException; + +import org.apache.log4j.Logger; +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.Group; + +import org.dspace.content.DSpaceObject; +import org.dspace.handle.HandleManager; + +import org.dspace.statistics.Dataset; +import org.dspace.statistics.content.DatasetDSpaceObjectGenerator; +import org.dspace.statistics.content.DatasetTimeGenerator; +import org.dspace.statistics.content.DatasetTypeGenerator; +import org.dspace.statistics.content.StatisticsDataVisits; +import org.dspace.statistics.content.StatisticsListing; +import org.dspace.statistics.content.StatisticsTable; + +import org.dspace.app.webui.components.StatisticsBean; +import org.dspace.app.webui.util.JSPManager; + + +/** + * + * + * @author Kim Shepherd + * @version $Revision: 4386 $ + */ +public class DisplayStatisticsServlet extends DSpaceServlet +{ + /** log4j logger */ + private static Logger log = Logger.getLogger(DisplayStatisticsServlet.class); + + + protected void doDSGet(Context context, HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException, + SQLException, AuthorizeException + { + + // is the statistics data publically viewable? + boolean privatereport = ConfigurationManager.getBooleanProperty("usage-statistics", "authorization.admin"); + + // is the user a member of the Administrator (1) group? + boolean admin = Group.isMember(context, 1); + + if (!privatereport || admin) + { + displayStatistics(context, request, response); + } + else + { + throw new AuthorizeException(); + } + } + + protected void displayStatistics(Context context, HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException, + SQLException, AuthorizeException + { + + DSpaceObject dso = null; + String handle = request.getParameter("handle"); + + if("".equals(handle) || handle == null) + { + // We didn't get passed a handle parameter. + // That means we're looking at /handle/*/*/statistics + // with handle injected as attribute from HandleServlet + handle = (String) request.getAttribute("handle"); + + } + + if(handle != null) + { + dso = HandleManager.resolveToObject(context, handle); + } + + if(dso == null) + { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + JSPManager.showJSP(request, response, "/error/404.jsp"); + return; + } + + + + boolean isItem = false; + + StatisticsBean statsVisits = new StatisticsBean(); + StatisticsBean statsMonthlyVisits = new StatisticsBean(); + StatisticsBean statsFileDownloads = new StatisticsBean(); + StatisticsBean statsCountryVisits = new StatisticsBean(); + StatisticsBean statsCityVisits = new StatisticsBean(); + + try + { + StatisticsListing statListing = new StatisticsListing( + new StatisticsDataVisits(dso)); + + statListing.setTitle("Total Visits"); + statListing.setId("list1"); + + DatasetDSpaceObjectGenerator dsoAxis = new DatasetDSpaceObjectGenerator(); + dsoAxis.addDsoChild(dso.getType(), 10, false, -1); + statListing.addDatasetGenerator(dsoAxis); + Dataset dataset = statListing.getDataset(context); + + dataset = statListing.getDataset(); + + if (dataset == null) + { + + dataset = statListing.getDataset(context); + } + + if (dataset != null) + { + String[][] matrix = dataset.getMatrix(); + List colLabels = dataset.getColLabels(); + List rowLabels = dataset.getRowLabels(); + + statsVisits.setMatrix(matrix); + statsVisits.setColLabels(colLabels); + statsVisits.setRowLabels(rowLabels); + } + + + } catch (Exception e) + { + log.error( + "Error occured while creating statistics for dso with ID: " + + dso.getID() + " and type " + dso.getType() + + " and handle: " + dso.getHandle(), e); + } + + + try + { + + StatisticsTable statisticsTable = new StatisticsTable(new StatisticsDataVisits(dso)); + + statisticsTable.setTitle("Total Visits Per Month"); + statisticsTable.setId("tab1"); + + DatasetTimeGenerator timeAxis = new DatasetTimeGenerator(); + timeAxis.setDateInterval("month", "-6", "+1"); + statisticsTable.addDatasetGenerator(timeAxis); + + DatasetDSpaceObjectGenerator dsoAxis = new DatasetDSpaceObjectGenerator(); + dsoAxis.addDsoChild(dso.getType(), 10, false, -1); + statisticsTable.addDatasetGenerator(dsoAxis); + Dataset dataset = statisticsTable.getDataset(context); + + dataset = statisticsTable.getDataset(); + + if (dataset == null) + { + + dataset = statisticsTable.getDataset(context); + } + + if (dataset != null) + { + String[][] matrix = dataset.getMatrix(); + List colLabels = dataset.getColLabels(); + List rowLabels = dataset.getRowLabels(); + + statsMonthlyVisits.setMatrix(matrix); + statsMonthlyVisits.setColLabels(colLabels); + statsMonthlyVisits.setRowLabels(rowLabels); + } + } catch (Exception e) + { + log.error( + "Error occured while creating statistics for dso with ID: " + + dso.getID() + " and type " + dso.getType() + + " and handle: " + dso.getHandle(), e); + } + + if(dso instanceof org.dspace.content.Item) + { + isItem = true; + try + { + + StatisticsListing statisticsTable = new StatisticsListing(new StatisticsDataVisits(dso)); + + statisticsTable.setTitle("File Downloads"); + statisticsTable.setId("tab1"); + + DatasetDSpaceObjectGenerator dsoAxis = new DatasetDSpaceObjectGenerator(); + dsoAxis.addDsoChild(Constants.BITSTREAM, 10, false, -1); + statisticsTable.addDatasetGenerator(dsoAxis); + + Dataset dataset = statisticsTable.getDataset(context); + + dataset = statisticsTable.getDataset(); + + if (dataset == null) + { + + dataset = statisticsTable.getDataset(context); + } + + if (dataset != null) + { + String[][] matrix = dataset.getMatrix(); + List colLabels = dataset.getColLabels(); + List rowLabels = dataset.getRowLabels(); + + statsFileDownloads.setMatrix(matrix); + statsFileDownloads.setColLabels(colLabels); + statsFileDownloads.setRowLabels(rowLabels); + } + } + catch (Exception e) + { + log.error( + "Error occured while creating statistics for dso with ID: " + + dso.getID() + " and type " + dso.getType() + + " and handle: " + dso.getHandle(), e); + } + } + + try + { + + StatisticsListing statisticsTable = new StatisticsListing(new StatisticsDataVisits(dso)); + + statisticsTable.setTitle("Top country views"); + statisticsTable.setId("tab1"); + + DatasetTypeGenerator typeAxis = new DatasetTypeGenerator(); + typeAxis.setType("countryCode"); + typeAxis.setMax(10); + statisticsTable.addDatasetGenerator(typeAxis); + + Dataset dataset = statisticsTable.getDataset(context); + + dataset = statisticsTable.getDataset(); + + if (dataset == null) + { + + dataset = statisticsTable.getDataset(context); + } + + if (dataset != null) + { + String[][] matrix = dataset.getMatrix(); + List colLabels = dataset.getColLabels(); + List rowLabels = dataset.getRowLabels(); + + statsCountryVisits.setMatrix(matrix); + statsCountryVisits.setColLabels(colLabels); + statsCountryVisits.setRowLabels(rowLabels); + } + } + catch (Exception e) + { + log.error( + "Error occured while creating statistics for dso with ID: " + + dso.getID() + " and type " + dso.getType() + + " and handle: " + dso.getHandle(), e); + } + + try + { + + StatisticsListing statisticsTable = new StatisticsListing(new StatisticsDataVisits(dso)); + + statisticsTable.setTitle("Top city views"); + statisticsTable.setId("tab1"); + + DatasetTypeGenerator typeAxis = new DatasetTypeGenerator(); + typeAxis.setType("city"); + typeAxis.setMax(10); + statisticsTable.addDatasetGenerator(typeAxis); + + Dataset dataset = statisticsTable.getDataset(context); + + dataset = statisticsTable.getDataset(); + + if (dataset == null) + { + + dataset = statisticsTable.getDataset(context); + } + + if (dataset != null) + { + String[][] matrix = dataset.getMatrix(); + List colLabels = dataset.getColLabels(); + List rowLabels = dataset.getRowLabels(); + + statsCityVisits.setMatrix(matrix); + statsCityVisits.setColLabels(colLabels); + statsCityVisits.setRowLabels(rowLabels); + } + } + catch (Exception e) + { + log.error( + "Error occured while creating statistics for dso with ID: " + + dso.getID() + " and type " + dso.getType() + + " and handle: " + dso.getHandle(), e); + } + + + request.setAttribute("statsVisits", statsVisits); + request.setAttribute("statsMonthlyVisits", statsMonthlyVisits); + request.setAttribute("statsFileDownloads", statsFileDownloads); + request.setAttribute("statsCountryVisits",statsCountryVisits); + request.setAttribute("statsCityVisits", statsCityVisits); + request.setAttribute("isItem", isItem); + + JSPManager.showJSP(request, response, "display-statistics.jsp"); + + } + +} \ No newline at end of file diff --git a/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/SearchResultLogServlet.java b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/SearchResultLogServlet.java new file mode 100644 index 0000000000..4b40a0eef2 --- /dev/null +++ b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/SearchResultLogServlet.java @@ -0,0 +1,74 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.webui.servlet; + +import org.apache.commons.lang.StringUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.handle.HandleManager; +import org.dspace.usage.UsageEvent; +import org.dspace.usage.UsageSearchEvent; +import org.dspace.utils.DSpace; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.sql.SQLException; +import java.util.Arrays; + +/** + * Every time a user clicks on a search result he will be redirected through this servlet + * this servlet will retrieve all query information & store this for the search statistics + * Once everything has been stored the user will be + * redirected to the dso he clicked on (indicated by the redirectUrl parameter) + * + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class SearchResultLogServlet extends DSpaceServlet{ + + @Override + protected void doDSPost(Context context, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, SQLException, AuthorizeException { + String redirectUrl = request.getParameter("redirectUrl"); + String scopeHandle = request.getParameter("scope"); + DSpaceObject scope = HandleManager.resolveToObject(context, scopeHandle); + String resultHandle = StringUtils.substringAfter(redirectUrl, "/handle/"); + DSpaceObject result = HandleManager.resolveToObject(context, resultHandle); + + //Fire an event to log our search result + UsageSearchEvent searchEvent = new UsageSearchEvent( + UsageEvent.Action.SEARCH, + request, + context, + result, + Arrays.asList(request.getParameterValues("query")), scope); + + if(!StringUtils.isBlank(request.getParameter("rpp"))){ + searchEvent.setRpp(Integer.parseInt(request.getParameter("rpp"))); + } + if(!StringUtils.isBlank(request.getParameter("sort_by"))){ + searchEvent.setSortBy(request.getParameter("sort_by")); + } + if(!StringUtils.isBlank(request.getParameter("order"))){ + searchEvent.setSortOrder(request.getParameter("order")); + } + if(!StringUtils.isBlank(request.getParameter("page"))){ + searchEvent.setPage(Integer.parseInt(request.getParameter("page"))); + } + + new DSpace().getEventService().fireEvent( + searchEvent); + + + response.sendRedirect(redirectUrl); + + } +} diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml index acdf9c1f2b..4b2ef23d54 100644 --- a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml @@ -428,6 +428,11 @@ org.dspace.app.webui.servlet.AuthorityChooseServlet + + SearchResultLogServlet + org.dspace.app.webui.servlet.SearchResultLogServlet + + shibboleth-login @@ -722,6 +727,11 @@ /json/* + + SearchResultLogServlet + /dso-display + + ico diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/search/results.jsp b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/search/results.jsp index 43c3b84fde..d3577deb22 100644 --- a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/search/results.jsp +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/search/results.jsp @@ -92,6 +92,10 @@ + + + + <%--

Search Results

--%>

@@ -293,6 +297,10 @@ else +<% + if(0 < communities.length || 0 < collections.length || 0 < items.length){ +%> +
<% if (communities.length > 0 ) { %> <%--

Community Hits:

--%>

@@ -312,6 +320,10 @@ else

<% } %> +
+<% + } +%>

@@ -383,5 +395,15 @@ if (pageTotal > pageCurrent)

+
+ + + + + + "/> + +
+
diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/static/js/jquery/jquery-1.6.2.min.js b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/static/js/jquery/jquery-1.6.2.min.js new file mode 100644 index 0000000000..57618417e2 --- /dev/null +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/static/js/jquery/jquery-1.6.2.min.js @@ -0,0 +1,25 @@ +/* + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +/*! + * jQuery JavaScript Library v1.6.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Jun 30 14:16:56 2011 -0400 + */ +(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. +shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j +)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/static/js/search-results.js b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/static/js/search-results.js new file mode 100644 index 0000000000..1f3334e026 --- /dev/null +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/static/js/search-results.js @@ -0,0 +1,32 @@ +/* + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +$.noConflict(); +(function ($) { + + /** + * Function ensures that all the links clicked in our results pass through the internal logging mechanism + */ + $(document).ready(function() { + + + //Retrieve all links with handles attached (comm/coll/item links) + var urls = $('div#search-results-division').find('a'); + + urls.click(function(){ + var $this = $(this); + //Instead of redirecting us to the page, first send us to the statistics logger + //By doing this we ensure that we register the query to the result + var form = $('form#dso-display'); + form.find('input[name="redirectUrl"]').val($this.attr('href')); + form.submit(); + return false; + }); + }); + + +})(jQuery); diff --git a/dspace-stats/pom.xml b/dspace-stats/pom.xml index bb5fcda8dd..1e521b956b 100644 --- a/dspace-stats/pom.xml +++ b/dspace-stats/pom.xml @@ -145,14 +145,19 @@ org.slf4j - slf4j-api + jcl-over-slf4j org.slf4j - jcl-over-slf4j + slf4j-api + + commons-configuration + commons-configuration + 1.8 + org.dspace.dependencies dspace-geoip diff --git a/dspace-stats/src/main/java/org/dspace/statistics/Dataset.java b/dspace-stats/src/main/java/org/dspace/statistics/Dataset.java index 050584dd2f..978364d161 100644 --- a/dspace-stats/src/main/java/org/dspace/statistics/Dataset.java +++ b/dspace-stats/src/main/java/org/dspace/statistics/Dataset.java @@ -18,13 +18,14 @@ import java.util.Map; import com.Ostermiller.util.ExcelCSVPrinter; import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; /** - * + * * @author kevinvandevelde at atmire.com * Date: 21-jan-2009 * Time: 13:44:48 - * + * */ public class Dataset { @@ -41,14 +42,14 @@ public class Dataset { /* The attributes for the rows */ private List> rowLabelsAttrs; /* The data in a matrix */ - private float[][]matrix; + private String[][]matrix; /* The format in which we format our floats */ private String format = "0"; public Dataset(int rows, int cols){ - matrix = new float[rows][cols]; + matrix = new String[rows][cols]; nbRows = rows; nbCols = cols; initColumnLabels(cols); @@ -56,7 +57,7 @@ public class Dataset { } public Dataset(float[][] matrix){ - this.matrix = (float[][]) ArrayUtils.clone(matrix); + this.matrix = (String[][]) ArrayUtils.clone(matrix); nbRows = matrix.length; if(0 < matrix.length && 0 < matrix[0].length) { @@ -146,10 +147,6 @@ public class Dataset { return rowLabels; } - public float[][] getMatrix() { - return (float[][]) ArrayUtils.clone(matrix); - } - public int getNbRows() { return nbRows; } @@ -166,40 +163,32 @@ public class Dataset { this.format = format; } - public String[][] getMatrixFormatted(){ - DecimalFormat decimalFormat = new DecimalFormat(format); + public String[][] getMatrix(){ if (matrix.length == 0) { return new String[0][0]; } else { - String[][] strMatrix = new String[matrix.length][matrix[0].length]; - for (int i = 0; i < matrix.length; i++) { - for (int j = 0; j < matrix[i].length; j++) { - strMatrix[i][j] = decimalFormat.format(matrix[i][j]); - } - } - return strMatrix; + return matrix; } } public void addValueToMatrix(int row, int coll, float value) { - matrix[row][coll] = value; + DecimalFormat decimalFormat = new DecimalFormat(format); + matrix[row][coll] = decimalFormat.format(value); } public void addValueToMatrix(int row, int coll, String value) throws ParseException { - DecimalFormat decimalFormat = new DecimalFormat(format); - Number number = decimalFormat.parse(value); - matrix[row][coll] = number.floatValue(); + matrix[row][coll] = value; } /** - * Returns false if this dataset only contains zero's. + * Returns false if this dataset only contains zero's. */ public boolean containsNonZeroValues(){ if (matrix != null) { - for (float[] vector : matrix) { - for (float v : vector) { - if (v != 0) + for (String[] vector : matrix) { + for (String v : vector) { + if (StringUtils.isBlank(v) || v.equals("0")) { return true; } @@ -215,7 +204,7 @@ public class Dataset { //Lets make sure we at least have something to flip if(0 < matrix.length && 0 < matrix[0].length){ //Flip the data first - float[][] newMatrix = new float[matrix[0].length][matrix.length]; + String[][] newMatrix = new String[matrix[0].length][matrix.length]; for (int i = 0; i < matrix.length; i++) { for (int j = 0; j < matrix[i].length; j++) { newMatrix[j][i] = matrix[i][j]; @@ -258,7 +247,7 @@ public class Dataset { ecsvp.writeln(); List rowLabels = getRowLabels(); - String[][] matrix = getMatrixFormatted(); + String[][] matrix = getMatrix(); for (int i = 0; i < rowLabels.size(); i++) { String rowLabel = rowLabels.get(i); ecsvp.write(rowLabel); diff --git a/dspace-stats/src/main/java/org/dspace/statistics/SolrLogger.java b/dspace-stats/src/main/java/org/dspace/statistics/SolrLogger.java index c8747cea6f..dd981ab8db 100644 --- a/dspace-stats/src/main/java/org/dspace/statistics/SolrLogger.java +++ b/dspace-stats/src/main/java/org/dspace/statistics/SolrLogger.java @@ -1,1149 +1,1460 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.statistics; - -import com.Ostermiller.util.CSVParser; -import com.Ostermiller.util.CSVPrinter; -import com.maxmind.geoip.Location; -import com.maxmind.geoip.LookupService; - -import java.io.*; - -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.time.DateFormatUtils; -import org.apache.log4j.Logger; -import org.apache.solr.client.solrj.SolrQuery; -import org.apache.solr.client.solrj.SolrServerException; -import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer; -import org.apache.solr.client.solrj.request.AbstractUpdateRequest; -import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest; -import org.apache.solr.client.solrj.response.FacetField; -import org.apache.solr.client.solrj.response.QueryResponse; -import org.apache.solr.client.solrj.util.ClientUtils; -import org.apache.solr.common.SolrDocument; -import org.apache.solr.common.SolrInputDocument; -import org.apache.solr.common.params.CommonParams; -import org.apache.solr.common.params.MapSolrParams; -import org.dspace.content.*; -import org.dspace.content.Collection; -import org.dspace.core.ConfigurationManager; -import org.dspace.core.Constants; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; -import org.dspace.statistics.util.DnsLookup; -import org.dspace.statistics.util.LocationUtils; -import org.dspace.statistics.util.SpiderDetector; - -import javax.servlet.http.HttpServletRequest; -import java.net.URLEncoder; -import java.sql.SQLException; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; - -/** - * Static holder for a HttpSolrClient connection pool to issue - * usage logging events to Solr from DSpace libraries, and some static query - * composers. - * - * @author ben at atmire.com - * @author kevinvandevelde at atmire.com - * @author mdiggory at atmire.com - */ -public class SolrLogger -{ - private static final Logger log = Logger.getLogger(SolrLogger.class); - - private static final CommonsHttpSolrServer solr; - - public static final String DATE_FORMAT_8601 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; - - public static final String DATE_FORMAT_DCDATE = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - - private static final LookupService locationService; - - private static final boolean useProxies; - - private static Map metadataStorageInfo; - - static - { - log.info("solr-statistics.spidersfile:" + ConfigurationManager.getProperty("solr-statistics", "spidersfile")); - log.info("solr-statistics.server:" + ConfigurationManager.getProperty("solr-statistics", "server")); - log.info("usage-statistics.dbfile:" + ConfigurationManager.getProperty("usage-statistics", "dbfile")); - - CommonsHttpSolrServer server = null; - - if (ConfigurationManager.getProperty("solr-statistics", "server") != null) - { - try - { - server = new CommonsHttpSolrServer(ConfigurationManager.getProperty("solr-statistics", "server")); - SolrQuery solrQuery = new SolrQuery() - .setQuery("type:2 AND id:1"); - server.query(solrQuery); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } - solr = server; - - // Read in the file so we don't have to do it all the time - //spiderIps = SpiderDetector.getSpiderIpAddresses(); - - LookupService service = null; - // Get the db file for the location - String dbfile = ConfigurationManager.getProperty("usage-statistics", "dbfile"); - if (dbfile != null) - { - try - { - service = new LookupService(dbfile, - LookupService.GEOIP_STANDARD); - } - catch (FileNotFoundException fe) - { - log.error("The GeoLite Database file is missing (" + dbfile + ")! Solr Statistics cannot generate location based reports! Please see the DSpace installation instructions for instructions to install this file.", fe); - } - catch (IOException e) - { - log.error("Unable to load GeoLite Database file (" + dbfile + ")! You may need to reinstall it. See the DSpace installation instructions for more details.", e); - } - } - else - { - log.error("The required 'dbfile' configuration is missing in usage-statistics.cfg!"); - } - locationService = service; - - if ("true".equals(ConfigurationManager.getProperty("useProxies"))) - { - useProxies = true; - } - else - { - useProxies = false; - } - - log.info("useProxies=" + useProxies); - - metadataStorageInfo = new HashMap(); - int count = 1; - String metadataVal; - while ((metadataVal = ConfigurationManager.getProperty("solr-statistics","metadata.item." + count)) != null) - { - String storeVal = metadataVal.split(":")[0]; - String metadataField = metadataVal.split(":")[1]; - - metadataStorageInfo.put(storeVal, metadataField); - log.info("solr-statistics.metadata.item." + count + "=" + metadataVal); - count++; - } - } - - /** - * Store a usage event into Solr. - * - * @param dspaceObject the object used. - * @param request the current request context. - * @param currentUser the current session's user. - */ - public static void post(DSpaceObject dspaceObject, HttpServletRequest request, - EPerson currentUser) - { - if (solr == null || locationService == null) - { - return; - } - - boolean isSpiderBot = SpiderDetector.isSpider(request); - - try - { - if(isSpiderBot && - !ConfigurationManager.getBooleanProperty("usage-statistics", "logBots",true)) - { - return; - } - - - - SolrInputDocument doc1 = new SolrInputDocument(); - // Save our basic info that we already have - - String ip = request.getRemoteAddr(); - - if(isUseProxies() && request.getHeader("X-Forwarded-For") != null) - { - /* This header is a comma delimited list */ - for(String xfip : request.getHeader("X-Forwarded-For").split(",")) - { - /* proxy itself will sometime populate this header with the same value in - remote address. ordering in spec is vague, we'll just take the last - not equal to the proxy - */ - if(!request.getHeader("X-Forwarded-For").contains(ip)) - { - ip = xfip.trim(); - } - } - } - - doc1.addField("ip", ip); - - doc1.addField("id", dspaceObject.getID()); - doc1.addField("type", dspaceObject.getType()); - // Save the current time - doc1.addField("time", DateFormatUtils.format(new Date(), DATE_FORMAT_8601)); - if (currentUser != null) - { - doc1.addField("epersonid", currentUser.getID()); - } - - try - { - String dns = DnsLookup.reverseDns(ip); - doc1.addField("dns", dns.toLowerCase()); - } - catch (Exception e) - { - log.error("Failed DNS Lookup for IP:" + ip); - log.debug(e.getMessage(),e); - } - - // Save the location information if valid, save the event without - // location information if not valid - Location location = locationService.getLocation(ip); - if (location != null - && !("--".equals(location.countryCode) - && location.latitude == -180 && location.longitude == -180)) - { - try - { - doc1.addField("continent", LocationUtils - .getContinentCode(location.countryCode)); - } - catch (Exception e) - { - System.out - .println("COUNTRY ERROR: " + location.countryCode); - } - doc1.addField("countryCode", location.countryCode); - doc1.addField("city", location.city); - doc1.addField("latitude", location.latitude); - doc1.addField("longitude", location.longitude); - doc1.addField("isBot",isSpiderBot); - - if(request.getHeader("User-Agent") != null) - { - doc1.addField("userAgent", request.getHeader("User-Agent")); - } - } - - if (dspaceObject instanceof Item) - { - Item item = (Item) dspaceObject; - // Store the metadata - for (Object storedField : metadataStorageInfo.keySet()) - { - String dcField = metadataStorageInfo - .get(storedField); - - DCValue[] vals = item.getMetadata(dcField.split("\\.")[0], - dcField.split("\\.")[1], dcField.split("\\.")[2], - Item.ANY); - for (DCValue val1 : vals) - { - String val = val1.value; - doc1.addField(String.valueOf(storedField), val); - doc1.addField(storedField + "_search", val - .toLowerCase()); - } - } - } - - if(dspaceObject instanceof Bitstream) - { - Bitstream bit = (Bitstream) dspaceObject; - Bundle[] bundles = bit.getBundles(); - for (Bundle bundle : bundles) { - doc1.addField("bundleName", bundle.getName()); - } - } - - storeParents(doc1, dspaceObject); - - solr.add(doc1); - //commits are executed automatically using the solr autocommit -// solr.commit(false, false); - - } - catch (RuntimeException re) - { - throw re; - } - catch (Exception e) - { - log.error(e.getMessage(), e); - } - } - - //@TODO remove metadataStorage object from all see: DS-421 - public static Map getMetadataStorageInfo() - { - return metadataStorageInfo; - } - - /** - * Method just used to log the parents. - *
    - *
  • Community log: owning comms.
  • - *
  • Collection log: owning comms & their comms.
  • - *
  • Item log: owning colls/comms.
  • - *
  • Bitstream log: owning item/colls/comms.
  • - *
- * - * @param doc1 - * the current SolrInputDocument - * @param dso - * the current dspace object we want to log - * @throws java.sql.SQLException - * ignore it - */ - public static void storeParents(SolrInputDocument doc1, DSpaceObject dso) - throws SQLException - { - if (dso instanceof Community) - { - Community comm = (Community) dso; - while (comm != null && comm.getParentCommunity() != null) - { - comm = comm.getParentCommunity(); - doc1.addField("owningComm", comm.getID()); - } - } - else if (dso instanceof Collection) - { - Collection coll = (Collection) dso; - for (int i = 0; i < coll.getCommunities().length; i++) - { - Community community = coll.getCommunities()[i]; - doc1.addField("owningComm", community.getID()); - storeParents(doc1, community); - } - } - else if (dso instanceof Item) - { - Item item = (Item) dso; - for (int i = 0; i < item.getCollections().length; i++) - { - Collection collection = item.getCollections()[i]; - doc1.addField("owningColl", collection.getID()); - storeParents(doc1, collection); - } - } - else if (dso instanceof Bitstream) - { - Bitstream bitstream = (Bitstream) dso; - for (int i = 0; i < bitstream.getBundles().length; i++) - { - Bundle bundle = bitstream.getBundles()[i]; - for (int j = 0; j < bundle.getItems().length; j++) - { - Item item = bundle.getItems()[j]; - doc1.addField("owningItem", item.getID()); - storeParents(doc1, item); - } - } - } - } - - public static boolean isUseProxies() - { - return useProxies; - } - - /** - * Delete data from the index, as described by a query. - * - * @param query description of the records to be deleted. - * @throws IOException - * @throws SolrServerException - */ - public static void removeIndex(String query) throws IOException, - SolrServerException - { - solr.deleteByQuery(query); - solr.commit(); - } - - public static Map> queryField(String query, - List oldFieldVals, String field) - { - Map> currentValsStored = new HashMap>(); - try - { - // Get one document (since all the metadata for all the values - // should be the same just get the first one we find - Map params = new HashMap(); - params.put("q", query); - params.put("rows", "1"); - MapSolrParams solrParams = new MapSolrParams(params); - QueryResponse response = solr.query(solrParams); - // Make sure we at least got a document - if (response.getResults().getNumFound() == 0) - { - return currentValsStored; - } - - // We have at least one document good - SolrDocument document = response.getResults().get(0); - for (Object storedField : metadataStorageInfo.keySet()) - { - // For each of these fields that are stored we are to create a - // list of the values it holds now - java.util.Collection collection = document - .getFieldValues((String) storedField); - List storedVals = new ArrayList(); - storedVals.addAll(collection); - // Now add it to our hashmap - currentValsStored.put((String) storedField, storedVals); - } - - // System.out.println("HERE"); - // Get the info we need - } - catch (SolrServerException e) - { - e.printStackTrace(); - } - return currentValsStored; - } - - - public static class ResultProcessor - { - - public void execute(String query) throws SolrServerException, IOException { - Map params = new HashMap(); - params.put("q", query); - params.put("rows", "10"); - MapSolrParams solrParams = new MapSolrParams(params); - QueryResponse response = solr.query(solrParams); - - long numbFound = response.getResults().getNumFound(); - - // process the first batch - process(response.getResults()); - - // Run over the rest - for (int i = 10; i < numbFound; i += 10) - { - params.put("start", String.valueOf(i)); - solrParams = new MapSolrParams(params); - response = solr.query(solrParams); - process(response.getResults()); - } - - } - - public void commit() throws IOException, SolrServerException { - solr.commit(); - } - - /** - * Override to manage pages of documents - * @param docs - */ - public void process(List docs) throws IOException, SolrServerException { - for(SolrDocument doc : docs){ - process(doc); - } - } - - /** - * Override to manage individual documents - * @param doc - */ - public void process(SolrDocument doc) throws IOException, SolrServerException { - - - } - } - - - public static void markRobotsByIP() - { - for(String ip : SpiderDetector.getSpiderIpAddresses()){ - - try { - - /* Result Process to alter record to be identified as a bot */ - ResultProcessor processor = new ResultProcessor(){ - public void process(SolrDocument doc) throws IOException, SolrServerException { - doc.removeFields("isBot"); - doc.addField("isBot", true); - SolrInputDocument newInput = ClientUtils.toSolrInputDocument(doc); - solr.add(newInput); - log.info("Marked " + doc.getFieldValue("ip") + " as bot"); - } - }; - - /* query for ip, exclude results previously set as bots. */ - processor.execute("ip:"+ip+ "* AND -isBot:true"); - - solr.commit(); - - } catch (Exception e) { - log.error(e.getMessage(),e); - } - - - } - - } - - public static void markRobotByUserAgent(String agent){ - try { - - /* Result Process to alter record to be identified as a bot */ - ResultProcessor processor = new ResultProcessor(){ - public void process(SolrDocument doc) throws IOException, SolrServerException { - doc.removeFields("isBot"); - doc.addField("isBot", true); - SolrInputDocument newInput = ClientUtils.toSolrInputDocument(doc); - solr.add(newInput); - } - }; - - /* query for ip, exclude results previously set as bots. */ - processor.execute("userAgent:"+agent+ " AND -isBot:true"); - - solr.commit(); - } catch (Exception e) { - log.error(e.getMessage(),e); - } - } - - public static void deleteRobotsByIsBotFlag() - { - try { - solr.deleteByQuery("isBot:true"); - } catch (Exception e) { - log.error(e.getMessage(),e); - } - } - - public static void deleteIP(String ip) - { - try { - solr.deleteByQuery("ip:"+ip + "*"); - } catch (Exception e) { - log.error(e.getMessage(),e); - } - } - - - public static void deleteRobotsByIP() - { - for(String ip : SpiderDetector.getSpiderIpAddresses()){ - deleteIP(ip); - } - } - - /* - * //TODO: below are not used public static void - * update(String query, boolean addField, String fieldName, Object - * fieldValue, Object oldFieldValue) throws SolrServerException, IOException - * { List vals = new ArrayList(); vals.add(fieldValue); - * List oldvals = new ArrayList(); oldvals.add(fieldValue); - * update(query, addField, fieldName, vals, oldvals); } - */ - public static void update(String query, String action, - List fieldNames, List> fieldValuesList) - throws SolrServerException, IOException - { - // Since there is NO update - // We need to get our documents - // QueryResponse queryResponse = solr.query()//query(query, null, -1, - // null, null, null); - - final List docsToUpdate = new ArrayList(); - - ResultProcessor processor = new ResultProcessor(){ - public void process(List docs) throws IOException, SolrServerException { - docsToUpdate.addAll(docs); - } - }; - - processor.execute(query); - - // We have all the docs delete the ones we don't need - solr.deleteByQuery(query); - - // Add the new (updated onces - for (int i = 0; i < docsToUpdate.size(); i++) - { - SolrDocument solrDocument = docsToUpdate.get(i); - // Now loop over our fieldname actions - for (int j = 0; j < fieldNames.size(); j++) - { - String fieldName = fieldNames.get(j); - List fieldValues = fieldValuesList.get(j); - - if (action.equals("addOne") || action.equals("replace")) - { - if (action.equals("replace")) - { - solrDocument.removeFields(fieldName); - } - - for (Object fieldValue : fieldValues) - { - solrDocument.addField(fieldName, fieldValue); - } - } - else if (action.equals("remOne")) - { - // Remove the field - java.util.Collection values = solrDocument - .getFieldValues(fieldName); - solrDocument.removeFields(fieldName); - for (Object value : values) - { - // Keep all the values besides the one we need to remove - if (!fieldValues.contains((value))) - { - solrDocument.addField(fieldName, value); - } - } - } - } - SolrInputDocument newInput = ClientUtils - .toSolrInputDocument(solrDocument); - solr.add(newInput); - } - solr.commit(); - // System.out.println("SolrLogger.update(\""+query+"\"):"+(new - // Date().getTime() - start)+"ms,"+numbFound+"records"); - } - - public static void query(String query, int max) throws SolrServerException - { - query(query, null, null, max, null, null, null, null); - } - - /** - * Query used to get values grouped by the given facet field. - * - * @param query - * the query to be used - * @param facetField - * the facet field on which to group our values - * @param max - * the max number of values given back (in case of 10 the top 10 - * will be given) - * @param showTotal - * a boolean determining whether the total amount should be given - * back as the last element of the array - * @return an array containing our results - * @throws SolrServerException - * ... - */ - public static ObjectCount[] queryFacetField(String query, - String filterQuery, String facetField, int max, boolean showTotal, - List facetQueries) throws SolrServerException - { - QueryResponse queryResponse = query(query, filterQuery, facetField, - max, null, null, null, facetQueries); - if (queryResponse == null) - { - return new ObjectCount[0]; - } - - FacetField field = queryResponse.getFacetField(facetField); - // At least make sure we have one value - if (0 < field.getValueCount()) - { - // Create an array for our result - ObjectCount[] result = new ObjectCount[field.getValueCount() - + (showTotal ? 1 : 0)]; - // Run over our results & store them - for (int i = 0; i < field.getValues().size(); i++) - { - FacetField.Count fieldCount = field.getValues().get(i); - result[i] = new ObjectCount(); - result[i].setCount(fieldCount.getCount()); - result[i].setValue(fieldCount.getName()); - } - if (showTotal) - { - result[result.length - 1] = new ObjectCount(); - result[result.length - 1].setCount(queryResponse.getResults() - .getNumFound()); - result[result.length - 1].setValue("total"); - } - return result; - } - else - { - // Return an empty array cause we got no data - return new ObjectCount[0]; - } - } - - /** - * Query used to get values grouped by the date. - * - * @param query - * the query to be used - * @param max - * the max number of values given back (in case of 10 the top 10 - * will be given) - * @param dateType - * the type to be used (example: DAY, MONTH, YEAR) - * @param dateStart - * the start date Format:(-3, -2, ..) the date is calculated - * relatively on today - * @param dateEnd - * the end date stop Format (-2, +1, ..) the date is calculated - * relatively on today - * @param showTotal - * a boolean determining whether the total amount should be given - * back as the last element of the array - * @return and array containing our results - * @throws SolrServerException - * ... - */ - public static ObjectCount[] queryFacetDate(String query, - String filterQuery, int max, String dateType, String dateStart, - String dateEnd, boolean showTotal) throws SolrServerException - { - QueryResponse queryResponse = query(query, filterQuery, null, max, - dateType, dateStart, dateEnd, null); - if (queryResponse == null) - { - return new ObjectCount[0]; - } - - FacetField dateFacet = queryResponse.getFacetDate("time"); - // TODO: check if this cannot crash I checked it, it crashed!!! - // Create an array for our result - ObjectCount[] result = new ObjectCount[dateFacet.getValueCount() - + (showTotal ? 1 : 0)]; - // Run over our datefacet & store all the values - for (int i = 0; i < dateFacet.getValues().size(); i++) - { - FacetField.Count dateCount = dateFacet.getValues().get(i); - result[i] = new ObjectCount(); - result[i].setCount(dateCount.getCount()); - result[i].setValue(getDateView(dateCount.getName(), dateType)); - } - if (showTotal) - { - result[result.length - 1] = new ObjectCount(); - result[result.length - 1].setCount(queryResponse.getResults() - .getNumFound()); - // TODO: Make sure that this total is gotten out of the msgs.xml - result[result.length - 1].setValue("total"); - } - return result; - } - - public static Map queryFacetQuery(String query, - String filterQuery, List facetQueries) - throws SolrServerException - { - QueryResponse response = query(query, filterQuery, null, 1, null, null, - null, facetQueries); - return response.getFacetQuery(); - } - - public static ObjectCount queryTotal(String query, String filterQuery) - throws SolrServerException - { - QueryResponse queryResponse = query(query, filterQuery, null, -1, null, - null, null, null); - ObjectCount objCount = new ObjectCount(); - objCount.setCount(queryResponse.getResults().getNumFound()); - - return objCount; - } - - private static String getDateView(String name, String type) - { - if (name != null && name.matches("^[0-9]{4}\\-[0-9]{2}.*")) - { - /* - * if("YEAR".equalsIgnoreCase(type)) return name.substring(0, 4); - * else if("MONTH".equalsIgnoreCase(type)) return name.substring(0, - * 7); else if("DAY".equalsIgnoreCase(type)) return - * name.substring(0, 10); else if("HOUR".equalsIgnoreCase(type)) - * return name.substring(11, 13); - */ - // Get our date - Date date = null; - try - { - SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT_8601); - date = format.parse(name); - } - catch (ParseException e) - { - try - { - // We should use the dcdate (the dcdate is used when - // generating random data) - SimpleDateFormat format = new SimpleDateFormat( - DATE_FORMAT_DCDATE); - date = format.parse(name); - } - catch (ParseException e1) - { - e1.printStackTrace(); - } - // e.printStackTrace(); - } - String dateformatString = "dd-MM-yyyy"; - if ("DAY".equals(type)) - { - dateformatString = "dd-MM-yyyy"; - } - else if ("MONTH".equals(type)) - { - dateformatString = "MMMM yyyy"; - - } - else if ("YEAR".equals(type)) - { - dateformatString = "yyyy"; - } - SimpleDateFormat simpleFormat = new SimpleDateFormat( - dateformatString); - if (date != null) - { - name = simpleFormat.format(date); - } - - } - return name; - } - - private static QueryResponse query(String query, String filterQuery, - String facetField, int max, String dateType, String dateStart, - String dateEnd, List facetQueries) - throws SolrServerException - { - if (solr == null) - { - return null; - } - - // System.out.println("QUERY"); - SolrQuery solrQuery = new SolrQuery().setRows(0).setQuery(query) - .setFacetMinCount(1); - - // Set the date facet if present - if (dateType != null) - { - solrQuery.setParam("facet.date", "time") - . - // EXAMPLE: NOW/MONTH+1MONTH - setParam("facet.date.end", - "NOW/" + dateType + dateEnd + dateType).setParam( - "facet.date.gap", "+1" + dateType) - . - // EXAMPLE: NOW/MONTH-" + nbMonths + "MONTHS - setParam("facet.date.start", - "NOW/" + dateType + dateStart + dateType + "S") - .setFacet(true); - } - if (facetQueries != null) - { - for (int i = 0; i < facetQueries.size(); i++) - { - String facetQuery = facetQueries.get(i); - solrQuery.addFacetQuery(facetQuery); - } - if (0 < facetQueries.size()) - { - solrQuery.setFacet(true); - } - } - - if (facetField != null) - { - solrQuery.addFacetField(facetField); - } - - // Set the top x of if present - if (max != -1) - { - solrQuery.setFacetLimit(max); - } - - // A filter is used instead of a regular query to improve - // performance and ensure the search result ordering will - // not be influenced - - // Choose to filter by the Legacy spider IP list (may get too long to properly filter all IP's - if(ConfigurationManager.getBooleanProperty("solr-statistics", "query.filter.spiderIp",false)) - { - solrQuery.addFilterQuery(getIgnoreSpiderIPs()); - } - - // Choose to filter by isBot field, may be overriden in future - // to allow views on stats based on bots. - if(ConfigurationManager.getBooleanProperty("solr-statistics", "query.filter.isBot",true)) - { - solrQuery.addFilterQuery("-isBot:true"); - } - - String bundles; - if((bundles = ConfigurationManager.getProperty("solr-statistics", "query.filter.bundles")) != null && 0 < bundles.length()){ - - /** - * The code below creates a query that will allow only records which do not have a bundlename - * (items, collections, ...) or bitstreams that have a configured bundle name - */ - StringBuffer bundleQuery = new StringBuffer(); - //Also add the possibility that if no bundle name is there these results will also be returned ! - bundleQuery.append("-(bundleName:[* TO *]"); - String[] split = bundles.split(","); - for (int i = 0; i < split.length; i++) { - String bundle = split[i].trim(); - bundleQuery.append("-bundleName:").append(bundle); - if(i != split.length - 1){ - bundleQuery.append(" AND "); - } - } - bundleQuery.append(")"); - - - solrQuery.addFilterQuery(bundleQuery.toString()); - } - - if (filterQuery != null) - { - solrQuery.addFilterQuery(filterQuery); - } - - QueryResponse response; - try - { - // solr.set - response = solr.query(solrQuery); - } - catch (SolrServerException e) - { - System.err.println("Error using query " + query); - throw e; - } - return response; - } - - - /** String of IP and Ranges in IPTable as a Solr Query */ - private static String filterQuery = null; - - /** - * Returns in a filterQuery string all the ip addresses that should be ignored - * - * @return a string query with ip addresses - */ - public static String getIgnoreSpiderIPs() { - if (filterQuery == null) { - StringBuilder query = new StringBuilder(); - boolean first = true; - for (String ip : SpiderDetector.getSpiderIpAddresses()) { - if (first) { - query.append(" AND "); - first = false; - } - - query.append(" NOT(ip: ").append(ip).append(")"); - } - filterQuery = query.toString(); - } - - return filterQuery; - - } - - /** - * Maintenance to keep a SOLR index efficient. - * Note: This might take a long time. - */ - public static void optimizeSOLR() { - try { - long start = System.currentTimeMillis(); - System.out.println("SOLR Optimize -- Process Started:"+start); - solr.optimize(); - long finish = System.currentTimeMillis(); - System.out.println("SOLR Optimize -- Process Finished:"+finish); - System.out.println("SOLR Optimize -- Total time taken:"+(finish-start) + " (ms)."); - } catch (SolrServerException sse) { - System.err.println(sse.getMessage()); - } catch (IOException ioe) { - System.err.println(ioe.getMessage()); - } - } - - public static void reindexBitstreamHits(boolean removeDeletedBitstreams) throws Exception { - Context context = new Context(); - - try { - //First of all retrieve the total number of records to be updated - SolrQuery query = new SolrQuery(); - query.setQuery("*:*"); - query.addFilterQuery("type:" + Constants.BITSTREAM); - //Only retrieve records which do not have a bundle name - query.addFilterQuery("-bundleName:[* TO *]"); - query.setRows(0); - long totalRecords = solr.query(query).getResults().getNumFound(); - - File tempDirectory = new File(ConfigurationManager.getProperty("dspace.dir") + File.separator + "temp" + File.separator); - tempDirectory.mkdirs(); - List tempCsvFiles = new ArrayList(); - for(int i = 0; i < totalRecords; i+=10000){ - Map params = new HashMap(); - params.put(CommonParams.Q, "*:*"); - params.put(CommonParams.FQ, "-bundleName:[* TO *] AND type:" + Constants.BITSTREAM); - params.put(CommonParams.WT, "csv"); - params.put(CommonParams.ROWS, String.valueOf(10000)); - params.put(CommonParams.START, String.valueOf(i)); - - String solrRequestUrl = solr.getBaseURL() + "/select"; - solrRequestUrl = generateURL(solrRequestUrl, params); - - GetMethod get = new GetMethod(solrRequestUrl); - new HttpClient().executeMethod(get); - - InputStream csvOutput = get.getResponseBodyAsStream(); - Reader csvReader = new InputStreamReader(csvOutput); - String[][] csvParsed = CSVParser.parse(csvReader); - String[] header = csvParsed[0]; - //Attempt to find the bitstream id index ! - int idIndex = 0; - for (int j = 0; j < header.length; j++) { - if(header[j].equals("id")){ - idIndex = j; - } - } - - File tempCsv = new File(tempDirectory.getPath() + File.separatorChar + "temp." + i + ".csv"); - tempCsvFiles.add(tempCsv); - FileOutputStream outputStream = new FileOutputStream(tempCsv); - CSVPrinter csvp = new CSVPrinter(outputStream); - csvp.setAlwaysQuote(false); - - //Write the header ! - csvp.write(header); - csvp.write("bundleName"); - csvp.writeln(); - Map bitBundleCache = new HashMap(); - //Loop over each line (skip the headers though)! - for (int j = 1; j < csvParsed.length; j++){ - String[] csvLine = csvParsed[j]; - //Write the default line ! - int bitstreamId = Integer.parseInt(csvLine[idIndex]); - //Attempt to retrieve our bundle name from the cache ! - String bundleName = bitBundleCache.get(bitstreamId); - if(bundleName == null){ - //Nothing found retrieve the bitstream - Bitstream bitstream = Bitstream.find(context, bitstreamId); - //Attempt to retrieve our bitstream ! - if (bitstream != null){ - Bundle[] bundles = bitstream.getBundles(); - if(bundles != null && 0 < bundles.length){ - Bundle bundle = bundles[0]; - bundleName = bundle.getName(); - context.removeCached(bundle, bundle.getID()); - }else{ - //No bundle found, we are either a collection or a community logo, check for it ! - DSpaceObject parentObject = bitstream.getParentObject(); - if(parentObject instanceof Collection){ - bundleName = "LOGO-COLLECTION"; - }else - if(parentObject instanceof Community){ - bundleName = "LOGO-COMMUNITY"; - } - if(parentObject != null){ - context.removeCached(parentObject, parentObject.getID()); - } - - } - //Cache the bundle name - bitBundleCache.put(bitstream.getID(), bundleName); - //Remove the bitstream from cache - context.removeCached(bitstream, bitstreamId); - } - //Check if we don't have a bundlename - //If we don't have one & we do not need to delete the deleted bitstreams ensure that a BITSTREAM_DELETED bundle name is given ! - if(bundleName == null && !removeDeletedBitstreams){ - bundleName = "BITSTREAM_DELETED"; - } - } - csvp.write(csvLine); - csvp.write(bundleName); - csvp.writeln(); - } - - //Loop over our parsed csv - csvp.flush(); - csvp.close(); - } - - //Add all the separate csv files - for (File tempCsv : tempCsvFiles) { - ContentStreamUpdateRequest contentStreamUpdateRequest = new ContentStreamUpdateRequest("/update/csv"); - contentStreamUpdateRequest.setParam("stream.contentType", "text/plain;charset=utf-8"); - contentStreamUpdateRequest.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true); - contentStreamUpdateRequest.addFile(tempCsv); - - solr.request(contentStreamUpdateRequest); - } - - //Now that all our new bitstream stats are in place, delete all the old ones ! - solr.deleteByQuery("-bundleName:[* TO *] AND type:" + Constants.BITSTREAM); - //Commit everything to wrap up - solr.commit(true, true); - //Clean up our directory ! - FileUtils.deleteDirectory(tempDirectory); - } catch (Exception e) { - log.error("Error while updating the bitstream statistics", e); - throw e; - } finally { - context.abort(); - } - } - - private static String generateURL(String baseURL, Map parameters) throws UnsupportedEncodingException { - boolean first = true; - StringBuilder result = new StringBuilder(baseURL); - for (String key : parameters.keySet()) - { - if (first) - { - result.append("?"); - first = false; - } - else - { - result.append("&"); - } - - result.append(key).append("=").append(URLEncoder.encode(parameters.get(key), "UTF-8")); - } - - return result.toString(); - } -} - +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.statistics; + +import com.Ostermiller.util.CSVParser; +import com.Ostermiller.util.CSVPrinter; +import com.maxmind.geoip.Location; +import com.maxmind.geoip.LookupService; + +import java.io.*; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.time.DateFormatUtils; +import org.apache.log4j.Logger; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer; +import org.apache.solr.client.solrj.request.AbstractUpdateRequest; +import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest; +import org.apache.solr.client.solrj.request.CoreAdminRequest; +import org.apache.solr.client.solrj.response.FacetField; +import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.client.solrj.response.RangeFacet; +import org.apache.solr.client.solrj.util.ClientUtils; +import org.apache.solr.common.SolrDocument; +import org.apache.solr.common.SolrInputDocument; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.common.params.FacetParams; +import org.apache.solr.common.params.MapSolrParams; +import org.apache.solr.common.params.ShardParams; +import org.dspace.content.*; +import org.dspace.content.Collection; +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.statistics.util.DnsLookup; +import org.dspace.statistics.util.LocationUtils; +import org.dspace.statistics.util.SpiderDetector; +import org.dspace.usage.UsageWorkflowEvent; + +import javax.servlet.http.HttpServletRequest; +import java.net.URLEncoder; +import java.sql.SQLException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Static holder for a HttpSolrClient connection pool to issue + * usage logging events to Solr from DSpace libraries, and some static query + * composers. + * + * @author ben at atmire.com + * @author kevinvandevelde at atmire.com + * @author mdiggory at atmire.com + */ +public class SolrLogger +{ + private static final Logger log = Logger.getLogger(SolrLogger.class); + + private static final CommonsHttpSolrServer solr; + + public static final String DATE_FORMAT_8601 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + + public static final String DATE_FORMAT_DCDATE = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + + private static final LookupService locationService; + + private static final boolean useProxies; + + private static Map metadataStorageInfo; + + private static List statisticYearCores = new ArrayList(); + + public static enum StatisticsType { + VIEW ("view"), + SEARCH ("search"), + SEARCH_RESULT ("search_result"), + WORKFLOW("workflow"); + + private final String text; + + StatisticsType(String text) { + this.text = text; + } + public String text() { return text; } + } + + + static + { + log.info("solr-statistics.spidersfile:" + ConfigurationManager.getProperty("solr-statistics", "spidersfile")); + log.info("solr-statistics.server:" + ConfigurationManager.getProperty("solr-statistics", "server")); + log.info("usage-statistics.dbfile:" + ConfigurationManager.getProperty("usage-statistics", "dbfile")); + + CommonsHttpSolrServer server = null; + + if (ConfigurationManager.getProperty("solr-statistics", "server") != null) + { + try + { + server = new CommonsHttpSolrServer(ConfigurationManager.getProperty("solr-statistics", "server")); + SolrQuery solrQuery = new SolrQuery() + .setQuery("type:2 AND id:1"); + server.query(solrQuery); + + //Attempt to retrieve all the statistic year cores + File solrDir = new File(ConfigurationManager.getProperty("dspace.dir") + "/solr/"); + File[] solrCoreFiles = solrDir.listFiles(new FileFilter() { + + @Override + public boolean accept(File file) { + //Core name example: statistics-2008 + return file.getName().matches("statistics-\\d\\d\\d\\d"); + } + }); + //Base url should like : http://localhost:{port.number}/solr + String baseSolrUrl = server.getBaseURL().replace("statistics", ""); + for (File solrCoreFile : solrCoreFiles) { + log.info("Loading core with name: " + solrCoreFile.getName()); + + createCore(server, solrCoreFile.getName()); + //Add it to our cores list so we can query it ! + statisticYearCores.add(baseSolrUrl.replace("http://", "").replace("https://", "") + solrCoreFile.getName()); + } + //Also add the core containing the current year ! + statisticYearCores.add(server.getBaseURL().replace("http://", "").replace("https://", "")); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + solr = server; + + // Read in the file so we don't have to do it all the time + //spiderIps = SpiderDetector.getSpiderIpAddresses(); + + LookupService service = null; + // Get the db file for the location + String dbfile = ConfigurationManager.getProperty("solr-statistics", "dbfile"); + if (dbfile != null) + { + try + { + service = new LookupService(dbfile, + LookupService.GEOIP_STANDARD); + } + catch (FileNotFoundException fe) + { + log.error("The GeoLite Database file is missing (" + dbfile + ")! Solr Statistics cannot generate location based reports! Please see the DSpace installation instructions for instructions to install this file.", fe); + } + catch (IOException e) + { + log.error("Unable to load GeoLite Database file (" + dbfile + ")! You may need to reinstall it. See the DSpace installation instructions for more details.", e); + } + } + else + { + log.error("The required 'dbfile' configuration is missing in solr-statistics.cfg!"); + } + locationService = service; + + if ("true".equals(ConfigurationManager.getProperty("useProxies"))) + { + useProxies = true; + } + else + { + useProxies = false; + } + + log.info("useProxies=" + useProxies); + + metadataStorageInfo = new HashMap(); + int count = 1; + String metadataVal; + while ((metadataVal = ConfigurationManager.getProperty("solr-statistics","metadata.item." + count)) != null) + { + String storeVal = metadataVal.split(":")[0]; + String metadataField = metadataVal.split(":")[1]; + + metadataStorageInfo.put(storeVal, metadataField); + log.info("solr-statistics.metadata.item." + count + "=" + metadataVal); + count++; + } + } + + /** + * Old post method, use the new postview method instead ! + * + * @deprecated + * @param dspaceObject the object used. + * @param request the current request context. + * @param currentUser the current session's user. + */ + public static void post(DSpaceObject dspaceObject, HttpServletRequest request, + EPerson currentUser) + { + postView(dspaceObject, request, currentUser); + } + + /** + * Store a usage event into Solr. + * + * @param dspaceObject the object used. + * @param request the current request context. + * @param currentUser the current session's user. + */ + public static void postView(DSpaceObject dspaceObject, HttpServletRequest request, + EPerson currentUser) + { + if (solr == null || locationService == null) + { + return; + } + + + try + { + SolrInputDocument doc1 = getCommonSolrDoc(dspaceObject, request, currentUser); + if (doc1 == null) return; + + if (dspaceObject instanceof Item) + { + Item item = (Item) dspaceObject; + // Store the metadata + for (Object storedField : metadataStorageInfo.keySet()) + { + String dcField = metadataStorageInfo + .get(storedField); + + DCValue[] vals = item.getMetadata(dcField.split("\\.")[0], + dcField.split("\\.")[1], dcField.split("\\.")[2], + Item.ANY); + for (DCValue val1 : vals) + { + String val = val1.value; + doc1.addField(String.valueOf(storedField), val); + doc1.addField(storedField + "_search", val + .toLowerCase()); + } + } + } + + if(dspaceObject instanceof Bitstream) + { + Bitstream bit = (Bitstream) dspaceObject; + Bundle[] bundles = bit.getBundles(); + for (Bundle bundle : bundles) { + doc1.addField("bundleName", bundle.getName()); + } + } + + doc1.addField("statistics_type", StatisticsType.VIEW.text()); + + + solr.add(doc1); + //commits are executed automatically using the solr autocommit +// solr.commit(false, false); + + } + catch (RuntimeException re) + { + throw re; + } + catch (Exception e) + { + log.error(e.getMessage(), e); + } + } + + /** + * Returns a solr input document containing common information about the statistics + * regardless if we are logging a search or a view of a DSpace object + * @param dspaceObject the object used. + * @param request the current request context. + * @param currentUser the current session's user. + * @return a solr input document + * @throws SQLException in case of a database exception + */ + private static SolrInputDocument getCommonSolrDoc(DSpaceObject dspaceObject, HttpServletRequest request, EPerson currentUser) throws SQLException { + boolean isSpiderBot = request != null && SpiderDetector.isSpider(request); + if(isSpiderBot && + !ConfigurationManager.getBooleanProperty("usage-statistics", "logBots", true)) + { + return null; + } + + SolrInputDocument doc1 = new SolrInputDocument(); + // Save our basic info that we already have + + if(request != null){ + String ip = request.getRemoteAddr(); + + if (isUseProxies() && request.getHeader("X-Forwarded-For") != null) { + /* This header is a comma delimited list */ + for (String xfip : request.getHeader("X-Forwarded-For").split(",")) { + /* proxy itself will sometime populate this header with the same value in + remote address. ordering in spec is vague, we'll just take the last + not equal to the proxy + */ + if (!request.getHeader("X-Forwarded-For").contains(ip)) { + ip = xfip.trim(); + } + } + } + + doc1.addField("ip", ip); + + //Also store the referrer + if(request.getHeader("referer") != null){ + doc1.addField("referrer", request.getHeader("referer")); + } + + try + { + String dns = DnsLookup.reverseDns(ip); + doc1.addField("dns", dns.toLowerCase()); + } + catch (Exception e) + { + log.error("Failed DNS Lookup for IP:" + ip); + log.debug(e.getMessage(),e); + } + + // Save the location information if valid, save the event without + // location information if not valid + Location location = locationService.getLocation(ip); + if (location != null + && !("--".equals(location.countryCode) + && location.latitude == -180 && location.longitude == -180)) + { + try + { + doc1.addField("continent", LocationUtils + .getContinentCode(location.countryCode)); + } + catch (Exception e) + { + System.out + .println("COUNTRY ERROR: " + location.countryCode); + } + doc1.addField("countryCode", location.countryCode); + doc1.addField("city", location.city); + doc1.addField("latitude", location.latitude); + doc1.addField("longitude", location.longitude); + doc1.addField("isBot",isSpiderBot); + + if(request.getHeader("User-Agent") != null) + { + doc1.addField("userAgent", request.getHeader("User-Agent")); + } + } + } + + if(dspaceObject != null){ + doc1.addField("id", dspaceObject.getID()); + doc1.addField("type", dspaceObject.getType()); + storeParents(doc1, dspaceObject); + } + // Save the current time + doc1.addField("time", DateFormatUtils.format(new Date(), DATE_FORMAT_8601)); + if (currentUser != null) + { + doc1.addField("epersonid", currentUser.getID()); + } + + return doc1; + } + + public static void postSearch(DSpaceObject resultObject, HttpServletRequest request, EPerson currentUser, + List queries, int rpp, String sortBy, String order, int page, DSpaceObject scope) { + try + { + SolrInputDocument solrDoc = getCommonSolrDoc(resultObject, request, currentUser); + if (solrDoc == null) return; + + for (String query : queries) { + solrDoc.addField("query", query); + } + + if(resultObject != null){ + //We have a search result + solrDoc.addField("statistics_type", StatisticsType.SEARCH_RESULT.text()); + }else{ + solrDoc.addField("statistics_type", StatisticsType.SEARCH.text()); + } + //Store the scope + if(scope != null){ + solrDoc.addField("scopeId", scope.getType()); + solrDoc.addField("scopeType", scope.getID()); + } + + if(rpp != -1){ + solrDoc.addField("rpp", rpp); + } + + if(sortBy != null){ + solrDoc.addField("sortBy", sortBy); + if(order != null){ + solrDoc.addField("sortOrder", order); + } + } + + if(page != -1){ + solrDoc.addField("page", page); + } + + solr.add(solrDoc); + } + catch (RuntimeException re) + { + throw re; + } + catch (Exception e) + { + log.error(e.getMessage(), e); + } + } + + public static void postWorkflow(UsageWorkflowEvent usageWorkflowEvent) throws SQLException { + try { + SolrInputDocument solrDoc = getCommonSolrDoc(usageWorkflowEvent.getObject(), null, null); + + //Log the current collection & the scope ! + solrDoc.addField("owningColl", usageWorkflowEvent.getScope().getID()); + storeParents(solrDoc, usageWorkflowEvent.getScope()); + + if(usageWorkflowEvent.getWorkflowStep() != null){ + solrDoc.addField("workflowStep", usageWorkflowEvent.getWorkflowStep()); + } + if(usageWorkflowEvent.getOldState() != null){ + solrDoc.addField("previousWorkflowStep", usageWorkflowEvent.getOldState()); + } + if(usageWorkflowEvent.getGroupOwners() != null){ + for (int i = 0; i < usageWorkflowEvent.getGroupOwners().length; i++) { + Group group = usageWorkflowEvent.getGroupOwners()[i]; + solrDoc.addField("owner", "g" + group.getID()); + } + } + if(usageWorkflowEvent.getEpersonOwners() != null){ + for (int i = 0; i < usageWorkflowEvent.getEpersonOwners().length; i++) { + EPerson ePerson = usageWorkflowEvent.getEpersonOwners()[i]; + solrDoc.addField("owner", "e" + ePerson.getID()); + } + } + + solrDoc.addField("workflowItemId", usageWorkflowEvent.getWorkflowItem().getID()); + + EPerson submitter = ((Item) usageWorkflowEvent.getObject()).getSubmitter(); + if(submitter != null){ + solrDoc.addField("submitter", submitter.getID()); + } + solrDoc.addField("statistics_type", StatisticsType.WORKFLOW.text()); + if(usageWorkflowEvent.getActor() != null){ + solrDoc.addField("actor", usageWorkflowEvent.getActor().getID()); + } + + solr.add(solrDoc); + } + catch (Exception e) + { + //Log the exception, no need to send it through, the workflow shouldn't crash because of this ! + log.error(e.getMessage(), e); + } + + } + + public static Map getMetadataStorageInfo() + { + return metadataStorageInfo; + } + + /** + * Method just used to log the parents. + *
    + *
  • Community log: owning comms.
  • + *
  • Collection log: owning comms & their comms.
  • + *
  • Item log: owning colls/comms.
  • + *
  • Bitstream log: owning item/colls/comms.
  • + *
+ * + * @param doc1 + * the current SolrInputDocument + * @param dso + * the current dspace object we want to log + * @throws java.sql.SQLException + * ignore it + */ + public static void storeParents(SolrInputDocument doc1, DSpaceObject dso) + throws SQLException + { + if (dso instanceof Community) + { + Community comm = (Community) dso; + while (comm != null && comm.getParentCommunity() != null) + { + comm = comm.getParentCommunity(); + doc1.addField("owningComm", comm.getID()); + } + } + else if (dso instanceof Collection) + { + Collection coll = (Collection) dso; + for (int i = 0; i < coll.getCommunities().length; i++) + { + Community community = coll.getCommunities()[i]; + doc1.addField("owningComm", community.getID()); + storeParents(doc1, community); + } + } + else if (dso instanceof Item) + { + Item item = (Item) dso; + for (int i = 0; i < item.getCollections().length; i++) + { + Collection collection = item.getCollections()[i]; + doc1.addField("owningColl", collection.getID()); + storeParents(doc1, collection); + } + } + else if (dso instanceof Bitstream) + { + Bitstream bitstream = (Bitstream) dso; + for (int i = 0; i < bitstream.getBundles().length; i++) + { + Bundle bundle = bitstream.getBundles()[i]; + for (int j = 0; j < bundle.getItems().length; j++) + { + Item item = bundle.getItems()[j]; + doc1.addField("owningItem", item.getID()); + storeParents(doc1, item); + } + } + } + } + + public static boolean isUseProxies() + { + return useProxies; + } + + /** + * Delete data from the index, as described by a query. + * + * @param query description of the records to be deleted. + * @throws IOException + * @throws SolrServerException + */ + public static void removeIndex(String query) throws IOException, + SolrServerException + { + solr.deleteByQuery(query); + solr.commit(); + } + + public static Map> queryField(String query, + List oldFieldVals, String field) + { + Map> currentValsStored = new HashMap>(); + try + { + // Get one document (since all the metadata for all the values + // should be the same just get the first one we find + Map params = new HashMap(); + params.put("q", query); + params.put("rows", "1"); + MapSolrParams solrParams = new MapSolrParams(params); + QueryResponse response = solr.query(solrParams); + // Make sure we at least got a document + if (response.getResults().getNumFound() == 0) + { + return currentValsStored; + } + + // We have at least one document good + SolrDocument document = response.getResults().get(0); + for (Object storedField : metadataStorageInfo.keySet()) + { + // For each of these fields that are stored we are to create a + // list of the values it holds now + java.util.Collection collection = document + .getFieldValues((String) storedField); + List storedVals = new ArrayList(); + storedVals.addAll(collection); + // Now add it to our hashmap + currentValsStored.put((String) storedField, storedVals); + } + + // System.out.println("HERE"); + // Get the info we need + } + catch (SolrServerException e) + { + e.printStackTrace(); + } + return currentValsStored; + } + + + public static class ResultProcessor + { + + public void execute(String query) throws SolrServerException, IOException { + Map params = new HashMap(); + params.put("q", query); + params.put("rows", "10"); + if(0 < statisticYearCores.size()){ + params.put(ShardParams.SHARDS, StringUtils.join(statisticYearCores.iterator(), ',')); + } + MapSolrParams solrParams = new MapSolrParams(params); + QueryResponse response = solr.query(solrParams); + + long numbFound = response.getResults().getNumFound(); + + // process the first batch + process(response.getResults()); + + // Run over the rest + for (int i = 10; i < numbFound; i += 10) + { + params.put("start", String.valueOf(i)); + solrParams = new MapSolrParams(params); + response = solr.query(solrParams); + process(response.getResults()); + } + + } + + public void commit() throws IOException, SolrServerException { + solr.commit(); + } + + /** + * Override to manage pages of documents + * @param docs + */ + public void process(List docs) throws IOException, SolrServerException { + for(SolrDocument doc : docs){ + process(doc); + } + } + + /** + * Override to manage individual documents + * @param doc + */ + public void process(SolrDocument doc) throws IOException, SolrServerException { + + + } + } + + + public static void markRobotsByIP() + { + for(String ip : SpiderDetector.getSpiderIpAddresses()){ + + try { + + /* Result Process to alter record to be identified as a bot */ + ResultProcessor processor = new ResultProcessor(){ + public void process(SolrDocument doc) throws IOException, SolrServerException { + doc.removeFields("isBot"); + doc.addField("isBot", true); + SolrInputDocument newInput = ClientUtils.toSolrInputDocument(doc); + solr.add(newInput); + log.info("Marked " + doc.getFieldValue("ip") + " as bot"); + } + }; + + /* query for ip, exclude results previously set as bots. */ + processor.execute("ip:"+ip+ "* AND -isBot:true"); + + solr.commit(); + + } catch (Exception e) { + log.error(e.getMessage(),e); + } + + + } + + } + + public static void markRobotByUserAgent(String agent){ + try { + + /* Result Process to alter record to be identified as a bot */ + ResultProcessor processor = new ResultProcessor(){ + public void process(SolrDocument doc) throws IOException, SolrServerException { + doc.removeFields("isBot"); + doc.addField("isBot", true); + SolrInputDocument newInput = ClientUtils.toSolrInputDocument(doc); + solr.add(newInput); + } + }; + + /* query for ip, exclude results previously set as bots. */ + processor.execute("userAgent:"+agent+ " AND -isBot:true"); + + solr.commit(); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + } + + public static void deleteRobotsByIsBotFlag() + { + try { + solr.deleteByQuery("isBot:true"); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + } + + public static void deleteIP(String ip) + { + try { + solr.deleteByQuery("ip:"+ip + "*"); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + } + + + public static void deleteRobotsByIP() + { + for(String ip : SpiderDetector.getSpiderIpAddresses()){ + deleteIP(ip); + } + } + + /* + * //TODO: below are not used public static void + * update(String query, boolean addField, String fieldName, Object + * fieldValue, Object oldFieldValue) throws SolrServerException, IOException + * { List vals = new ArrayList(); vals.add(fieldValue); + * List oldvals = new ArrayList(); oldvals.add(fieldValue); + * update(query, addField, fieldName, vals, oldvals); } + */ + public static void update(String query, String action, + List fieldNames, List> fieldValuesList) + throws SolrServerException, IOException + { + // Since there is NO update + // We need to get our documents + // QueryResponse queryResponse = solr.query()//query(query, null, -1, + // null, null, null); + + final List docsToUpdate = new ArrayList(); + + ResultProcessor processor = new ResultProcessor(){ + public void process(List docs) throws IOException, SolrServerException { + docsToUpdate.addAll(docs); + } + }; + + processor.execute(query); + + // We have all the docs delete the ones we don't need + solr.deleteByQuery(query); + + // Add the new (updated onces + for (int i = 0; i < docsToUpdate.size(); i++) + { + SolrDocument solrDocument = docsToUpdate.get(i); + // Now loop over our fieldname actions + for (int j = 0; j < fieldNames.size(); j++) + { + String fieldName = fieldNames.get(j); + List fieldValues = fieldValuesList.get(j); + + if (action.equals("addOne") || action.equals("replace")) + { + if (action.equals("replace")) + { + solrDocument.removeFields(fieldName); + } + + for (Object fieldValue : fieldValues) + { + solrDocument.addField(fieldName, fieldValue); + } + } + else if (action.equals("remOne")) + { + // Remove the field + java.util.Collection values = solrDocument + .getFieldValues(fieldName); + solrDocument.removeFields(fieldName); + for (Object value : values) + { + // Keep all the values besides the one we need to remove + if (!fieldValues.contains((value))) + { + solrDocument.addField(fieldName, value); + } + } + } + } + SolrInputDocument newInput = ClientUtils + .toSolrInputDocument(solrDocument); + solr.add(newInput); + } + solr.commit(); + // System.out.println("SolrLogger.update(\""+query+"\"):"+(new + // Date().getTime() - start)+"ms,"+numbFound+"records"); + } + + public static void query(String query, int max) throws SolrServerException + { + query(query, null, null,0, max, null, null, null, null, null, false); + } + + /** + * Query used to get values grouped by the given facet field. + * + * @param query + * the query to be used + * @param facetField + * the facet field on which to group our values + * @param max + * the max number of values given back (in case of 10 the top 10 + * will be given) + * @param showTotal + * a boolean determining whether the total amount should be given + * back as the last element of the array + * @return an array containing our results + * @throws SolrServerException + * ... + */ + public static ObjectCount[] queryFacetField(String query, + String filterQuery, String facetField, int max, boolean showTotal, + List facetQueries) throws SolrServerException + { + QueryResponse queryResponse = query(query, filterQuery, facetField, + 0,max, null, null, null, facetQueries, null, false); + if (queryResponse == null) + { + return new ObjectCount[0]; + } + + FacetField field = queryResponse.getFacetField(facetField); + // At least make sure we have one value + if (0 < field.getValueCount()) + { + // Create an array for our result + ObjectCount[] result = new ObjectCount[field.getValueCount() + + (showTotal ? 1 : 0)]; + // Run over our results & store them + for (int i = 0; i < field.getValues().size(); i++) + { + FacetField.Count fieldCount = field.getValues().get(i); + result[i] = new ObjectCount(); + result[i].setCount(fieldCount.getCount()); + result[i].setValue(fieldCount.getName()); + } + if (showTotal) + { + result[result.length - 1] = new ObjectCount(); + result[result.length - 1].setCount(queryResponse.getResults() + .getNumFound()); + result[result.length - 1].setValue("total"); + } + return result; + } + else + { + // Return an empty array cause we got no data + return new ObjectCount[0]; + } + } + + /** + * Query used to get values grouped by the date. + * + * @param query + * the query to be used + * @param max + * the max number of values given back (in case of 10 the top 10 + * will be given) + * @param dateType + * the type to be used (example: DAY, MONTH, YEAR) + * @param dateStart + * the start date Format:(-3, -2, ..) the date is calculated + * relatively on today + * @param dateEnd + * the end date stop Format (-2, +1, ..) the date is calculated + * relatively on today + * @param showTotal + * a boolean determining whether the total amount should be given + * back as the last element of the array + * @return and array containing our results + * @throws SolrServerException + * ... + */ + public static ObjectCount[] queryFacetDate(String query, + String filterQuery, int max, String dateType, String dateStart, + String dateEnd, boolean showTotal) throws SolrServerException + { + QueryResponse queryResponse = query(query, filterQuery, null, 0, max, + dateType, dateStart, dateEnd, null, null, false); + if (queryResponse == null) + { + return new ObjectCount[0]; + } + + FacetField dateFacet = queryResponse.getFacetDate("time"); + // TODO: check if this cannot crash I checked it, it crashed!!! + // Create an array for our result + ObjectCount[] result = new ObjectCount[dateFacet.getValueCount() + + (showTotal ? 1 : 0)]; + // Run over our datefacet & store all the values + for (int i = 0; i < dateFacet.getValues().size(); i++) + { + FacetField.Count dateCount = dateFacet.getValues().get(i); + result[i] = new ObjectCount(); + result[i].setCount(dateCount.getCount()); + result[i].setValue(getDateView(dateCount.getName(), dateType)); + } + if (showTotal) + { + result[result.length - 1] = new ObjectCount(); + result[result.length - 1].setCount(queryResponse.getResults() + .getNumFound()); + // TODO: Make sure that this total is gotten out of the msgs.xml + result[result.length - 1].setValue("total"); + } + return result; + } + + public static Map queryFacetQuery(String query, + String filterQuery, List facetQueries) + throws SolrServerException + { + QueryResponse response = query(query, filterQuery, null,0, 1, null, null, + null, facetQueries, null, false); + return response.getFacetQuery(); + } + + public static ObjectCount queryTotal(String query, String filterQuery) + throws SolrServerException + { + QueryResponse queryResponse = query(query, filterQuery, null,0, -1, null, + null, null, null, null, false); + ObjectCount objCount = new ObjectCount(); + objCount.setCount(queryResponse.getResults().getNumFound()); + + return objCount; + } + + private static String getDateView(String name, String type) + { + if (name != null && name.matches("^[0-9]{4}\\-[0-9]{2}.*")) + { + /* + * if("YEAR".equalsIgnoreCase(type)) return name.substring(0, 4); + * else if("MONTH".equalsIgnoreCase(type)) return name.substring(0, + * 7); else if("DAY".equalsIgnoreCase(type)) return + * name.substring(0, 10); else if("HOUR".equalsIgnoreCase(type)) + * return name.substring(11, 13); + */ + // Get our date + Date date = null; + try + { + SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT_8601); + date = format.parse(name); + } + catch (ParseException e) + { + try + { + // We should use the dcdate (the dcdate is used when + // generating random data) + SimpleDateFormat format = new SimpleDateFormat( + DATE_FORMAT_DCDATE); + date = format.parse(name); + } + catch (ParseException e1) + { + e1.printStackTrace(); + } + // e.printStackTrace(); + } + String dateformatString = "dd-MM-yyyy"; + if ("DAY".equals(type)) + { + dateformatString = "dd-MM-yyyy"; + } + else if ("MONTH".equals(type)) + { + dateformatString = "MMMM yyyy"; + + } + else if ("YEAR".equals(type)) + { + dateformatString = "yyyy"; + } + SimpleDateFormat simpleFormat = new SimpleDateFormat( + dateformatString); + if (date != null) + { + name = simpleFormat.format(date); + } + + } + return name; + } + + public static QueryResponse query(String query, String filterQuery, + String facetField, int rows, int max, String dateType, String dateStart, + String dateEnd, List facetQueries, String sort, boolean ascending) + throws SolrServerException + { + if (solr == null) + { + return null; + } + + // System.out.println("QUERY"); + SolrQuery solrQuery = new SolrQuery().setRows(rows).setQuery(query) + .setFacetMinCount(1); + addAdditionalSolrYearCores(solrQuery); + + // Set the date facet if present + if (dateType != null) + { + solrQuery.setParam("facet.date", "time") + . + // EXAMPLE: NOW/MONTH+1MONTH + setParam("facet.date.end", + "NOW/" + dateType + dateEnd + dateType).setParam( + "facet.date.gap", "+1" + dateType) + . + // EXAMPLE: NOW/MONTH-" + nbMonths + "MONTHS + setParam("facet.date.start", + "NOW/" + dateType + dateStart + dateType + "S") + .setFacet(true); + } + if (facetQueries != null) + { + for (int i = 0; i < facetQueries.size(); i++) + { + String facetQuery = facetQueries.get(i); + solrQuery.addFacetQuery(facetQuery); + } + if (0 < facetQueries.size()) + { + solrQuery.setFacet(true); + } + } + + if (facetField != null) + { + solrQuery.addFacetField(facetField); + } + + // Set the top x of if present + if (max != -1) + { + solrQuery.setFacetLimit(max); + } + + // A filter is used instead of a regular query to improve + // performance and ensure the search result ordering will + // not be influenced + + // Choose to filter by the Legacy spider IP list (may get too long to properly filter all IP's + if(ConfigurationManager.getBooleanProperty("solr-statistics", "query.filter.spiderIp",false)) + { + solrQuery.addFilterQuery(getIgnoreSpiderIPs()); + } + + // Choose to filter by isBot field, may be overriden in future + // to allow views on stats based on bots. + if(ConfigurationManager.getBooleanProperty("solr-statistics", "query.filter.isBot",true)) + { + solrQuery.addFilterQuery("-isBot:true"); + } + + if(sort != null){ + solrQuery.setSortField(sort, (ascending ? SolrQuery.ORDER.asc : SolrQuery.ORDER.desc)); + } + + String bundles; + if((bundles = ConfigurationManager.getProperty("solr-statistics", "query.filter.bundles")) != null && 0 < bundles.length()){ + + /** + * The code below creates a query that will allow only records which do not have a bundlename + * (items, collections, ...) or bitstreams that have a configured bundle name + */ + StringBuffer bundleQuery = new StringBuffer(); + //Also add the possibility that if no bundle name is there these results will also be returned ! + bundleQuery.append("-(bundleName:[* TO *]"); + String[] split = bundles.split(","); + for (int i = 0; i < split.length; i++) { + String bundle = split[i].trim(); + bundleQuery.append("-bundleName:").append(bundle); + if(i != split.length - 1){ + bundleQuery.append(" AND "); + } + } + bundleQuery.append(")"); + + + solrQuery.addFilterQuery(bundleQuery.toString()); + } + + if (filterQuery != null) + { + solrQuery.addFilterQuery(filterQuery); + } + + QueryResponse response; + try + { + // solr.set + response = solr.query(solrQuery); + } + catch (SolrServerException e) + { + System.err.println("Error using query " + query); + throw e; + } + return response; + } + + + /** String of IP and Ranges in IPTable as a Solr Query */ + private static String filterQuery = null; + + /** + * Returns in a filterQuery string all the ip addresses that should be ignored + * + * @return a string query with ip addresses + */ + public static String getIgnoreSpiderIPs() { + if (filterQuery == null) { + StringBuilder query = new StringBuilder(); + boolean first = true; + for (String ip : SpiderDetector.getSpiderIpAddresses()) { + if (first) { + query.append(" AND "); + first = false; + } + + query.append(" NOT(ip: ").append(ip).append(")"); + } + filterQuery = query.toString(); + } + + return filterQuery; + + } + + /** + * Maintenance to keep a SOLR index efficient. + * Note: This might take a long time. + */ + public static void optimizeSOLR() { + try { + long start = System.currentTimeMillis(); + System.out.println("SOLR Optimize -- Process Started:"+start); + solr.optimize(); + long finish = System.currentTimeMillis(); + System.out.println("SOLR Optimize -- Process Finished:"+finish); + System.out.println("SOLR Optimize -- Total time taken:"+(finish-start) + " (ms)."); + } catch (SolrServerException sse) { + System.err.println(sse.getMessage()); + } catch (IOException ioe) { + System.err.println(ioe.getMessage()); + } + } + + public static void shardSolrIndex() throws IOException, SolrServerException { + /* + Start by faceting by year so we can include each year in a seperate core ! + */ + SolrQuery yearRangeQuery = new SolrQuery(); + yearRangeQuery.setQuery("*:*"); + yearRangeQuery.setRows(0); + yearRangeQuery.setFacet(true); + yearRangeQuery.add(FacetParams.FACET_RANGE, "time"); + //We go back to 2000 the year 2000, this is a bit overkill but this way we ensure we have everything + //The alternative would be to sort but that isn't recommended since it would be a very costly query ! + yearRangeQuery.add(FacetParams.FACET_RANGE_START, "NOW/YEAR-" + (Calendar.getInstance().get(Calendar.YEAR) - 2000) + "YEARS"); + //Add the +0year to ensure that we DO NOT include the current year + yearRangeQuery.add(FacetParams.FACET_RANGE_END, "NOW/YEAR+0YEARS"); + yearRangeQuery.add(FacetParams.FACET_RANGE_GAP, "+1YEAR"); + yearRangeQuery.add(FacetParams.FACET_MINCOUNT, String.valueOf(1)); + + //Create a temp directory to store our files in ! + File tempDirectory = new File(ConfigurationManager.getProperty("dspace.dir") + File.separator + "temp" + File.separator); + tempDirectory.mkdirs(); + + + QueryResponse queryResponse = solr.query(yearRangeQuery); + //We only have one range query ! + List yearResults = queryResponse.getFacetRanges().get(0).getCounts(); + for (RangeFacet.Count count : yearResults) { + long totalRecords = count.getCount(); + + //Create a range query from this ! + //We start with out current year + DCDate dcStart = new DCDate(count.getValue()); + Calendar endDate = Calendar.getInstance(); + //Advance one year for the start of the next one ! + endDate.setTime(dcStart.toDate()); + endDate.add(Calendar.YEAR, 1); + DCDate dcEndDate = new DCDate(endDate.getTime()); + + + StringBuilder filterQuery = new StringBuilder(); + filterQuery.append("time:(["); + filterQuery.append(ClientUtils.escapeQueryChars(dcStart.toString())); + filterQuery.append(" TO "); + filterQuery.append(ClientUtils.escapeQueryChars(dcEndDate.toString())); + filterQuery.append("]"); + //The next part of the filter query excludes the content from midnight of the next year ! + filterQuery.append(" NOT ").append(ClientUtils.escapeQueryChars(dcEndDate.toString())); + filterQuery.append(")"); + + + Map yearQueryParams = new HashMap(); + yearQueryParams.put(CommonParams.Q, "*:*"); + yearQueryParams.put(CommonParams.ROWS, String.valueOf(10000)); + yearQueryParams.put(CommonParams.FQ, filterQuery.toString()); + yearQueryParams.put(CommonParams.WT, "csv"); + + //Start by creating a new core + String coreName = "statistics-" + dcStart.getYear(); + CommonsHttpSolrServer statisticsYearServer = createCore(solr, coreName); + + System.out.println("Moving: " + totalRecords + " into core " + coreName); + log.info("Moving: " + totalRecords + " records into core " + coreName); + + List filesToUpload = new ArrayList(); + for(int i = 0; i < totalRecords; i+=10000){ + String solrRequestUrl = solr.getBaseURL() + "/select"; + solrRequestUrl = generateURL(solrRequestUrl, yearQueryParams); + + GetMethod get = new GetMethod(solrRequestUrl); + new HttpClient().executeMethod(get); + InputStream csvInputstream = get.getResponseBodyAsStream(); + //Write the csv ouput to a file ! + File csvFile = new File(tempDirectory.getPath() + File.separatorChar + "temp." + dcStart.getYear() + "." + i + ".csv"); + FileUtils.copyInputStreamToFile(csvInputstream, csvFile); + filesToUpload.add(csvFile); + + //Add 10000 & start over again + yearQueryParams.put(CommonParams.START, String.valueOf((i + 10000))); + } + + for (File tempCsv : filesToUpload) { + //Upload the data in the csv files to our new solr core + ContentStreamUpdateRequest contentStreamUpdateRequest = new ContentStreamUpdateRequest("/update/csv"); + contentStreamUpdateRequest.setParam("stream.contentType", "text/plain;charset=utf-8"); + contentStreamUpdateRequest.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true); + contentStreamUpdateRequest.addFile(tempCsv); + + statisticsYearServer.request(contentStreamUpdateRequest); + } + statisticsYearServer.commit(true, true); + + + //Delete contents of this year from our year query ! + solr.deleteByQuery(filterQuery.toString()); + solr.commit(true, true); + + log.info("Moved " + totalRecords + " records into core: " + coreName); + } + + FileUtils.deleteDirectory(tempDirectory); + } + + private static CommonsHttpSolrServer createCore(CommonsHttpSolrServer solr, String coreName) throws IOException, SolrServerException { + String solrDir = ConfigurationManager.getProperty("dspace.dir") + File.separator + "solr" +File.separator; + String baseSolrUrl = solr.getBaseURL().replace("statistics", ""); + CoreAdminRequest.Create create = new CoreAdminRequest.Create(); + create.setCoreName(coreName); + create.setInstanceDir("statistics"); + create.setDataDir(solrDir + coreName + File.separator + "data"); + CommonsHttpSolrServer solrServer = new CommonsHttpSolrServer(baseSolrUrl); + create.process(solrServer); + log.info("Created core with name: " + coreName); + return new CommonsHttpSolrServer(baseSolrUrl + "/" + coreName); + } + + + public static void reindexBitstreamHits(boolean removeDeletedBitstreams) throws Exception { + Context context = new Context(); + + try { + //First of all retrieve the total number of records to be updated + SolrQuery query = new SolrQuery(); + query.setQuery("*:*"); + query.addFilterQuery("type:" + Constants.BITSTREAM); + //Only retrieve records which do not have a bundle name + query.addFilterQuery("-bundleName:[* TO *]"); + query.setRows(0); + addAdditionalSolrYearCores(query); + long totalRecords = solr.query(query).getResults().getNumFound(); + + File tempDirectory = new File(ConfigurationManager.getProperty("dspace.dir") + File.separator + "temp" + File.separator); + tempDirectory.mkdirs(); + List tempCsvFiles = new ArrayList(); + for(int i = 0; i < totalRecords; i+=10000){ + Map params = new HashMap(); + params.put(CommonParams.Q, "*:*"); + params.put(CommonParams.FQ, "-bundleName:[* TO *] AND type:" + Constants.BITSTREAM); + params.put(CommonParams.WT, "csv"); + params.put(CommonParams.ROWS, String.valueOf(10000)); + params.put(CommonParams.START, String.valueOf(i)); + + String solrRequestUrl = solr.getBaseURL() + "/select"; + solrRequestUrl = generateURL(solrRequestUrl, params); + + GetMethod get = new GetMethod(solrRequestUrl); + new HttpClient().executeMethod(get); + + InputStream csvOutput = get.getResponseBodyAsStream(); + Reader csvReader = new InputStreamReader(csvOutput); + String[][] csvParsed = CSVParser.parse(csvReader); + String[] header = csvParsed[0]; + //Attempt to find the bitstream id index ! + int idIndex = 0; + for (int j = 0; j < header.length; j++) { + if(header[j].equals("id")){ + idIndex = j; + } + } + + File tempCsv = new File(tempDirectory.getPath() + File.separatorChar + "temp." + i + ".csv"); + tempCsvFiles.add(tempCsv); + FileOutputStream outputStream = new FileOutputStream(tempCsv); + CSVPrinter csvp = new CSVPrinter(outputStream); + csvp.setAlwaysQuote(false); + + //Write the header ! + csvp.write(header); + csvp.write("bundleName"); + csvp.writeln(); + Map bitBundleCache = new HashMap(); + //Loop over each line (skip the headers though)! + for (int j = 1; j < csvParsed.length; j++){ + String[] csvLine = csvParsed[j]; + //Write the default line ! + int bitstreamId = Integer.parseInt(csvLine[idIndex]); + //Attempt to retrieve our bundle name from the cache ! + String bundleName = bitBundleCache.get(bitstreamId); + if(bundleName == null){ + //Nothing found retrieve the bitstream + Bitstream bitstream = Bitstream.find(context, bitstreamId); + //Attempt to retrieve our bitstream ! + if (bitstream != null){ + Bundle[] bundles = bitstream.getBundles(); + if(bundles != null && 0 < bundles.length){ + Bundle bundle = bundles[0]; + bundleName = bundle.getName(); + context.removeCached(bundle, bundle.getID()); + }else{ + //No bundle found, we are either a collection or a community logo, check for it ! + DSpaceObject parentObject = bitstream.getParentObject(); + if(parentObject instanceof Collection){ + bundleName = "LOGO-COLLECTION"; + }else + if(parentObject instanceof Community){ + bundleName = "LOGO-COMMUNITY"; + } + if(parentObject != null){ + context.removeCached(parentObject, parentObject.getID()); + } + + } + //Cache the bundle name + bitBundleCache.put(bitstream.getID(), bundleName); + //Remove the bitstream from cache + context.removeCached(bitstream, bitstreamId); + } + //Check if we don't have a bundlename + //If we don't have one & we do not need to delete the deleted bitstreams ensure that a BITSTREAM_DELETED bundle name is given ! + if(bundleName == null && !removeDeletedBitstreams){ + bundleName = "BITSTREAM_DELETED"; + } + } + csvp.write(csvLine); + csvp.write(bundleName); + csvp.writeln(); + } + + //Loop over our parsed csv + csvp.flush(); + csvp.close(); + } + + //Add all the separate csv files + for (File tempCsv : tempCsvFiles) { + ContentStreamUpdateRequest contentStreamUpdateRequest = new ContentStreamUpdateRequest("/update/csv"); + contentStreamUpdateRequest.setParam("stream.contentType", "text/plain;charset=utf-8"); + contentStreamUpdateRequest.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true); + contentStreamUpdateRequest.addFile(tempCsv); + + solr.request(contentStreamUpdateRequest); + } + + //Now that all our new bitstream stats are in place, delete all the old ones ! + solr.deleteByQuery("-bundleName:[* TO *] AND type:" + Constants.BITSTREAM); + //Commit everything to wrap up + solr.commit(true, true); + //Clean up our directory ! + FileUtils.deleteDirectory(tempDirectory); + } catch (Exception e) { + log.error("Error while updating the bitstream statistics", e); + throw e; + } finally { + context.abort(); + } + } + + private static String generateURL(String baseURL, Map parameters) throws UnsupportedEncodingException { + boolean first = true; + StringBuilder result = new StringBuilder(baseURL); + for (String key : parameters.keySet()) + { + if (first) + { + result.append("?"); + first = false; + } + else + { + result.append("&"); + } + + result.append(key).append("=").append(URLEncoder.encode(parameters.get(key), "UTF-8")); + } + + return result.toString(); + } + + private static void addAdditionalSolrYearCores(SolrQuery solrQuery){ + //Only add if needed + if(0 < statisticYearCores.size()){ + //The shards are a comma separated list of the urls to the cores + solrQuery.add(ShardParams.SHARDS, StringUtils.join(statisticYearCores.iterator(), ",")); + } + + } +} diff --git a/dspace-stats/src/main/java/org/dspace/statistics/SolrLoggerUsageEventListener.java b/dspace-stats/src/main/java/org/dspace/statistics/SolrLoggerUsageEventListener.java index f44c6869e6..c686065515 100644 --- a/dspace-stats/src/main/java/org/dspace/statistics/SolrLoggerUsageEventListener.java +++ b/dspace-stats/src/main/java/org/dspace/statistics/SolrLoggerUsageEventListener.java @@ -12,6 +12,9 @@ import org.dspace.eperson.EPerson; import org.dspace.services.model.Event; import org.dspace.usage.AbstractUsageEventListener; import org.dspace.usage.UsageEvent; +import org.dspace.usage.UsageSearchEvent; +import org.dspace.usage.UsageWorkflowEvent; +import org.springframework.util.CollectionUtils; /** * Simple SolrLoggerUsageEvent facade to separate Solr specific @@ -29,12 +32,27 @@ public class SolrLoggerUsageEventListener extends AbstractUsageEventListener { if(event instanceof UsageEvent) { try{ - UsageEvent ue = (UsageEvent)event; EPerson currentUser = ue.getContext() == null ? null : ue.getContext().getCurrentUser(); - SolrLogger.post(ue.getObject(), ue.getRequest(), currentUser); + if(UsageEvent.Action.VIEW == ue.getAction()){ + SolrLogger.postView(ue.getObject(), ue.getRequest(), currentUser); + }else + if(UsageEvent.Action.SEARCH == ue.getAction()){ + UsageSearchEvent usageSearchEvent = (UsageSearchEvent) ue; + //Only log if the user has already filled in a query ! + if(!CollectionUtils.isEmpty(((UsageSearchEvent) ue).getQueries())){ + SolrLogger.postSearch(ue.getObject(), ue.getRequest(), currentUser, + usageSearchEvent.getQueries(), usageSearchEvent.getRpp(), usageSearchEvent.getSortBy(), + usageSearchEvent.getSortOrder(), usageSearchEvent.getPage(), usageSearchEvent.getScope()); + } + }else + if(UsageEvent.Action.WORKFLOW == ue.getAction()){ + UsageWorkflowEvent usageWorkflowEvent = (UsageWorkflowEvent) ue; + + SolrLogger.postWorkflow(usageWorkflowEvent); + } } catch(Exception e) diff --git a/dspace-stats/src/main/java/org/dspace/statistics/content/DatasetSearchGenerator.java b/dspace-stats/src/main/java/org/dspace/statistics/content/DatasetSearchGenerator.java new file mode 100644 index 0000000000..50f7bf7ac9 --- /dev/null +++ b/dspace-stats/src/main/java/org/dspace/statistics/content/DatasetSearchGenerator.java @@ -0,0 +1,56 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.statistics.content; + +/** + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class DatasetSearchGenerator extends DatasetTypeGenerator { + + public static enum Mode { + SEARCH_OVERVIEW ("search_overview"), + SEARCH_OVERVIEW_TOTAL ("search_overview_total"); + + private final String text; + + Mode(String text) { + this.text = text; + } + public String text() { return text; } + } + + private Mode mode; + private boolean percentage = false; + private boolean retrievePageViews; + + public boolean isRetrievePageViews() { + return retrievePageViews; + } + + public void setRetrievePageViews(boolean retrievePageViews) { + this.retrievePageViews = retrievePageViews; + } + + public void setPercentage(boolean percentage){ + this.percentage = percentage; + } + + public boolean isPercentage() { + return percentage; + } + + public Mode getMode() { + return mode; + } + + public void setMode(Mode mode) { + this.mode = mode; + } +} diff --git a/dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataSearches.java b/dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataSearches.java new file mode 100644 index 0000000000..9520d403f5 --- /dev/null +++ b/dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataSearches.java @@ -0,0 +1,238 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.statistics.content; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.util.ClientUtils; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.statistics.Dataset; +import org.dspace.statistics.ObjectCount; +import org.dspace.statistics.SolrLogger; +import org.dspace.statistics.content.filter.StatisticsFilter; +import org.dspace.utils.DSpace; + +import java.io.IOException; +import java.sql.SQLException; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +/** + * A statistics data implementation that will query the statistics backend for search information + * + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class StatisticsDataSearches extends StatisticsData { + + private static final DecimalFormat pageViewFormat = new DecimalFormat("0.00"); + private static final DecimalFormat percentageFormat = new DecimalFormat("0.00%"); + /** Current DSpaceObject for which to generate the statistics. */ + private DSpaceObject currentDso; + + public StatisticsDataSearches(DSpaceObject dso) { + super(); + this.currentDso = dso; + } + + + + @Override + public Dataset createDataset(Context context) throws SQLException, SolrServerException, IOException, ParseException { + // Check if we already have one. + // If we do then give it back. + if(getDataset() != null) + { + return getDataset(); + } + + List filters = getFilters(); + List defaultFilters = new ArrayList(); + for (StatisticsFilter statisticsFilter : filters) { + defaultFilters.add(statisticsFilter.toQuery()); + } + + String defaultFilterQuery = StringUtils.join(defaultFilters.iterator(), " AND "); + + String query = getQuery(); + + Dataset dataset = new Dataset(0,0); + List datasetGenerators = getDatasetGenerators(); + if(0 < datasetGenerators.size()){ + //At the moment we can only have one dataset generator + DatasetGenerator datasetGenerator = datasetGenerators.get(0); + if(datasetGenerator instanceof DatasetSearchGenerator){ + DatasetSearchGenerator typeGenerator = (DatasetSearchGenerator) datasetGenerator; + + if(typeGenerator.getMode() == DatasetSearchGenerator.Mode.SEARCH_OVERVIEW){ + StringBuilder fqBuffer = new StringBuilder(defaultFilterQuery); + if(0 < fqBuffer.length()) + { + fqBuffer.append(" AND "); + } + fqBuffer.append(getSearchFilterQuery()); + + ObjectCount[] topCounts = SolrLogger.queryFacetField(query, fqBuffer.toString(), typeGenerator.getType(), typeGenerator.getMax(), (typeGenerator.isPercentage() || typeGenerator.isIncludeTotal()), null); + long totalCount = -1; + if(typeGenerator.isPercentage() && 0 < topCounts.length){ + //Retrieve the total required to calculate the percentage + totalCount = topCounts[topCounts.length - 1].getCount(); + //Remove the total count from view ! + topCounts = (ObjectCount[]) ArrayUtils.subarray(topCounts, 0, topCounts.length - 1); + } + + int nrColumns = 2; + if(typeGenerator.isPercentage()){ + nrColumns++; + } + if(typeGenerator.isRetrievePageViews()){ + nrColumns++; + } + + dataset = new Dataset(topCounts.length, nrColumns); + dataset.setColLabel(0, "search-terms"); + dataset.setColLabel(1, "searches"); + if(typeGenerator.isPercentage()){ + dataset.setColLabel(2, "percent-total"); + } + if(typeGenerator.isRetrievePageViews()){ + dataset.setColLabel(3, "views-search"); + } + for (int i = 0; i < topCounts.length; i++) { + ObjectCount queryCount = topCounts[i]; + + dataset.setRowLabel(i, String.valueOf(i + 1)); + String displayedValue = queryCount.getValue(); + if(new DSpace().getConfigurationService().getPropertyAsType("usage-statistics.search.statistics.unescape.queries", Boolean.TRUE)){ + displayedValue = displayedValue.replace("\\", ""); + } + dataset.addValueToMatrix(i, 0, displayedValue); + dataset.addValueToMatrix(i, 1, queryCount.getCount()); + if(typeGenerator.isPercentage()){ + //Calculate our percentage from the total ! + dataset.addValueToMatrix(i, 2, percentageFormat.format(((float) queryCount.getCount() / totalCount))); + } + if(typeGenerator.isRetrievePageViews()){ + String queryString = ClientUtils.escapeQueryChars(queryCount.getValue()); + if(queryString.equals("")){ + queryString = "\"\""; + } + + ObjectCount totalPageViews = getTotalPageViews("query:" + queryString, defaultFilterQuery); + dataset.addValueToMatrix(i, 3, pageViewFormat.format((float) totalPageViews.getCount() / queryCount.getCount())); + } + } + }else + if(typeGenerator.getMode() == DatasetSearchGenerator.Mode.SEARCH_OVERVIEW_TOTAL){ + //Retrieve the total counts ! + ObjectCount totalCount = SolrLogger.queryTotal(query, getSearchFilterQuery()); + + //Retrieve the filtered count by using the default filter query + StringBuilder fqBuffer = new StringBuilder(defaultFilterQuery); + if(0 < fqBuffer.length()) + { + fqBuffer.append(" AND "); + } + fqBuffer.append(getSearchFilterQuery()); + + ObjectCount totalFiltered = SolrLogger.queryTotal(query, fqBuffer.toString()); + + + fqBuffer = new StringBuilder(defaultFilterQuery); + if(0 < fqBuffer.length()) + { + fqBuffer.append(" AND "); + } + fqBuffer.append("statistics_type:").append(SolrLogger.StatisticsType.SEARCH_RESULT.text()); + + ObjectCount totalPageViews = getTotalPageViews(query, defaultFilterQuery); + + dataset = new Dataset(1, 3); + dataset.setRowLabel(0, ""); + + + dataset.setColLabel(0, "searches"); + dataset.addValueToMatrix(0, 0, totalFiltered.getCount()); + dataset.setColLabel(1, "percent-total"); + //Ensure that we do NOT divide by 0 + float percentTotal; + if(totalCount.getCount() == 0){ + percentTotal = 0; + }else{ + percentTotal = (float) totalFiltered.getCount() / totalCount.getCount(); + } + + + dataset.addValueToMatrix(0, 1, percentageFormat.format(percentTotal)); + dataset.setColLabel(2, "views-search"); + //Ensure that we do NOT divide by 0 + float pageViews; + if(totalFiltered.getCount() == 0){ + pageViews = 0; + }else{ + pageViews = (float) totalPageViews.getCount() / totalFiltered.getCount(); + } + + dataset.addValueToMatrix(0, 2, pageViewFormat.format(pageViews)); + } + }else{ + throw new IllegalArgumentException("Data generator with class" + datasetGenerator.getClass().getName() + " is not supported by the statistics search engine !"); + } + } + + return dataset; + } + + /** + * Returns the query to be used in solr + * in case of a dso a scopeDso query will be returned otherwise the default *:* query will be used + * @return the query as a string + */ + protected String getQuery() { + String query; + if(currentDso != null){ + query = "scopeType: " + currentDso.getType() + " AND scopeId: " + currentDso.getID(); + + }else{ + query = "*:*"; + } + return query; + } + + private ObjectCount getTotalPageViews(String query, String defaultFilterQuery) throws SolrServerException { + StringBuilder fqBuffer; + fqBuffer = new StringBuilder(defaultFilterQuery); + if(0 < fqBuffer.length()) + { + fqBuffer.append(" AND "); + } + fqBuffer.append("statistics_type:").append(SolrLogger.StatisticsType.SEARCH_RESULT.text()); + + + //Retrieve the number of page views by this query ! + return SolrLogger.queryTotal(query, fqBuffer.toString()); + } + + /** + * Returns a filter query that only allows new searches to pass + * new searches are searches that haven't been paged through + * @return a solr filterquery + */ + private String getSearchFilterQuery() { + StringBuilder fqBuffer = new StringBuilder(); + fqBuffer.append("statistics_type:").append(SolrLogger.StatisticsType.SEARCH.text()); + //Also append a filter query to ensure that paging is left out ! + fqBuffer.append(" AND -page:[* TO *]"); + return fqBuffer.toString(); + } +} diff --git a/dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java b/dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java index 631bcd3c4f..a774b45013 100644 --- a/dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java +++ b/dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java @@ -7,6 +7,7 @@ */ package org.dspace.statistics.content; +import org.apache.commons.lang.StringUtils; import org.dspace.content.*; import org.dspace.statistics.Dataset; import org.dspace.statistics.ObjectCount; @@ -148,12 +149,8 @@ public class StatisticsDataVisits extends StatisticsData } // Determine our filterQuery - String filterQuery = null; + String filterQuery = ""; for (int i = 0; i < getFilters().size(); i++) { - if(filterQuery == null) - { - filterQuery = ""; - } StatisticsFilter filter = getFilters().get(i); filterQuery += "(" + filter.toQuery() + ")"; @@ -162,6 +159,14 @@ public class StatisticsDataVisits extends StatisticsData filterQuery += " AND "; } } + if(StringUtils.isNotBlank(filterQuery)){ + filterQuery += " AND "; + } + //Only use the view type and make sure old data (where no view type is present) is also supported + //Solr doesn't explicitly apply boolean logic, so this query cannot be simplified to an OR query + filterQuery += "-(statistics_type:[* TO *] AND -statistics_type:" + SolrLogger.StatisticsType.VIEW.text() + ")"; + + // System.out.println("FILTERQUERY: " + filterQuery); // We determine our values on the queries resolved above diff --git a/dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataWorkflow.java b/dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataWorkflow.java new file mode 100644 index 0000000000..5031465090 --- /dev/null +++ b/dspace-stats/src/main/java/org/dspace/statistics/content/StatisticsDataWorkflow.java @@ -0,0 +1,202 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.statistics.content; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.common.SolrDocument; +import org.dspace.content.DCDate; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.dspace.statistics.Dataset; +import org.dspace.statistics.ObjectCount; +import org.dspace.statistics.SolrLogger; +import org.dspace.statistics.content.filter.StatisticsFilter; +import org.dspace.utils.DSpace; + +import java.io.File; +import java.io.IOException; +import java.sql.SQLException; +import java.text.ParseException; +import java.util.*; + +/** + * A workflow data implementation that will query the statistics backend for workflow information + * + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class StatisticsDataWorkflow extends StatisticsData { + + private static final Logger log = Logger.getLogger(StatisticsDataWorkflow.class); + + /** Current DSpaceObject for which to generate the statistics. */ + private DSpaceObject currentDso; + /** Variable used to indicate of how many months an average is required (-1 is inactive) **/ + private int averageMonths = -1; + + public StatisticsDataWorkflow(DSpaceObject dso, int averageMonths) { + super(); + this.currentDso = dso; + this.averageMonths = averageMonths; + } + + + @Override + public Dataset createDataset(Context context) throws SQLException, SolrServerException, IOException, ParseException { + // Check if we already have one. + // If we do then give it back. + if(getDataset() != null) + { + return getDataset(); + } + + List filters = getFilters(); + List defaultFilters = new ArrayList(); + for (StatisticsFilter statisticsFilter : filters) { + defaultFilters.add(statisticsFilter.toQuery()); + } + + String defaultFilterQuery = StringUtils.join(defaultFilters.iterator(), " AND "); + + String query = getQuery(); + + Dataset dataset = new Dataset(0,0); + List datasetGenerators = getDatasetGenerators(); + if(0 < datasetGenerators.size()){ + //At the moment we can only have one dataset generator + DatasetGenerator datasetGenerator = datasetGenerators.get(0); + if(datasetGenerator instanceof DatasetTypeGenerator){ + DatasetTypeGenerator typeGenerator = (DatasetTypeGenerator) datasetGenerator; + ObjectCount[] topCounts = SolrLogger.queryFacetField(query, defaultFilterQuery, typeGenerator.getType(), typeGenerator.getMax(), typeGenerator.isIncludeTotal(), null); + + //Retrieve our total field counts + Map totalFieldCounts = new HashMap(); + if(averageMonths != -1){ + totalFieldCounts = getTotalFacetCounts(typeGenerator); + } + long monthDifference = 1; + if(getOldestWorkflowItemDate() != null){ + monthDifference = getMonthsDifference(new Date(), getOldestWorkflowItemDate()); + } + + dataset = new Dataset(topCounts.length, (averageMonths != -1 ? 3 : 2)); + dataset.setColLabel(0, "step"); + dataset.setColLabel(1, "performed"); + if(averageMonths != -1){ + dataset.setColLabel(2, "average"); + } + for (int i = 0; i < topCounts.length; i++) { + ObjectCount topCount = topCounts[i]; + dataset.setRowLabel(i, String.valueOf(i + 1)); + dataset.addValueToMatrix(i, 0, topCount.getValue()); + dataset.addValueToMatrix(i, 1, topCount.getCount()); + if(averageMonths != -1){ + //Calculate the average of one month + long monthlyAverage = 0; + if(totalFieldCounts.get(topCount.getValue()) != null){ + monthlyAverage = totalFieldCounts.get(topCount.getValue()) / monthDifference; + } + //We multiple our average for one month by the number of + dataset.addValueToMatrix(i, 2, (monthlyAverage * averageMonths)); + } + + } + } + } + + return dataset; + } + + /** + * Returns the query to be used in solr + * in case of a dso a scopeDso query will be returned otherwise the default *:* query will be used + * @return the query as a string + */ + protected String getQuery() { + String query = "statistics_type:" + SolrLogger.StatisticsType.WORKFLOW.text(); + query += " AND NOT(previousWorkflowStep: SUBMIT)"; + if(currentDso != null){ + if(currentDso.getType() == Constants.COMMUNITY){ + query += " AND owningComm:"; + + }else + if(currentDso.getType() == Constants.COLLECTION){ + query += " AND owningColl:"; + } + query += currentDso.getID(); + } + return query; + } + + private int getMonthsDifference(Date date1, Date date2) { + int m1 = date1.getYear() * 12 + date1.getMonth(); + int m2 = date2.getYear() * 12 + date2.getMonth(); + return m2 - m1 + 1; + } + + + /** + * Retrieve the total counts for the facets (total count is same query but none of the filter queries + * @param typeGenerator the type generator + * @return as a key the + * @throws org.apache.solr.client.solrj.SolrServerException + */ + protected Map getTotalFacetCounts(DatasetTypeGenerator typeGenerator) throws SolrServerException { + ObjectCount[] objectCounts = SolrLogger.queryFacetField(getQuery(), null, typeGenerator.getType(), -1, false, null); + Map result = new HashMap(); + for (ObjectCount objectCount : objectCounts) { + result.put(objectCount.getValue(), objectCount.getCount()); + } + return result; + } + + + + protected Date getOldestWorkflowItemDate() throws SolrServerException { + ConfigurationService configurationService = new DSpace().getConfigurationService(); + String workflowStartDate = configurationService.getProperty("usage-statistics.workflow-start-date"); + if(workflowStartDate == null){ + //Query our solr for it ! + QueryResponse oldestRecord = SolrLogger.query(getQuery(), null, null, 1, 0, null, null, null, null, "time", true); + if(0 < oldestRecord.getResults().getNumFound()){ + SolrDocument solrDocument = oldestRecord.getResults().get(0); + Date oldestDate = (Date) solrDocument.getFieldValue("time"); + //Store the date, we only need to retrieve this once ! + try { + //Also store it in the solr-statics configuration file, the reason for this being that the sort query + //can be very time consuming & we do not want this delay each time we want to see workflow statistics + String solrConfigDir = configurationService.getProperty("dspace.dir") + File.separator + "config" + + File.separator + "modules" + File.separator + "usage-statistics.cfg"; + PropertiesConfiguration config = new PropertiesConfiguration(solrConfigDir); + config.setProperty("workflow-start-date", new DCDate(oldestDate)); + config.save(); + } catch (ConfigurationException e) { + log.error("Error while storing workflow start date", e); + } + //ALso store it in our local config ! + configurationService.setProperty("usage-statistics.workflow-start-date", new DCDate(oldestDate).toString()); + + //Write to file + return oldestDate; + }else{ + return null; + } + + }else{ + return new DCDate(workflowStartDate).toDate(); + } + } +} diff --git a/dspace-stats/src/main/java/org/dspace/statistics/util/StatisticsClient.java b/dspace-stats/src/main/java/org/dspace/statistics/util/StatisticsClient.java index 986436700d..88f75127d9 100644 --- a/dspace-stats/src/main/java/org/dspace/statistics/util/StatisticsClient.java +++ b/dspace-stats/src/main/java/org/dspace/statistics/util/StatisticsClient.java @@ -1,155 +1,160 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.statistics.util; - -import org.apache.commons.cli.*; -import org.apache.log4j.Logger; -import org.apache.tools.ant.taskdefs.Get; -import org.dspace.core.ConfigurationManager; -import org.dspace.statistics.SolrLogger; - -import java.io.*; -import java.net.URL; - -/** - * Class to load intermediate statistics files into solr - * - * @author Stuart Lewis - */ -public class StatisticsClient -{ - private static final Logger log = Logger.getLogger(StatisticsClient.class); - - /** - * Print the help message - * - * @param options The command line options the user gave - * @param exitCode the system exit code to use - */ - private static void printHelp(Options options, int exitCode) - { - // print the help message - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("StatisticsClient\n", options); - System.exit(exitCode); - } - - /** - * Main method to run the statistics importer. - * - * @param args The command line arguments - * @throws Exception If something goes wrong - */ - public static void main(String[] args) throws Exception - { - CommandLineParser parser = new PosixParser(); - - Options options = new Options(); - - options.addOption("u", "update-spider-files", false, - "Update Spider IP Files from internet into " + - ConfigurationManager.getProperty("dspace.dir") + "/config/spiders"); - - options.addOption("m", "mark-spiders", false, "Update isBot Flag in Solr"); - options.addOption("f", "delete-spiders-by-flag", false, "Delete Spiders in Solr By isBot Flag"); - options.addOption("i", "delete-spiders-by-ip", false, "Delete Spiders in Solr By IP Address"); - options.addOption("o", "optimize", false, "Run maintenance on the SOLR index"); - options.addOption("b", "reindex-bitstreams", false, "Reindex the bitstreams to ensure we have the bundle name"); - options.addOption("r", "remove-deleted-bitstreams", false, "While indexing the bundle names remove the statistics about deleted bitstreams"); - options.addOption("h", "help", false, "help"); - - CommandLine line = parser.parse(options, args); - - // Did the user ask to see the help? - if (line.hasOption('h')) - { - printHelp(options, 0); - } - - if(line.hasOption("u")) - { - StatisticsClient.updateSpiderFiles(); - } - else if (line.hasOption('m')) - { - SolrLogger.markRobotsByIP(); - } - else if(line.hasOption('f')) - { - SolrLogger.deleteRobotsByIsBotFlag(); - } - else if(line.hasOption('i')) - { - SolrLogger.deleteRobotsByIP(); - } - else if(line.hasOption('o')) - { - SolrLogger.optimizeSOLR(); - } - else if(line.hasOption('b')) - { - SolrLogger.reindexBitstreamHits(line.hasOption('r')); - } - else - { - printHelp(options, 0); - } - } - - /** - * Method to update Spiders in config directory. - */ - private static void updateSpiderFiles() - { - try - { - System.out.println("Downloading latest spider IP addresses:"); - - // Get the list URLs to download from - String urls = ConfigurationManager.getProperty("solr-statistics", "spiderips.urls"); - if ((urls == null) || ("".equals(urls))) - { - System.err.println(" - Missing setting from dspace.cfg: solr.spiderips.urls"); - System.exit(0); - } - - // Get the location of spiders directory - File spiders = new File(ConfigurationManager.getProperty("dspace.dir"),"config/spiders"); - - if (!spiders.exists() && !spiders.mkdirs()) - { - log.error("Unable to create spiders directory"); - } - - String[] values = urls.split(","); - for (String value : values) - { - value = value.trim(); - System.out.println(" Downloading: " + value); - - URL url = new URL(value); - - Get get = new Get(); - get.setDest(new File(spiders, url.getHost() + url.getPath().replace("/","-"))); - get.setSrc(url); - get.setUseTimestamp(true); - get.execute(); - - } - - - } catch (Exception e) - { - System.err.println(" - Error: " + e.getMessage()); - e.printStackTrace(); - System.exit(1); - } - } - - -} +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.statistics.util; + +import org.apache.commons.cli.*; +import org.apache.log4j.Logger; +import org.apache.tools.ant.taskdefs.Get; +import org.dspace.core.ConfigurationManager; +import org.dspace.statistics.SolrLogger; + +import java.io.*; +import java.net.URL; + +/** + * Class to load intermediate statistics files into solr + * + * @author Stuart Lewis + */ +public class StatisticsClient +{ + private static final Logger log = Logger.getLogger(StatisticsClient.class); + + /** + * Print the help message + * + * @param options The command line options the user gave + * @param exitCode the system exit code to use + */ + private static void printHelp(Options options, int exitCode) + { + // print the help message + HelpFormatter myhelp = new HelpFormatter(); + myhelp.printHelp("StatisticsClient\n", options); + System.exit(exitCode); + } + + /** + * Main method to run the statistics importer. + * + * @param args The command line arguments + * @throws Exception If something goes wrong + */ + public static void main(String[] args) throws Exception + { + CommandLineParser parser = new PosixParser(); + + Options options = new Options(); + + options.addOption("u", "update-spider-files", false, + "Update Spider IP Files from internet into " + + ConfigurationManager.getProperty("dspace.dir") + "/config/spiders"); + + options.addOption("m", "mark-spiders", false, "Update isBot Flag in Solr"); + options.addOption("f", "delete-spiders-by-flag", false, "Delete Spiders in Solr By isBot Flag"); + options.addOption("i", "delete-spiders-by-ip", false, "Delete Spiders in Solr By IP Address"); + options.addOption("o", "optimize", false, "Run maintenance on the SOLR index"); + options.addOption("b", "reindex-bitstreams", false, "Reindex the bitstreams to ensure we have the bundle name"); + options.addOption("r", "remove-deleted-bitstreams", false, "While indexing the bundle names remove the statistics about deleted bitstreams"); + options.addOption("s", "shard-solr-index", false, "Split the data from the main Solr core into separate Solr cores per year"); + options.addOption("h", "help", false, "help"); + + CommandLine line = parser.parse(options, args); + + // Did the user ask to see the help? + if (line.hasOption('h')) + { + printHelp(options, 0); + } + + if(line.hasOption("u")) + { + StatisticsClient.updateSpiderFiles(); + } + else if (line.hasOption('m')) + { + SolrLogger.markRobotsByIP(); + } + else if(line.hasOption('f')) + { + SolrLogger.deleteRobotsByIsBotFlag(); + } + else if(line.hasOption('i')) + { + SolrLogger.deleteRobotsByIP(); + } + else if(line.hasOption('o')) + { + SolrLogger.optimizeSOLR(); + } + else if(line.hasOption('b')) + { + SolrLogger.reindexBitstreamHits(line.hasOption('r')); + } + else if(line.hasOption('s')) + { + SolrLogger.shardSolrIndex(); + } + else + { + printHelp(options, 0); + } + } + + /** + * Method to update Spiders in config directory. + */ + private static void updateSpiderFiles() + { + try + { + System.out.println("Downloading latest spider IP addresses:"); + + // Get the list URLs to download from + String urls = ConfigurationManager.getProperty("solr-statistics", "spiderips.urls"); + if ((urls == null) || ("".equals(urls))) + { + System.err.println(" - Missing setting from dspace.cfg: solr.spiderips.urls"); + System.exit(0); + } + + // Get the location of spiders directory + File spiders = new File(ConfigurationManager.getProperty("dspace.dir"),"config/spiders"); + + if (!spiders.exists() && !spiders.mkdirs()) + { + log.error("Unable to create spiders directory"); + } + + String[] values = urls.split(","); + for (String value : values) + { + value = value.trim(); + System.out.println(" Downloading: " + value); + + URL url = new URL(value); + + Get get = new Get(); + get.setDest(new File(spiders, url.getHost() + url.getPath().replace("/","-"))); + get.setSrc(url); + get.setUseTimestamp(true); + get.execute(); + + } + + + } catch (Exception e) + { + System.err.println(" - Error: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + + +} diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearch.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearch.java index 8031c43229..b019edaaae 100644 --- a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearch.java +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearch.java @@ -10,14 +10,12 @@ package org.dspace.app.xmlui.aspect.artifactbrowser; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; import org.apache.cocoon.caching.CacheableProcessingComponent; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.Request; -import org.apache.oro.text.perl.Perl5Util; import org.dspace.app.xmlui.utils.HandleUtil; import org.dspace.app.xmlui.utils.UIException; import org.dspace.app.xmlui.wing.Message; @@ -98,10 +96,9 @@ public class AdvancedSearch extends AbstractSearch implements CacheableProcessin /** How many conjunction fields to display */ private static final int FIELD_DISPLAY_COUNT = 3; - private static final int FIELD_MAX_COUNT = 12; - + /** A cache of extracted search fields */ - private java.util.List fields; + private java.util.List fields; /** * Add Page metadata. @@ -167,7 +164,7 @@ public class AdvancedSearch extends AbstractSearch implements CacheableProcessin buildQueryField(i, row.addCell()); } - for (SearchField field : fields) + for (AdvancedSearchUtils.SearchField field : fields) { // Skip over all the fields we've displayed. int i = field.getIndex(); @@ -315,7 +312,7 @@ public class AdvancedSearch extends AbstractSearch implements CacheableProcessin parameters.put("scope", scope); } - for (SearchField searchField : getSearchFields(request)) + for (AdvancedSearchUtils.SearchField searchField : getSearchFields(request)) { int index = searchField.getIndex(); String field = searchField.getField(); @@ -363,188 +360,13 @@ public class AdvancedSearch extends AbstractSearch implements CacheableProcessin protected String getQuery() throws UIException { Request request = ObjectModelHelper.getRequest(objectModel); - return buildQuery(getSearchFields(request)); + return AdvancedSearchUtils.buildQuery(getSearchFields(request)); } - - - /** - * Given a list of search fields buld a lucene search query string. - * - * @param fields The search fields - * @return A string - */ - private String buildQuery(java.util.List fields) - { - Perl5Util util = new Perl5Util(); - - StringBuilder query = new StringBuilder(); - query.append("("); - - // Loop through the fields building the search query as we go. - for (SearchField field : fields) - { - // if the field is empty, then skip it and try a later one. - if (field.getQuery() == null) - { - continue; - } - - // Add the conjunction for everything but the first field. - if (fields.indexOf(field) > 0) - { - query.append(" ").append(field.getConjunction()).append(" ").toString(); - } - - // Two cases, one if a specific search field is specified or if - // ANY is given then just a general search is performed. - if ("ANY".equals(field.getField())) - { - // No field specified, - query.append("(").append(field.getQuery()).append(")").toString(); - } - else - { - // Specific search field specified, add the field specific field. - - // Replace single quotes with double quotes (only if they match) - String subQuery = util.substitute("s/\'(.*)\'/\"$1\"/g", field.getQuery()); - - // If the field is not quoted ... - if (!util.match("/\".*\"/", subQuery)) - { - // ... then separate each word and re-specify the search field. - subQuery = util.substitute("s/[ ]+/ " + field.getField() + ":/g", subQuery); - } - - // Put the subQuery into the general query - query.append("(").append(field.getField()).append(":").append(subQuery).append(")").toString(); - } - } - - if (query.length() == 1) - { - return ""; + private java.util.List getSearchFields(Request request) throws UIException { + if (fields == null){ + fields = AdvancedSearchUtils.getSearchFields(request); } - - return query.append(")").toString(); + return fields; } - - - /** - * Get a list of search fields from the request object - * and parse them into a linear array of fileds. The field's - * index is preserved, so if it comes in as index 17 it will - * be outputted as field 17. - * - * @param request The http request object - * @return Array of search fields - * @throws UIException - */ - public java.util.List getSearchFields(Request request) throws UIException - { - if (this.fields != null) - { - return this.fields; - } - - // Get how many fields to search - int numSearchField; - try { - String numSearchFieldStr = request.getParameter("num_search_field"); - numSearchField = Integer.valueOf(numSearchFieldStr); - } - catch (NumberFormatException nfe) - { - numSearchField = FIELD_MAX_COUNT; - } - - // Iterate over all the possible fields and add each one to the list of fields. - ArrayList fields = new ArrayList(); - for (int i = 1; i <= numSearchField; i++) - { - String field = request.getParameter("field"+i); - String query = decodeFromURL(request.getParameter("query"+i)); - String conjunction = request.getParameter("conjunction"+i); - - if (field != null) - { - field = field.trim(); - if (field.length() == 0) - { - field = null; - } - } - - - if (query != null) - { - query = query.trim(); - if (query.length() == 0) - { - query = null; - } - } - - if (conjunction != null) - { - conjunction = conjunction.trim(); - if (conjunction.length() == 0) - { - conjunction = null; - } - } - - if (field == null) - { - field = "ANY"; - } - if (conjunction == null) - { - conjunction = "AND"; - } - - if (query != null) - { - fields.add(new SearchField(i, field, query, conjunction)); - } - } - - this.fields = fields; - - return this.fields; - } - - /** - * A private record keeping class to relate the various - * components of a search field together. - */ - private static class SearchField { - - /** What index the search field is, typically there are just three - but the theme may expand this number */ - private int index; - - /** The field to search, ANY if none specified */ - private String field; - - /** The query string to search for */ - private String query; - - /** the conjunction: either "AND" or "OR" */ - private String conjuction; - - public SearchField(int index, String field, String query, String conjunction) - { - this.index = index; - this.field = field; - this.query = query; - this.conjuction = conjunction; - } - - public int getIndex() { return this.index;} - public String getField() { return this.field;} - public String getQuery() { return this.query;} - public String getConjunction() { return this.conjuction;} - } - } diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearchLoggerAction.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearchLoggerAction.java new file mode 100644 index 0000000000..4ee6e5a697 --- /dev/null +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearchLoggerAction.java @@ -0,0 +1,44 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.xmlui.aspect.artifactbrowser; + +import org.apache.cocoon.environment.Request; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.app.xmlui.cocoon.SearchLoggerAction; +import org.dspace.app.xmlui.utils.UIException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Returns the query for the advanced search so our SearchLoggerAction can log this + * + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class AdvancedSearchLoggerAction extends SearchLoggerAction { + + private static final Logger log = Logger.getLogger(SearchLoggerAction.class); + + @Override + protected List getQueries(Request request) { + try { + String advancedSearchQuery = AdvancedSearchUtils.buildQuery(AdvancedSearchUtils.getSearchFields(request)); + if(!StringUtils.isBlank(advancedSearchQuery)) + { + return Arrays.asList(advancedSearchQuery); + } + } catch (UIException e) { + log.error(e.getMessage(), e); + } + return new ArrayList(); + } +} diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearchUtils.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearchUtils.java new file mode 100644 index 0000000000..1c85200d6f --- /dev/null +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/AdvancedSearchUtils.java @@ -0,0 +1,233 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.xmlui.aspect.artifactbrowser; + +import org.apache.cocoon.environment.Request; +import org.apache.oro.text.perl.Perl5Util; +import org.dspace.app.xmlui.utils.UIException; +import org.dspace.core.Constants; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayList; + +/** + * Central place where advanced search queries can be built since these are built on several places + * + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class AdvancedSearchUtils { + + private static final int FIELD_MAX_COUNT = 12; + + /** + * Given a list of search fields build a lucene search query string. + * + * @param fields The search fields + * @return A string + */ + public static String buildQuery(java.util.List fields) + { + Perl5Util util = new Perl5Util(); + + StringBuilder query = new StringBuilder(); + query.append("("); + + // Loop through the fields building the search query as we go. + for (SearchField field : fields) + { + // if the field is empty, then skip it and try a later one. + if (field.getQuery() == null) + { + continue; + } + + // Add the conjunction for everything but the first field. + if (fields.indexOf(field) > 0) + { + query.append(" ").append(field.getConjunction()).append(" "); + } + + // Two cases, one if a specific search field is specified or if + // ANY is given then just a general search is performed. + if ("ANY".equals(field.getField())) + { + // No field specified, + query.append("(").append(field.getQuery()).append(")"); + } + else + { + // Specific search field specified, add the field specific field. + + // Replace single quotes with double quotes (only if they match) + String subQuery = util.substitute("s/\'(.*)\'/\"$1\"/g", field.getQuery()); + + // If the field is not quoted ... + if (!util.match("/\".*\"/", subQuery)) + { + // ... then separate each word and re-specify the search field. + subQuery = util.substitute("s/[ ]+/ " + field.getField() + ":/g", subQuery); + } + + // Put the subQuery into the general query + query.append("(").append(field.getField()).append(":").append(subQuery).append(")"); + } + } + + + if (query.length() == 1) + { + return ""; + } + + return query.append(")").toString(); + } + + + /** + * Get a list of search fields from the request object + * and parse them into a linear array of fileds. The field's + * index is preserved, so if it comes in as index 17 it will + * be outputted as field 17. + * + * @param request The http request object + * @return Array of search fields + * @throws org.dspace.app.xmlui.utils.UIException + */ + public static java.util.List getSearchFields(Request request) throws UIException + { + // Get how many fields to search + int numSearchField; + try { + String numSearchFieldStr = request.getParameter("num_search_field"); + numSearchField = Integer.valueOf(numSearchFieldStr); + } + catch (NumberFormatException nfe) + { + numSearchField = FIELD_MAX_COUNT; + } + + // Iterate over all the possible fields and add each one to the list of fields. + ArrayList fields = new ArrayList(); + for (int i = 1; i <= numSearchField; i++) + { + String field = request.getParameter("field"+i); + String query = decodeFromURL(request.getParameter("query"+i)); + String conjunction = request.getParameter("conjunction"+i); + + if (field != null) + { + field = field.trim(); + if (field.length() == 0) + { + field = null; + } + } + + + if (query != null) + { + query = query.trim(); + if (query.length() == 0) + { + query = null; + } + } + + if (conjunction != null) + { + conjunction = conjunction.trim(); + if (conjunction.length() == 0) + { + conjunction = null; + } + } + + if (field == null) + { + field = "ANY"; + } + if (conjunction == null) + { + conjunction = "AND"; + } + + if (query != null) + { + fields.add(new SearchField(i, field, query, conjunction)); + } + } + + return fields; + } + + /** + * A private record keeping class to relate the various + * components of a search field together. + */ + public static class SearchField { + + /** What index the search field is, typically there are just three - but the theme may expand this number */ + private int index; + + /** The field to search, ANY if none specified */ + private String field; + + /** The query string to search for */ + private String query; + + /** the conjunction: either "AND" or "OR" */ + private String conjuction; + + public SearchField(int index, String field, String query, String conjunction) + { + this.index = index; + this.field = field; + this.query = query; + this.conjuction = conjunction; + } + + public int getIndex() { return this.index;} + public String getField() { return this.field;} + public String getQuery() { return this.query;} + public String getConjunction() { return this.conjuction;} + } + + /** + * Decode the given string from URL transmission. + * + * @param encodedString + * The encoded string. + * @return The unencoded string + */ + private static String decodeFromURL(String encodedString) throws UIException + { + if (encodedString == null) + { + return null; + } + + try + { + // Percent(%) is a special character, and must first be escaped as %25 + if (encodedString.contains("%")) + { + encodedString = encodedString.replace("%", "%25"); + } + + return URLDecoder.decode(encodedString, Constants.DEFAULT_ENCODING); + } + catch (UnsupportedEncodingException uee) + { + throw new UIException(uee); + } + + } +} diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/SimpleSearchLoggerAction.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/SimpleSearchLoggerAction.java new file mode 100644 index 0000000000..41df8598b0 --- /dev/null +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/artifactbrowser/SimpleSearchLoggerAction.java @@ -0,0 +1,36 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.xmlui.aspect.artifactbrowser; + +import org.apache.cocoon.environment.Request; +import org.apache.commons.lang.StringUtils; +import org.dspace.app.xmlui.cocoon.SearchLoggerAction; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Returns the query for a simple search so our SearchLoggerAction can log this + * + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class SimpleSearchLoggerAction extends SearchLoggerAction{ + + @Override + protected List getQueries(Request request) { + String query = request.getParameter("query"); + if(!StringUtils.isBlank(query)){ + return Arrays.asList(request.getParameter("query")); + }else{ + return new ArrayList(); + } + } +} diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/AbstractStatisticsDataTransformer.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/AbstractStatisticsDataTransformer.java new file mode 100644 index 0000000000..87971219a9 --- /dev/null +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/AbstractStatisticsDataTransformer.java @@ -0,0 +1,155 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.xmlui.aspect.statistics; + +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Request; +import org.apache.commons.lang.StringUtils; +import org.apache.solr.client.solrj.SolrServerException; +import org.dspace.app.xmlui.cocoon.AbstractDSpaceTransformer; +import org.dspace.app.xmlui.wing.Message; +import org.dspace.app.xmlui.wing.WingException; +import org.dspace.app.xmlui.wing.element.*; +import org.dspace.statistics.Dataset; +import org.dspace.statistics.content.StatisticsTable; +import org.dspace.statistics.content.filter.StatisticsSolrDateFilter; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.sql.SQLException; +import java.text.ParseException; + +/** + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public abstract class AbstractStatisticsDataTransformer extends AbstractDSpaceTransformer { + + private static final Message T_time_filter_last_month = message("xmlui.statistics.StatisticsSearchTransformer.time-filter.last-month"); + private static final Message T_time_filter_overall = message("xmlui.statistics.StatisticsSearchTransformer.time-filter.overall"); + private static final Message T_time_filter_last_year = message("xmlui.statistics.StatisticsSearchTransformer.time-filter.last-year"); + private static final Message T_time_filter_last6_months = message("xmlui.statistics.StatisticsSearchTransformer.time-filter.last-6-months"); + + + protected void addTimeFilter(Division mainDivision) throws WingException { + Request request = ObjectModelHelper.getRequest(objectModel); + String selectedTimeFilter = request.getParameter("time_filter"); + + Select timeFilter = mainDivision.addPara().addSelect("time_filter"); + timeFilter.addOption(StringUtils.equals(selectedTimeFilter, "-1"), "-1", T_time_filter_last_month); + timeFilter.addOption(StringUtils.equals(selectedTimeFilter, "-6"), "-6", T_time_filter_last6_months); + timeFilter.addOption(StringUtils.equals(selectedTimeFilter, "-12"), "-12", T_time_filter_last_year); + timeFilter.addOption(StringUtils.isBlank(selectedTimeFilter), "", T_time_filter_overall); + } + + protected StatisticsSolrDateFilter getDateFilter(String timeFilter){ + if(StringUtils.isNotEmpty(timeFilter)) + { + StatisticsSolrDateFilter dateFilter = new StatisticsSolrDateFilter(); + dateFilter.setStartStr(timeFilter); + dateFilter.setEndStr("0"); + dateFilter.setTypeStr("month"); + return dateFilter; + }else{ + return null; + } + } + + /** + * Adds a table layout to the page + * + * @param mainDiv + * the div to add the table to + * @param display the statistics table containing our data + * @throws org.xml.sax.SAXException + * @throws org.dspace.app.xmlui.wing.WingException + * @throws java.text.ParseException + * @throws java.io.IOException + * @throws org.apache.solr.client.solrj.SolrServerException + * @throws java.sql.SQLException + */ + protected void addDisplayTable(Division mainDiv, StatisticsTable display, boolean addRowTitles, String []valueMessagePrefixes) + throws SAXException, WingException, SQLException, + SolrServerException, IOException, ParseException { + + String title = display.getTitle(); + + Dataset dataset = display.getDataset(); + + if (dataset == null) + { + /** activate dataset query */ + dataset = display.getDataset(context); + } + + if (dataset != null) + { + + String[][] matrix = dataset.getMatrix(); + + if(matrix.length == 0){ + //If no results are found alert the user of this ! + mainDiv.addPara(getNoResultsMessage()); + return; + } + + /** Generate Table */ + Division wrapper = mainDiv.addDivision("tablewrapper"); + Table table = wrapper.addTable("list-table", 1, 1, + title == null ? "detailtable" : "tableWithTitle detailtable"); + if (title != null) + { + table.setHead(message(title)); + } + + /** Generate Header Row */ + Row headerRow = table.addRow(); + if(addRowTitles) + { + headerRow.addCell("spacer", Cell.ROLE_HEADER, "labelcell"); + } + + String[] cLabels = dataset.getColLabels().toArray(new String[0]); + for (int row = 0; row < cLabels.length; row++) + { + Cell cell = headerRow.addCell(0 + "-" + row + "-h", Cell.ROLE_HEADER, "labelcell"); + cell.addContent(message("xmlui.statistics.display.table.column-label." + cLabels[row])); + } + + /** Generate Table Body */ + for (int row = 0; row < matrix.length; row++) { + Row valListRow = table.addRow(); + + if(addRowTitles){ + /** Add Row Title */ + valListRow.addCell("" + row, Cell.ROLE_DATA, "labelcell") + .addContent(dataset.getRowLabels().get(row)); + } + + /** Add Rest of Row */ + for (int col = 0; col < matrix[row].length; col++) { + Cell cell = valListRow.addCell(row + "-" + col, + Cell.ROLE_DATA, "datacell"); + String messagePrefix = null; + if(valueMessagePrefixes != null && col < valueMessagePrefixes.length){ + messagePrefix = valueMessagePrefixes[col]; + } + + if(messagePrefix != null){ + cell.addContent(message(messagePrefix + matrix[row][col])); + }else{ + cell.addContent(matrix[row][col]); + } + } + } + } + } + + protected abstract Message getNoResultsMessage(); +} diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/Navigation.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/Navigation.java index 33a11b7d5b..597e2e8838 100644 --- a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/Navigation.java +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/Navigation.java @@ -15,10 +15,15 @@ import org.dspace.app.xmlui.wing.Message; import org.dspace.app.xmlui.utils.UIException; import org.dspace.app.xmlui.utils.HandleUtil; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.AuthorizeManager; import org.dspace.content.DSpaceObject; import org.apache.cocoon.caching.CacheableProcessingComponent; import org.apache.excalibur.source.SourceValidity; import org.apache.excalibur.source.impl.validity.NOPValidity; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.dspace.utils.DSpace; import org.xml.sax.SAXException; import java.io.Serializable; @@ -35,7 +40,9 @@ import java.sql.SQLException; public class Navigation extends AbstractDSpaceTransformer implements CacheableProcessingComponent { private static final Message T_statistics_head = message("xmlui.statistics.Navigation.title"); - private static final Message T_statistics_view = message("xmlui.statistics.Navigation.view"); + private static final Message T_statistics_usage_view = message("xmlui.statistics.Navigation.usage.view"); + private static final Message T_statistics_search_view = message("xmlui.statistics.Navigation.search.view"); + private static final Message T_statistics_workflow_view = message("xmlui.statistics.Navigation.workflow.view"); public Serializable getKey() { //TODO: DO THIS @@ -67,17 +74,46 @@ public class Navigation extends AbstractDSpaceTransformer implements CacheablePr List statistics = options.addList("statistics"); DSpaceObject dso = HandleUtil.obtainHandle(objectModel); + boolean displayUsageStats = displayStatsType(context, "usage", dso); + boolean displaySearchStats = displayStatsType(context, "search", dso); + boolean displayWorkflowStats = displayStatsType(context, "workflow", dso); + + if(dso != null && dso.getHandle() != null){ statistics.setHead(T_statistics_head); - statistics.addItemXref(contextPath + "/handle/" + dso.getHandle() + "/statistics", T_statistics_view); + if(displayUsageStats){ + statistics.addItemXref(contextPath + "/handle/" + dso.getHandle() + "/statistics", T_statistics_usage_view); + } + //Items cannot have search statistics + if(displaySearchStats && dso.getType() != Constants.ITEM){ + statistics.addItemXref(contextPath + "/handle/" + dso.getHandle() + "/search-statistics", T_statistics_search_view); + } + //Items cannot have workflow statistics + if(displayWorkflowStats && dso.getType() != Constants.ITEM){ + statistics.addItemXref(contextPath + "/handle/" + dso.getHandle() + "/workflow-statistics", T_statistics_workflow_view); + } }else{ // This Navigation is only called either on a DSO related page, or the homepage // If on the home page: add statistics link for the home page statistics.setHead(T_statistics_head); - statistics.addItemXref(contextPath + "/statistics-home", T_statistics_view); + if(displayUsageStats){ + statistics.addItemXref(contextPath + "/statistics-home", T_statistics_usage_view.parameterize()); + } + if(displaySearchStats){ + statistics.addItemXref(contextPath + "/search-statistics", T_statistics_search_view); + } + if(displayWorkflowStats){ + statistics.addItemXref(contextPath + "/workflow-statistics", T_statistics_workflow_view); + } } } + + protected boolean displayStatsType(Context context, String type, DSpaceObject dso) throws SQLException { + ConfigurationService cs = new DSpace().getConfigurationService(); + return !cs.getPropertyAsType("usage-statistics.authorization.admin." + type, Boolean.TRUE) || AuthorizeManager.isAdmin(context, dso); + + } } diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/SearchResultLogAction.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/SearchResultLogAction.java new file mode 100644 index 0000000000..3df2a5fa7a --- /dev/null +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/SearchResultLogAction.java @@ -0,0 +1,80 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.xmlui.aspect.statistics; + +import org.apache.avalon.framework.parameters.Parameters; +import org.apache.cocoon.acting.AbstractAction; +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Redirector; +import org.apache.cocoon.environment.Request; +import org.apache.cocoon.environment.SourceResolver; +import org.apache.cocoon.environment.http.HttpEnvironment; +import org.apache.commons.lang.StringUtils; +import org.dspace.app.xmlui.utils.ContextUtil; +import org.dspace.app.xmlui.utils.HandleUtil; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.handle.HandleManager; +import org.dspace.usage.UsageEvent; +import org.dspace.usage.UsageSearchEvent; +import org.dspace.utils.DSpace; + +import javax.servlet.http.HttpServletResponse; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Action that fires a search event & redirect the user to the view page of the object clicked on in the search results + * + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class SearchResultLogAction extends AbstractAction { + + @Override + public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception { + Request request = ObjectModelHelper.getRequest(objectModel); + Context context = ContextUtil.obtainContext(objectModel); + DSpaceObject scope = HandleUtil.obtainHandle(objectModel); + + String redirectUrl = request.getParameter("redirectUrl"); + String resultHandle = StringUtils.substringAfter(redirectUrl, "/handle/"); + DSpaceObject result = HandleManager.resolveToObject(ContextUtil.obtainContext(request), resultHandle); + + //Fire an event to log our search result + UsageSearchEvent searchEvent = new UsageSearchEvent( + UsageEvent.Action.SEARCH, + request, + context, + result, + Arrays.asList(request.getParameterValues("query")), scope); + + if(!StringUtils.isBlank(request.getParameter("rpp"))){ + searchEvent.setRpp(Integer.parseInt(request.getParameter("rpp"))); + } + if(!StringUtils.isBlank(request.getParameter("sort_by"))){ + searchEvent.setSortBy(request.getParameter("sort_by")); + } + if(!StringUtils.isBlank(request.getParameter("order"))){ + searchEvent.setSortOrder(request.getParameter("order")); + } + if(!StringUtils.isBlank(request.getParameter("page"))){ + searchEvent.setPage(Integer.parseInt(request.getParameter("page"))); + } + + new DSpace().getEventService().fireEvent( + searchEvent); + + HttpServletResponse httpResponse = (HttpServletResponse) objectModel.get(HttpEnvironment.HTTP_RESPONSE_OBJECT); + httpResponse.sendRedirect(redirectUrl); + + return new HashMap(); + } +} diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsAuthorizedMatcher.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsAuthorizedMatcher.java index 78b26ad396..f9cbd9ac68 100644 --- a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsAuthorizedMatcher.java +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsAuthorizedMatcher.java @@ -1,95 +1,111 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.xmlui.aspect.statistics; - -import org.apache.cocoon.matching.Matcher; -import org.apache.cocoon.sitemap.PatternException; -import org.apache.avalon.framework.parameters.Parameters; -import org.apache.avalon.framework.logger.AbstractLogEnabled; -import org.dspace.core.Context; -import org.dspace.core.ConfigurationManager; -import org.dspace.core.Constants; -import org.dspace.app.xmlui.utils.ContextUtil; -import org.dspace.app.xmlui.utils.HandleUtil; -import org.dspace.content.DSpaceObject; -import org.dspace.authorize.AuthorizeManager; - -import java.util.Map; -import java.util.HashMap; -import java.sql.SQLException; - -/** - * User: @author kevinvandevelde (kevin at atmire.com) - * Date: 19-nov-2009 - * Time: 17:19:56 - */ -public class StatisticsAuthorizedMatcher extends AbstractLogEnabled implements Matcher{ - - - public Map match(String pattern, Map objectModel, Parameters parameters) throws PatternException { - // Are we checking for *NOT* the action or the action. - boolean not = false; - int action = Constants.READ; // the action to check - - if (pattern.startsWith("!")) - { - not = true; - pattern = pattern.substring(1); - } - - if(!pattern.equals("READ")) - { - getLogger().warn("Invalid action: '"+pattern+"'"); - return null; - } - - try - { - Context context = ContextUtil.obtainContext(objectModel); - DSpaceObject dso = HandleUtil.obtainHandle(objectModel); - - //We have always got rights to view stats on the home page (admin rights will be checked later) - boolean authorized = dso == null || AuthorizeManager.authorizeActionBoolean(context, dso, action, false); - //If we are authorized check for any other authorization actions present - if(authorized && ConfigurationManager.getBooleanProperty("usage-statistics", "authorization.admin")) - { - //If we have no user, we cannot be admin - if(context.getCurrentUser() == null) - { - authorized = false; - } - - if(authorized){ - //Check for admin - authorized = AuthorizeManager.isAdmin(context); - if(!authorized) - { - //Check if we have authorization for the owning colls, comms, ... - authorized = AuthorizeManager.isAdmin(context, dso); - } - } - } - - // XOR - if (not ^ authorized) - { - return new HashMap(); - } - else - { - return null; - } - - - } - catch (SQLException sqle) - { - throw new PatternException("Unable to obtain DSpace Context", sqle); - } - } -} +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.xmlui.aspect.statistics; + +import org.apache.cocoon.matching.Matcher; +import org.apache.cocoon.sitemap.PatternException; +import org.apache.avalon.framework.parameters.Parameters; +import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.dspace.core.Context; +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Constants; +import org.dspace.app.xmlui.utils.ContextUtil; +import org.dspace.app.xmlui.utils.HandleUtil; +import org.dspace.content.DSpaceObject; +import org.dspace.authorize.AuthorizeManager; + +import java.util.Map; +import java.util.HashMap; +import java.sql.SQLException; + +/** + * User: @author kevinvandevelde (kevin at atmire.com) + * Date: 19-nov-2009 + * Time: 17:19:56 + */ +public class StatisticsAuthorizedMatcher extends AbstractLogEnabled implements Matcher{ + + + public Map match(String pattern, Map objectModel, Parameters parameters) throws PatternException { + String[] statisticsDisplayTypes = parameters.getParameter("type", "").split(","); + + // Are we checking for *NOT* the action or the action. + boolean not = false; + int action = Constants.READ; // the action to check + + if (pattern.startsWith("!")) + { + not = true; + pattern = pattern.substring(1); + } + + if(!pattern.equals("READ")) + { + getLogger().warn("Invalid action: '"+pattern+"'"); + return null; + } + + try + { + Context context = ContextUtil.obtainContext(objectModel); + DSpaceObject dso = HandleUtil.obtainHandle(objectModel); + + //We have always got rights to view stats on the home page (admin rights will be checked later) + boolean authorized = dso == null || AuthorizeManager.authorizeActionBoolean(context, dso, action, false); + //Check if (one of our) display type is admin only + //If one of the given ones isn't admin only, no need to check ! + boolean adminCheckNeeded = true; + for (String statisticsDisplayType : statisticsDisplayTypes) { + //Only usage statics are available on an item level + if(!"usage".equals(statisticsDisplayType) && dso != null && dso.getType() == Constants.ITEM){ + continue; + } + //If one isn't admin enabled no need to check for admin + if(!ConfigurationManager.getBooleanProperty("usage-statistics", "authorization.admin." + statisticsDisplayType, true)){ + adminCheckNeeded = false; + } + } + + //If we are authorized check for any other authorization actions present + if(authorized && adminCheckNeeded) + { + //If we have no user, we cannot be admin + if(context.getCurrentUser() == null) + { + authorized = false; + } + + if(authorized){ + //Check for admin + authorized = AuthorizeManager.isAdmin(context); + if(!authorized) + { + //Check if we have authorization for the owning colls, comms, ... + authorized = AuthorizeManager.isAdmin(context, dso); + } + } + } + + // XOR + if (not ^ authorized) + { + return new HashMap(); + } + else + { + return null; + } + + + } + catch (SQLException sqle) + { + throw new PatternException("Unable to obtain DSpace Context", sqle); + } + } +} diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsSearchResultTransformer.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsSearchResultTransformer.java new file mode 100644 index 0000000000..da62d01afd --- /dev/null +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsSearchResultTransformer.java @@ -0,0 +1,114 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.xmlui.aspect.statistics; + +import org.apache.cocoon.ProcessingException; +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Request; +import org.apache.commons.lang.StringUtils; +import org.dspace.app.xmlui.aspect.artifactbrowser.AdvancedSearchUtils; +import org.dspace.app.xmlui.cocoon.AbstractDSpaceTransformer; +import org.dspace.app.xmlui.utils.HandleUtil; +import org.dspace.app.xmlui.utils.UIException; +import org.dspace.app.xmlui.wing.WingException; +import org.dspace.app.xmlui.wing.element.Body; +import org.dspace.app.xmlui.wing.element.Division; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.handle.HandleManager; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.sql.SQLException; + +/** + * Transformer that adds a hidden form + * which will be submitted each time an dspace object link is clicked on a lucene search page + * This form will ensure that the results clicked after each search are logged for the search statistics + * + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class StatisticsSearchResultTransformer extends AbstractDSpaceTransformer { + + + @Override + public void addBody(Body body) throws SAXException, WingException, SQLException, IOException, AuthorizeException, ProcessingException { + Request request = ObjectModelHelper.getRequest(objectModel); + StringBuilder formUrl = new StringBuilder(); + formUrl.append(request.getContextPath()); + DSpaceObject scope = getScope(); + if(scope != null){ + formUrl.append("/handle/").append(scope.getHandle()); + } + formUrl.append("/dso-display"); + + Division mainForm = body.addInteractiveDivision("dso-display", formUrl.toString(), Division.METHOD_POST, ""); + + mainForm.addHidden("query").setValue(getQuery()); + if(!StringUtils.isBlank(request.getParameter("rpp"))){ + mainForm.addHidden("rpp").setValue(Integer.parseInt(request.getParameter("rpp"))); + } + if(!StringUtils.isBlank(request.getParameter("sort_by"))){ + mainForm.addHidden("sort_by").setValue(request.getParameter("sort_by")); + } + if(!StringUtils.isBlank(request.getParameter("order"))){ + mainForm.addHidden("order").setValue(request.getParameter("order")); + } + if(!StringUtils.isBlank(request.getParameter("page"))){ + mainForm.addHidden("page").setValue(Integer.parseInt(request.getParameter("page"))); + } + + + //This hidden input will contain the resulting url to which we redirect once our work has been completed + mainForm.addHidden("redirectUrl"); + } + + private String getQuery() throws UIException { + Request request = ObjectModelHelper.getRequest(objectModel); + if(parameters.getParameterAsBoolean("advanced-search", false)){ + return AdvancedSearchUtils.buildQuery(AdvancedSearchUtils.getSearchFields(request)); + }else{ + String query = decodeFromURL(request.getParameter("query")); + if (query == null) + { + return ""; + } + return query; + } + } + + /** + * Determine the current scope. This may be derived from the current url + * handle if present or the scope parameter is given. If no scope is + * specified then null is returned. + * + * @return The current scope. + */ + protected DSpaceObject getScope() throws SQLException + { + Request request = ObjectModelHelper.getRequest(objectModel); + String scopeString = request.getParameter("scope"); + + // Are we in a community or collection? + DSpaceObject dso; + if (scopeString == null || "".equals(scopeString)) + { + // get the search scope from the url handle + dso = HandleUtil.obtainHandle(objectModel); + } + else + { + // Get the search scope from the location parameter + dso = HandleManager.resolveToObject(context, scopeString); + } + + return dso; + } +} diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsSearchTransformer.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsSearchTransformer.java new file mode 100644 index 0000000000..458737b7b0 --- /dev/null +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsSearchTransformer.java @@ -0,0 +1,136 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.xmlui.aspect.statistics; + +import org.apache.cocoon.ProcessingException; +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Request; +import org.dspace.app.xmlui.utils.HandleUtil; +import org.dspace.app.xmlui.utils.UIException; +import org.dspace.app.xmlui.wing.Message; +import org.dspace.app.xmlui.wing.WingException; +import org.dspace.app.xmlui.wing.element.Body; +import org.dspace.app.xmlui.wing.element.Division; +import org.dspace.app.xmlui.wing.element.PageMeta; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.statistics.content.DatasetSearchGenerator; +import org.dspace.statistics.content.StatisticsDataSearches; +import org.dspace.statistics.content.StatisticsTable; +import org.dspace.statistics.content.filter.StatisticsSolrDateFilter; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.sql.SQLException; + +/** + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class StatisticsSearchTransformer extends AbstractStatisticsDataTransformer { + + private static final Message T_dspace_home = message("xmlui.general.dspace_home"); + private static final Message T_search_terms_head = message("xmlui.statistics.StatisticsSearchTransformer.search-terms.head"); + private static final Message T_search_total_head = message("xmlui.statistics.StatisticsSearchTransformer.search-total.head"); + private static final Message T_trail = message("xmlui.statistics.trail-search"); + private static final Message T_head_title = message("xmlui.statistics.search.title"); + private static final Message T_retrieval_error = message("xmlui.statistics.search.error"); + private static final Message T_search_head = message("xmlui.statistics.search.head"); + private static final Message T_search_head_dso = message("xmlui.statistics.search.head-dso"); + private static final Message T_no_results = message("xmlui.statistics.search.no-results"); + + /** + * Add a page title and trail links + */ + public void addPageMeta(PageMeta pageMeta) throws SAXException, WingException, SQLException, IOException, AuthorizeException { + //Try to find our dspace object + DSpaceObject dso = HandleUtil.obtainHandle(objectModel); + + pageMeta.addTrailLink(contextPath + "/",T_dspace_home); + + if(dso != null) + { + HandleUtil.buildHandleTrail(dso, pageMeta, contextPath); + } + pageMeta.addTrailLink(contextPath + (dso != null && dso.getHandle() != null ? "/handle/" + dso.getHandle() : "") + "/search-statistics", T_trail); + + // Add the page title + pageMeta.addMetadata("title").addContent(T_head_title); + } + + + @Override + public void addBody(Body body) throws SAXException, WingException, SQLException, IOException, AuthorizeException, ProcessingException { + //Try to find our dspace object + DSpaceObject dso = HandleUtil.obtainHandle(objectModel); + Request request = ObjectModelHelper.getRequest(objectModel); + String selectedTimeFilter = request.getParameter("time_filter"); + + StringBuilder actionPath = new StringBuilder().append(request.getContextPath()); + if(dso != null){ + actionPath.append("/handle/").append(dso.getHandle()); + } + actionPath.append("/search-statistics"); + + Division mainDivision = body.addInteractiveDivision("search-statistics", actionPath.toString(), Division.METHOD_POST, null); + if(dso != null){ + mainDivision.setHead(T_search_head_dso.parameterize(dso.getName())); + }else{ + mainDivision.setHead(T_search_head); + } + try { + //Add the time filter box + Division searchTermsDivision = mainDivision.addDivision("search-terms"); + searchTermsDivision.setHead(T_search_terms_head); + addTimeFilter(searchTermsDivision); + + //Retrieve the optional time filter + StatisticsSolrDateFilter dateFilter = getDateFilter(selectedTimeFilter); + + + StatisticsTable statisticsTable = new StatisticsTable(new StatisticsDataSearches(dso)); + + DatasetSearchGenerator queryGenerator = new DatasetSearchGenerator(); + queryGenerator.setType("query"); + queryGenerator.setMax(10); + queryGenerator.setMode(DatasetSearchGenerator.Mode.SEARCH_OVERVIEW); + queryGenerator.setPercentage(true); + queryGenerator.setRetrievePageViews(true); + statisticsTable.addDatasetGenerator(queryGenerator); + if(dateFilter != null){ + statisticsTable.addFilter(dateFilter); + } + + addDisplayTable(searchTermsDivision, statisticsTable, true, null); + + + Division totalDivision = mainDivision.addDivision("search-total"); + totalDivision.setHead(T_search_total_head); + statisticsTable = new StatisticsTable(new StatisticsDataSearches(dso)); + + queryGenerator = new DatasetSearchGenerator(); + queryGenerator.setMode(DatasetSearchGenerator.Mode.SEARCH_OVERVIEW_TOTAL); + queryGenerator.setPercentage(true); + queryGenerator.setRetrievePageViews(true); + statisticsTable.addDatasetGenerator(queryGenerator); + if(dateFilter != null){ + statisticsTable.addFilter(dateFilter); + } + + addDisplayTable(totalDivision, statisticsTable, false, null); + } catch (Exception e) { + mainDivision.addPara().addContent(T_retrieval_error); + } + } + + @Override + protected Message getNoResultsMessage() { + return T_no_results; + } +} diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsTransformer.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsTransformer.java index f373cc96d1..e05457ff6b 100644 --- a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsTransformer.java +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsTransformer.java @@ -326,7 +326,7 @@ public class StatisticsTransformer extends AbstractDSpaceTransformer { if (dataset != null) { - String[][] matrix = dataset.getMatrixFormatted(); + String[][] matrix = dataset.getMatrix(); /** Generate Table */ Division wrapper = mainDiv.addDivision("tablewrapper"); @@ -339,12 +339,12 @@ public class StatisticsTransformer extends AbstractDSpaceTransformer { /** Generate Header Row */ Row headerRow = table.addRow(); - headerRow.addCell("spacer", Cell.ROLE_DATA, "labelcell"); + headerRow.addCell("spacer", Cell.ROLE_HEADER, "labelcell"); String[] cLabels = dataset.getColLabels().toArray(new String[0]); for (int row = 0; row < cLabels.length; row++) { Cell cell = headerRow.addCell(0 + "-" + row + "-h", - Cell.ROLE_DATA, "labelcell"); + Cell.ROLE_HEADER, "labelcell"); cell.addContent(cLabels[row]); } @@ -382,7 +382,7 @@ public class StatisticsTransformer extends AbstractDSpaceTransformer { if (dataset != null) { - String[][] matrix = dataset.getMatrixFormatted(); + String[][] matrix = dataset.getMatrix(); // String[] rLabels = dataset.getRowLabels().toArray(new String[0]); @@ -395,9 +395,9 @@ public class StatisticsTransformer extends AbstractDSpaceTransformer { Row headerRow = table.addRow(); - headerRow.addCell("", Cell.ROLE_DATA, "labelcell"); + headerRow.addCell("", Cell.ROLE_HEADER, "labelcell"); - headerRow.addCell("", Cell.ROLE_DATA, "labelcell").addContent(message(T_head_visits_views)); + headerRow.addCell("", Cell.ROLE_HEADER, "labelcell").addContent(message(T_head_visits_views)); /** Generate Table Body */ for (int col = 0; col < matrix[0].length; col++) { diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsWorkflowTransformer.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsWorkflowTransformer.java new file mode 100644 index 0000000000..d361804a60 --- /dev/null +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/aspect/statistics/StatisticsWorkflowTransformer.java @@ -0,0 +1,126 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.xmlui.aspect.statistics; + +import org.apache.cocoon.ProcessingException; +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Request; +import org.dspace.app.util.Util; +import org.dspace.app.xmlui.utils.HandleUtil; +import org.dspace.app.xmlui.utils.UIException; +import org.dspace.app.xmlui.wing.Message; +import org.dspace.app.xmlui.wing.WingException; +import org.dspace.app.xmlui.wing.element.*; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.statistics.content.DatasetTypeGenerator; +import org.dspace.statistics.content.StatisticsDataWorkflow; +import org.dspace.statistics.content.StatisticsTable; +import org.dspace.statistics.content.filter.StatisticsSolrDateFilter; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.sql.SQLException; + +/** + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class StatisticsWorkflowTransformer extends AbstractStatisticsDataTransformer { + + private static final Message T_dspace_home = message("xmlui.general.dspace_home"); + private static final Message T_trail = message("xmlui.statistics.trail-workflow"); + private static final Message T_head_title = message("xmlui.statistics.workflow.title"); + private static final Message T_title = message("xmlui.statistics.workflow.title"); + private static final Message T_retrieval_error = message("xmlui.statistics.workflow.error"); + private static final Message T_no_results = message("xmlui.statistics.workflow.no-results"); + private static final Message T_workflow_head = message("xmlui.statistics.workflow.head"); + private static final Message T_workflow_head_dso = message("xmlui.statistics.workflow.head-dso"); + + /** + * Add a page title and trail links + */ + public void addPageMeta(PageMeta pageMeta) throws SAXException, WingException, UIException, SQLException, IOException, AuthorizeException { + //Try to find our dspace object + DSpaceObject dso = HandleUtil.obtainHandle(objectModel); + + pageMeta.addTrailLink(contextPath + "/",T_dspace_home); + + if(dso != null) + { + HandleUtil.buildHandleTrail(dso, pageMeta, contextPath); + } + pageMeta.addTrailLink(contextPath + (dso != null && dso.getHandle() != null ? "/handle/" + dso.getHandle() : "") + "/workflow-statistics", T_trail); + + // Add the page title + pageMeta.addMetadata("title").addContent(T_head_title); + } + + + @Override + public void addBody(Body body) throws SAXException, WingException, SQLException, IOException, AuthorizeException, ProcessingException { + //Try to find our dspace object + DSpaceObject dso = HandleUtil.obtainHandle(objectModel); + Request request = ObjectModelHelper.getRequest(objectModel); + String selectedTimeFilter = request.getParameter("time_filter"); + + + StringBuilder actionPath = new StringBuilder().append(contextPath); + if(dso != null){ + actionPath.append("/handle/").append(dso.getHandle()); + } + actionPath.append("/workflow-statistics"); + + Division mainDivision = body.addInteractiveDivision("workflow-statistics", actionPath.toString(), Division.METHOD_POST, null); + if(dso != null){ + mainDivision.setHead(T_workflow_head_dso.parameterize(dso.getName())); + }else{ + mainDivision.setHead(T_workflow_head); + } + try { + + //Add the time filter box + Division workflowTermsDivision = mainDivision.addDivision("workflow-terms"); + workflowTermsDivision.setHead(T_title); + addTimeFilter(workflowTermsDivision); + + //Retrieve the optional time filter + StatisticsSolrDateFilter dateFilter = getDateFilter(selectedTimeFilter); + + + + int time_filter = -1; + if(request.getParameter("time_filter") != null && !"".equals(request.getParameter("time_filter"))){ + //Our time filter is a negative value if present + time_filter = Math.abs(Util.getIntParameter(request, "time_filter")); + + } + StatisticsTable statisticsTable = new StatisticsTable(new StatisticsDataWorkflow(dso, time_filter)); + + DatasetTypeGenerator queryGenerator = new DatasetTypeGenerator(); + //Set our type to previousworkflow step (indicates our performed actions !) + queryGenerator.setType("previousWorkflowStep"); + queryGenerator.setMax(10); + statisticsTable.addDatasetGenerator(queryGenerator); + if(dateFilter != null){ + statisticsTable.addFilter(dateFilter); + } + + addDisplayTable(workflowTermsDivision, statisticsTable, true, new String[]{"xmlui.statistics.display.table.workflow.step."}); + } catch (Exception e) { + mainDivision.addPara().addContent(T_retrieval_error); + + } + } + + @Override + protected Message getNoResultsMessage() { + return T_no_results; + } +} diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/cocoon/SearchLoggerAction.java b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/cocoon/SearchLoggerAction.java new file mode 100644 index 0000000000..412c23e86d --- /dev/null +++ b/dspace-xmlui/dspace-xmlui-api/src/main/java/org/dspace/app/xmlui/cocoon/SearchLoggerAction.java @@ -0,0 +1,104 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.xmlui.cocoon; + +import org.apache.avalon.framework.parameters.Parameters; +import org.apache.cocoon.acting.AbstractAction; +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Redirector; +import org.apache.cocoon.environment.Request; +import org.apache.cocoon.environment.SourceResolver; +import org.apache.commons.lang.StringUtils; +import org.dspace.app.xmlui.utils.ContextUtil; +import org.dspace.app.xmlui.utils.HandleUtil; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.handle.HandleManager; +import org.dspace.usage.UsageEvent; +import org.dspace.usage.UsageSearchEvent; +import org.dspace.utils.DSpace; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +/** + * Fires an event each time a search occurs + * + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public abstract class SearchLoggerAction extends AbstractAction { + + @Override + public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception { + Request request = ObjectModelHelper.getRequest(objectModel); + Context context = ContextUtil.obtainContext(objectModel); + DSpaceObject scope = getScope(context, objectModel); + + UsageSearchEvent searchEvent = new UsageSearchEvent( + UsageEvent.Action.SEARCH, + request, + context, + null, getQueries(request), scope); + + if(!StringUtils.isBlank(request.getParameter("rpp"))){ + searchEvent.setRpp(Integer.parseInt(request.getParameter("rpp"))); + } + if(!StringUtils.isBlank(request.getParameter("sort_by"))){ + searchEvent.setSortBy(request.getParameter("sort_by")); + } + if(!StringUtils.isBlank(request.getParameter("order"))){ + searchEvent.setSortOrder(request.getParameter("order")); + } + if(!StringUtils.isBlank(request.getParameter("page"))){ + searchEvent.setPage(Integer.parseInt(request.getParameter("page"))); + } + + //Fire our event + new DSpace().getEventService().fireEvent(searchEvent); + + + // Finished, allow to pass. + return null; + } + + protected abstract List getQueries(Request request) throws SQLException; + + + /** + * Determine the current scope. This may be derived from the current url + * handle if present or the scope parameter is given. If no scope is + * specified then null is returned. + * + * @return The current scope. + */ + protected DSpaceObject getScope(Context context, Map objectModel) throws SQLException + { + Request request = ObjectModelHelper.getRequest(objectModel); + String scopeString = request.getParameter("scope"); + + // Are we in a community or collection? + DSpaceObject dso; + if (scopeString == null || "".equals(scopeString)) + { + // get the search scope from the url handle + dso = HandleUtil.obtainHandle(objectModel); + } + else + { + // Get the search scope from the location parameter + dso = HandleManager.resolveToObject(context, scopeString); + } + + return dso; + } + + +} diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/SearchArtifacts/sitemap.xmap b/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/SearchArtifacts/sitemap.xmap index 7ba936007c..4bd741813a 100644 --- a/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/SearchArtifacts/sitemap.xmap +++ b/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/SearchArtifacts/sitemap.xmap @@ -51,6 +51,10 @@ and searching the repository. + + + + @@ -83,16 +87,19 @@ and searching the repository. - - + + + - - + + + - - + + + @@ -119,20 +126,23 @@ and searching the repository. - - + + + - - + + + - - + + + diff --git a/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/Statistics/sitemap.xmap b/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/Statistics/sitemap.xmap index 9101ef00f4..c3b9a011b2 100644 --- a/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/Statistics/sitemap.xmap +++ b/dspace-xmlui/dspace-xmlui-api/src/main/resources/aspects/Statistics/sitemap.xmap @@ -1,81 +1,289 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/i18n/messages.xml b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/i18n/messages.xml index b5fde035d5..712697bf13 100644 --- a/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/i18n/messages.xml +++ b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/i18n/messages.xml @@ -1898,6 +1898,12 @@ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!--> Statistics + Search Statistics + Search Statistics + Search Statistics for {0} + There was an error while generating the search statistics, please try again later. + No search statistics available for the selected period. + Workflow Statistics Total Visits Total Visits Per Month Views @@ -1905,8 +1911,53 @@ Top cities views File Visits Statistics - View Statistics + View Usage Statistics + View Search Statistics + View Workflow Statistics Statistics + Search Statistics + Workflow Statistics + No workflow statistics available for the selected period. + There was an error while generating the workflow statistics, please try again later. + Workflow Statistics + Workflow Statistics for {0} + + + Top Search Terms + Total + Previous month + Previous 6 months + Previous year + Overall + + + Search Term + Searches + % of Total + Pageviews / Search + Step + Performed + Average + Accept/Reject Step Pool + Accept/Reject Step + Accept/Reject/Edit Metadata Step Pool + Accept/Reject/Edit Metadata Step + Edit Metadata Step Pool + Edit Metadata Step + Accept/Reject Step Pool + Accept/Reject Step + Accept/Reject/Edit Metadata Step Pool + Accept/Reject/Edit Metadata Step + Edit Metadata Step Pool + Edit Metadata Step + Edit Metadata Step + Score Review Pool + Score Review + Score Review Evaluation + Score Review Evaluation Configuration + Single User Review Pool + Single User Auto Assign Action + Single User Review Action diff --git a/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/static/js/search-results.js b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/static/js/search-results.js new file mode 100644 index 0000000000..3443c0736b --- /dev/null +++ b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/static/js/search-results.js @@ -0,0 +1,32 @@ +/* + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +(function ($) { + + /** + * Function ensures that all the links clicked in our results pass through the internal logging mechanism + */ + $(document).ready(function() { + + + //Retrieve all links with handles attached (comm/coll/item links) + var urls = $('div#aspect_artifactbrowser_SimpleSearch_div_search-results,' + + 'div#aspect_artifactbrowser_AdvancedSearch_div_search-results').find('a'); + + urls.click(function(){ + var $this = $(this); + //Instead of redirecting us to the page, first send us to the statistics logger + //By doing this we ensure that we register the query to the result + var form = $('form#aspect_statistics_StatisticsSearchResultTransformer_div_dso-display'); + form.find('input[name="redirectUrl"]').val($this.attr('href')); + form.submit(); + return false; + }); + }); + + +})(jQuery); diff --git a/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/static/js/usage-statistics.js b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/static/js/usage-statistics.js new file mode 100644 index 0000000000..9634f55893 --- /dev/null +++ b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/static/js/usage-statistics.js @@ -0,0 +1,20 @@ +/* + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +(function ($) { + + /** + * Function ensures that when a new time filter is selected the form is submitted + */ + $(document).ready(function() { + $('select[name="time_filter"]').change(function(){ + $(this).parents('form:first').submit(); + + }); + + }); +})(jQuery); diff --git a/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/themes/Mirage/lib/css/style.css b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/themes/Mirage/lib/css/style.css index 2d2dcaaf34..e4880e5bf0 100644 --- a/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/themes/Mirage/lib/css/style.css +++ b/dspace-xmlui/dspace-xmlui-webapp/src/main/webapp/themes/Mirage/lib/css/style.css @@ -1237,6 +1237,20 @@ div.vocabulary-container li.error{ border: 1px solid lightgray; } +table.detailtable { + background-color: #D8E8EB; +} + +table.detailtable th{ + background-color: #F0F2F5; + word-wrap: normal; +} + +table.detailtable td{ + background-color: #FFFFFF; + text-align: right; +} + span.highlight{ font-weight: bold; diff --git a/dspace/config/modules/usage-statistics.cfg b/dspace/config/modules/usage-statistics.cfg index 4762a9875b..34fa6afac4 100644 --- a/dspace/config/modules/usage-statistics.cfg +++ b/dspace/config/modules/usage-statistics.cfg @@ -13,7 +13,12 @@ resolver.timeout = 200 # view the statistics. # If disabled, anyone with READ permissions on the DSpaceObject will be able # to view the statistics. -authorization.admin=true +#View/download statistics +authorization.admin.usage=true +#Search/search result statistics +authorization.admin.search=true +#Workflow result statistics +authorization.admin.workflow=true # Enable/disable logging of spiders in solr statistics. # If false, and IP matches an address in spiderips.urls, event is not logged. diff --git a/dspace/modules/solr/pom.xml b/dspace/modules/solr/pom.xml index 341aa3c7c1..feb9ea1504 100644 --- a/dspace/modules/solr/pom.xml +++ b/dspace/modules/solr/pom.xml @@ -21,6 +21,27 @@ org.apache.maven.plugins maven-war-plugin + + 2.1-alpha-2 + + + + + org.apache.solr + solr + + + WEB-INF/lib/apache-solr-core-3.5.0.jar + + WEB-INF/web.xml + + + + prepare-package @@ -33,7 +54,7 @@ org.dspace dspace-solr - 3.3.0.0 + 3.5.0.1 skinny war @@ -41,24 +62,11 @@ org.dspace dspace-solr - 3.3.0.0 + 3.5.0.1 classes jar - - org.slf4j - slf4j-api - 1.5.6 - - - - org.slf4j - slf4j-jdk14 - 1.5.6 - - - xalan diff --git a/dspace/solr/search/conf/solrconfig.xml b/dspace/solr/search/conf/solrconfig.xml index 2ccd3410cc..d203e19b19 100644 --- a/dspace/solr/search/conf/solrconfig.xml +++ b/dspace/solr/search/conf/solrconfig.xml @@ -32,6 +32,15 @@ --> ${solr.abortOnConfigurationError:true} + + + LUCENE_35 + - - @@ -71,9 +87,29 @@ - + + + + + false @@ -87,74 +123,86 @@ for buffering added documents and deletions before they are flushed to the Directory. --> 32 - + + + 10000 1000 10000 - - - - - - - - - single = SingleInstanceLockFactory - suggested for a read-only index - or when there is no possibility of another process trying - to modify the index. - native = NativeFSLockFactory - uses OS native file locking + native - + + + false 32 10 - - - - + true - - + - + false - + + + + - + - - + + - + - - - + - - + + - + 1024 - - + + @@ -326,65 +424,91 @@ initialSize="512" autowarmCount="0"/> - + - + - + - - true + + Cache used to hold field values that are quickly accessible + by document id. The fieldValueCache is created by default + even if not configured here. + --> + + + - + true + + + - 200 - @@ -1027,7 +1162,7 @@ - solr + *:* + @@ -277,8 +278,8 @@ best performance. --> - - + + @@ -286,8 +287,8 @@ - - + + @@ -295,9 +296,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + uid id diff --git a/pom.xml b/pom.xml index 4646582a44..65d4fb1bd2 100644 --- a/pom.xml +++ b/pom.xml @@ -694,7 +694,7 @@ commons-io commons-io - 1.4 + 2.3 commons-lang