/**
* 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.dspace.content.*;
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.statistics.content.filter.StatisticsSolrDateFilter;
import org.dspace.statistics.util.LocationUtils;
import org.dspace.core.Context;
import org.dspace.core.Constants;
import org.dspace.core.ConfigurationManager;
import org.dspace.handle.HandleManager;
import org.dspace.app.util.Util;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.util.ClientUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.sql.SQLException;
import java.text.ParseException;
import java.io.UnsupportedEncodingException;
/**
* Query factory associated with a DSpaceObject.
* Encapsulates the raw data, independent of rendering.
* <p>
* To use:
* <ol>
* <li>Instantiate, passing a reference to the interesting DSO.</li>
* <li>Add a {@link DatasetDSpaceObjectGenerator} for the appropriate object type.</li>
* <li>Add other generators as required to get the statistic you want.</li>
* <li>Add {@link org.dspace.statistics.content.filter filters} as required.</li>
* <li>{@link #createDataset(Context)} will run the query and return a result matrix.
* Subsequent calls skip the query and return the same matrix.</li>
* </ol>
*
* @author kevinvandevelde at atmire.com
* Date: 23-feb-2009
* Time: 12:25:20
*/
public class StatisticsDataVisits extends StatisticsData
{
/** Current DSpaceObject for which to generate the statistics. */
private DSpaceObject currentDso;
/** Construct a completely uninitialized query. */
public StatisticsDataVisits()
{
}
/** Construct an empty query concerning a given DSpaceObject. */
public StatisticsDataVisits(DSpaceObject dso)
{
super();
this.currentDso = dso;
}
/** Construct an unconfigured query around a given DSO and Dataset. */
public StatisticsDataVisits(DSpaceObject currentDso, Dataset dataset)
{
super(dataset);
this.currentDso = currentDso;
}
/** Construct an unconfigured query around a given Dataset. */
public StatisticsDataVisits(Dataset dataset)
{
super(dataset);
}
public Dataset createDataset(Context context) throws SQLException,
SolrServerException, ParseException
{
//Check if we already have one.
//If we do then give it back.
if(getDataset() != null)
{
return getDataset();
}
///////////////////////////
// 1. DETERMINE OUR AXIS //
///////////////////////////
ArrayList<DatasetQuery> datasetQueries = new ArrayList<DatasetQuery>();
for (int i = 0; i < getDatasetGenerators().size(); i++) {
DatasetGenerator dataSet = getDatasetGenerators().get(i);
processAxis(dataSet, datasetQueries);
}
//Now lets determine our values.
//First check if we have a date facet & if so find it.
DatasetTimeGenerator dateFacet = null;
if (getDatasetGenerators().get(0) instanceof DatasetTimeGenerator
|| (1 < getDatasetGenerators().size() && getDatasetGenerators()
.get(1) instanceof DatasetTimeGenerator))
{
if(getDatasetGenerators().get(0) instanceof DatasetTimeGenerator)
{
dateFacet = (DatasetTimeGenerator) getDatasetGenerators().get(0);
}
else
{
dateFacet = (DatasetTimeGenerator) getDatasetGenerators().get(1);
}
}
/////////////////////////
// 2. DETERMINE VALUES //
/////////////////////////
boolean showTotal = false;
//Check if we need our total
if ((getDatasetGenerators().get(0) != null && getDatasetGenerators()
.get(0).isIncludeTotal())
|| (1 < getDatasetGenerators().size()
&& getDatasetGenerators().get(1) != null && getDatasetGenerators()
.get(1).isIncludeTotal()))
{
showTotal = true;
}
if (dateFacet != null && dateFacet.getActualStartDate() != null
&& dateFacet.getActualEndDate() != null)
{
StatisticsSolrDateFilter dateFilter = new StatisticsSolrDateFilter();
dateFilter.setStartDate(dateFacet.getActualStartDate());
dateFilter.setEndDate(dateFacet.getActualEndDate());
dateFilter.setTypeStr(dateFacet.getDateType());
addFilters(dateFilter);
}
else if (dateFacet != null && dateFacet.getStartDate() != null
&& dateFacet.getEndDate() != null)
{
StatisticsSolrDateFilter dateFilter = new StatisticsSolrDateFilter();
dateFilter.setStartStr(dateFacet.getStartDate());
dateFilter.setEndStr(dateFacet.getEndDate());
dateFilter.setTypeStr(dateFacet.getDateType());
addFilters(dateFilter);
}
//Determine our filterQuery
String filterQuery = null;
for (int i = 0; i < getFilters().size(); i++) {
if(filterQuery == null)
{
filterQuery = "";
}
StatisticsFilter filter = getFilters().get(i);
filterQuery += "(" + filter.toQuery() + ")";
if(i != (getFilters().size() -1))
{
filterQuery += " AND ";
}
}
// System.out.println("FILTERQUERY: " + filterQuery);
//We determine our values on the queries resolved above
Dataset dataset = null;
//Run over our queries.
//First how many queries do we have ?
if(dateFacet != null){
//So do all the queries and THEN do the date facet
for (int i = 0; i < datasetQueries.size(); i++) {
DatasetQuery dataSetQuery = datasetQueries.get(i);
if(dataSetQuery.getQueries().size() != 1){
//TODO: do this
}else{
String query = dataSetQuery.getQueries().get(0).getQuery();
if(dataSetQuery.getMax() == -1){
//We are asking from our current query all the visits faceted by date
ObjectCount[] results = SolrLogger.queryFacetDate(query, filterQuery, dataSetQuery.getMax(), dateFacet.getDateType(), dateFacet.getStartDate(), dateFacet.getEndDate(), showTotal);
dataset = new Dataset(1, results.length);
//Now that we have our results put em in a matrix
for(int j = 0; j < results.length; j++){
dataset.setColLabel(j, results[j].getValue());
dataset.addValueToMatrix(0, j, results[j].getCount());
}
//TODO: change this !
//Now add the column label
dataset.setRowLabel(0, getResultName(dataSetQuery.getName(), dataSetQuery, context));
dataset.setRowLabelAttr(0, getAttributes(dataSetQuery.getName(), dataSetQuery, context));
}else{
//We need to get the max objects and the next part of the query on them (next part beeing the datasettimequery
ObjectCount[] maxObjectCounts = SolrLogger.queryFacetField(query, filterQuery, dataSetQuery.getFacetField(), dataSetQuery.getMax(), false, null);
for (int j = 0; j < maxObjectCounts.length; j++) {
ObjectCount firstCount = maxObjectCounts[j];
String newQuery = dataSetQuery.getFacetField() + ": " + ClientUtils.escapeQueryChars(firstCount.getValue()) + " AND " + query;
ObjectCount[] maxDateFacetCounts = SolrLogger.queryFacetDate(newQuery, filterQuery, dataSetQuery.getMax(), dateFacet.getDateType(), dateFacet.getStartDate(), dateFacet.getEndDate(), showTotal);
//Make sure we have a dataSet
if(dataset == null)
{
dataset = new Dataset(maxObjectCounts.length, maxDateFacetCounts.length);
}
//TODO: this is a very dirty fix change this ! ! ! ! ! !
dataset.setRowLabel(j, getResultName(firstCount.getValue(), dataSetQuery, context));
dataset.setRowLabelAttr(j, getAttributes(firstCount.getValue(), dataSetQuery, context));
for (int k = 0; k < maxDateFacetCounts.length; k++) {
ObjectCount objectCount = maxDateFacetCounts[k];
//No need to add this many times
if(j == 0)
{
dataset.setColLabel(k, objectCount.getValue());
}
dataset.addValueToMatrix(j, k, objectCount.getCount());
}
}
if(dataset != null && !(getDatasetGenerators().get(0) instanceof DatasetTimeGenerator)){
dataset.flipRowCols();
}
}
}
}
}else{
//We do NOT have a date facet so just do queries after each other
/*
for (int i = 0; i < datasetQueries.size(); i++) {
DatasetQuery datasetQuery = datasetQueries.get(i);
if(datasetQuery.getQueries().size() != 1){
//TODO: do this
}else{
String query = datasetQuery.getQueries().get(0);
//Loop over the queries & do em
// ObjectCount[] topCounts = SolrLogger.queryFacetField(query, );
}
}
*/
DatasetQuery firsDataset = datasetQueries.get(0);
//Do the first query
ObjectCount[] topCounts1 = null;
// if(firsDataset.getQueries().size() == 1){
topCounts1 = queryFacetField(firsDataset, firsDataset.getQueries().get(0).getQuery(), filterQuery);
// }else{
// TODO: do this
// }
//Check if we have more queries that need to be done
if(datasetQueries.size() == 2){
DatasetQuery secondDataSet = datasetQueries.get(1);
//Now do the second one
ObjectCount[] topCounts2 = queryFacetField(secondDataSet, secondDataSet.getQueries().get(0).getQuery(), filterQuery);
//Now that have results for both of them lets do x.y queries
List<String> facetQueries = new ArrayList<String>();
for (ObjectCount count2 : topCounts2) {
String facetQuery = secondDataSet.getFacetField() + ":" + ClientUtils.escapeQueryChars(count2.getValue());
//Check if we also have a type present (if so this should be put into the query
if ("id".equals(secondDataSet.getFacetField()) && secondDataSet.getQueries().get(0).getDsoType() != -1)
{
facetQuery += " AND type:" + secondDataSet.getQueries().get(0).getDsoType();
}
facetQueries.add(facetQuery);
}
for (int i = 0; i < topCounts1.length; i++){
ObjectCount count1 = topCounts1[i];
ObjectCount[] currentResult = new ObjectCount[topCounts2.length];
//Make sure we have a dataSet
if(dataset == null)
{
dataset = new Dataset(topCounts2.length, topCounts1.length);
}
dataset.setColLabel(i, getResultName(count1.getValue(), firsDataset, context));
dataset.setColLabelAttr(i, getAttributes(count1.getValue(), firsDataset, context));
String query = firsDataset.getFacetField() + ":" + ClientUtils.escapeQueryChars(count1.getValue());
//Check if we also have a type present (if so this should be put into the query
if("id".equals(firsDataset.getFacetField()) && firsDataset.getQueries().get(0).getDsoType() != -1)
{
query += " AND type:" + firsDataset.getQueries().get(0).getDsoType();
}
Map<String, Integer> facetResult = SolrLogger.queryFacetQuery(query, filterQuery, facetQueries);
//TODO: the show total
//No need to add this many times
//TODO: dit vervangen door te displayen value
for (int j = 0; j < topCounts2.length; j++) {
ObjectCount count2 = topCounts2[j];
if(i == 0) {
dataset.setRowLabel(j, getResultName(count2.getValue(), secondDataSet, context));
dataset.setRowLabelAttr(j, getAttributes(count2.getValue(), secondDataSet, context));
}
//Get our value the value is the same as the query
String facetQuery = secondDataSet.getFacetField() + ":" + ClientUtils.escapeQueryChars(count2.getValue());
//Check if we also have a type present (if so this should be put into the query
if ("id".equals(secondDataSet.getFacetField()) && secondDataSet.getQueries().get(0).getDsoType() != -1)
{
facetQuery += " AND type:" + secondDataSet.getQueries().get(0).getDsoType();
}
//We got our query so now get the value
dataset.addValueToMatrix(j, i, facetResult.get(facetQuery));
}
/*
for (int j = 0; j < topCounts2.length; j++) {
ObjectCount count2 = topCounts2[j];
String query = firsDataset.getFacetField() + ":" + count1.getValue();
//Check if we also have a type present (if so this should be put into the query
if("id".equals(firsDataset.getFacetField()) && firsDataset.getQueries().get(0).getDsoType() != -1)
query += " AND type:" + firsDataset.getQueries().get(0).getDsoType();
query += " AND " + secondDataSet.getFacetField() + ":" + count2.getValue();
//Check if we also have a type present (if so this should be put into the query
if("id".equals(secondDataSet.getFacetField()) && secondDataSet.getQueries().get(0).getDsoType() != -1)
query += " AND type:" + secondDataSet.getQueries().get(0).getDsoType();
long count = SolrLogger.queryFacetQuery(query, filterQuery);
//TODO: the show total
//No need to add this many times
//TODo: dit vervangen door te displayen value
if(i == 0) {
dataset.setRowLabel(j, getResultName(count2.getValue(), secondDataSet, context));
dataset.setRowLabelAttr(j, getAttributes(count2.getValue(), secondDataSet, context));
}
dataset.addValueToMatrix(j, i, count);
}
*/
}
// System.out.println("BOTH");
} else{
//Make sure we have a dataSet
dataset = new Dataset(1, topCounts1.length);
for (int i = 0; i < topCounts1.length; i++) {
ObjectCount count = topCounts1[i];
dataset.setColLabel(i, getResultName(count.getValue(), firsDataset, context));
dataset.setColLabelAttr(i, getAttributes(count.getValue(), firsDataset, context));
dataset.addValueToMatrix(0, i, count.getCount());
}
}
}
if(dataset != null){
dataset.setRowTitle("Dataset 1");
dataset.setColTitle("Dataset 2");
}else
{
dataset = new Dataset(0, 0);
}
return dataset;
}
private void processAxis(DatasetGenerator datasetGenerator, List<DatasetQuery> queries) throws SQLException {
if(datasetGenerator instanceof DatasetDSpaceObjectGenerator){
DatasetDSpaceObjectGenerator dspaceObjAxis = (DatasetDSpaceObjectGenerator) datasetGenerator;
//Get the types involved
List<DSORepresentation> dsoRepresentations = dspaceObjAxis.getDsoRepresentations();
for (int i = 0; i < dsoRepresentations.size(); i++){
DatasetQuery datasetQuery = new DatasetQuery();
Integer dsoType = dsoRepresentations.get(i).getType();
boolean seperate = dsoRepresentations.get(i).getSeparate();
Integer dsoLength = dsoRepresentations.get(i).getNameLength();
//Check if our type is our current object
if(currentDso != null && dsoType == currentDso.getType()){
Query query = new Query();
query.setDso(currentDso.getID(), currentDso.getType(), dsoLength);
datasetQuery.addQuery(query);
}else{
//TODO: only do this for bitstreams from an item
Query query = new Query();
if(currentDso != null && seperate && dsoType == Constants.BITSTREAM){
//CURRENTLY THIS IS ONLY POSSIBLE FOR AN ITEM ! ! ! ! ! ! !
//We need to get the separate bitstreams from our item and make a query for each of them
Item item = (Item) currentDso;
for (int j = 0; j < item.getBundles().length; j++) {
Bundle bundle = item.getBundles()[j];
for (int k = 0; k < bundle.getBitstreams().length; k++) {
Bitstream bitstream = bundle.getBitstreams()[k];
if(!bitstream.getFormat().isInternal()){
//Add a separate query for each bitstream
query.setDso(bitstream.getID(), bitstream.getType(), dsoLength);
}
}
}
} else {
//We have something else than our current object.
//So we need some kind of children from it, so put this in our query
query.setOwningDso(currentDso);
query.setDsoLength(dsoLength);
String title = "";
switch(dsoType){
case Constants.BITSTREAM:
title = "Files";
break;
case Constants.ITEM:
title = "Items";
break;
case Constants.COLLECTION:
title = "Collections";
break;
case Constants.COMMUNITY:
title = "Communities";
break;
}
datasetQuery.setName(title);
//Put the type in so we only get the children of the type specified
query.setDsoType(dsoType);
}
datasetQuery.addQuery(query);
}
datasetQuery.setFacetField("id");
datasetQuery.setMax(dsoRepresentations.get(i).getMax());
queries.add(datasetQuery);
}
}else
if(datasetGenerator instanceof DatasetTypeGenerator){
DatasetTypeGenerator typeAxis = (DatasetTypeGenerator) datasetGenerator;
DatasetQuery datasetQuery = new DatasetQuery();
//First make sure our query is in order
Query query = new Query();
if(currentDso != null)
{
query.setDso(currentDso.getID(), currentDso.getType());
}
datasetQuery.addQuery(query);
//Then add the rest
datasetQuery.setMax(typeAxis.getMax());
datasetQuery.setFacetField(typeAxis.getType());
datasetQuery.setName(typeAxis.getType());
queries.add(datasetQuery);
}
}
/**
* Gets the name of the DSO (example for collection: ((Collection) dso).getname();
* @return the name of the given DSO
*/
private String getResultName(String value, DatasetQuery datasetQuery,
Context context) throws SQLException
{
if("continent".equals(datasetQuery.getName())){
value = LocationUtils.getContinentName(value);
}else
if("countryCode".equals(datasetQuery.getName())){
value = LocationUtils.getCountryName(value);
}else{
Query query = datasetQuery.getQueries().get(0);
//TODO: CHANGE & THROW AWAY THIS ENTIRE METHOD
//Check if int
int dsoId;
int dsoLength = query.getDsoLength();
try {
dsoId = Integer.parseInt(value);
}catch(Exception e){
dsoId = -1;
}
if(dsoId == -1 && query.getDsoId() != -1 && value == null)
{
dsoId = query.getDsoId();
}
if(dsoId != -1 && query.dsoType != -1){
DSpaceObject dso = DSpaceObject.find(context, query.getDsoType(), dsoId);
if(dso != null){
switch(dso.getType()){
case Constants.BITSTREAM:
Bitstream bit = (Bitstream) dso;
return bit.getName();
case Constants.ITEM:
Item item = (Item) dso;
String name = "untitled";
DCValue[] vals = item.getMetadata("dc", "title", null, Item.ANY);
if(vals != null && 0 < vals.length)
{
name = vals[0].value;
}
if(dsoLength != -1 && name.length() > dsoLength){
//Cut it off at the first space
int firstSpace = name.indexOf(' ', dsoLength);
if(firstSpace != -1){
name = name.substring(0, firstSpace) + " ...";
}
}
return name;
case Constants.COLLECTION:
Collection coll = (Collection) dso;
name = coll.getName();
if(dsoLength != -1 && name.length() > dsoLength){
//Cut it off at the first space
int firstSpace = name.indexOf(' ', dsoLength);
if(firstSpace != -1){
name = name.substring(0, firstSpace) + " ...";
}
}
return name;
case Constants.COMMUNITY:
Community comm = (Community) dso;
name = comm.getName();
if(dsoLength != -1 && name.length() > dsoLength){
//Cut it off at the first space
int firstSpace = name.indexOf(' ', dsoLength);
if(firstSpace != -1){
name = name.substring(0, firstSpace) + " ...";
}
}
return name;
}
}
}
}
return value;
}
private Map<String, String> getAttributes(String value,
DatasetQuery datasetQuery, Context context) throws SQLException
{
HashMap<String, String> attrs = new HashMap<String, String>();
Query query = datasetQuery.getQueries().get(0);
//TODO: CHANGE & THROW AWAY THIS ENTIRE METHOD
//Check if int
int dsoId;
try {
dsoId = Integer.parseInt(value);
}catch(Exception e){
dsoId = -1;
}
if(dsoId == -1 && query.getDsoId() != -1 && value == null)
{
dsoId = query.getDsoId();
}
if(dsoId != -1 && query.dsoType != -1){
DSpaceObject dso = DSpaceObject.find(context, query.getDsoType(), dsoId);
if(dso != null){
switch(dso.getType()){
case Constants.BITSTREAM:
Bitstream bit = (Bitstream) dso;
//Get our owning item
Item owningItem = null;
Bundle[] bunds = bit.getBundles();
if(0 < bunds.length && 0 < bunds[0].getItems().length)
{
owningItem = bunds[0].getItems()[0];
}
// If possible reference this bitstream via a handle, however this may
// be null if a handle has not yet been assigned. In this case refrence the
// item its internal id. In the last case where the bitstream is not associated
// with an item (such as a community logo) then reference the bitstreamID directly.
String identifier = null;
if (owningItem != null && owningItem.getHandle() != null)
{
identifier = "handle/" + owningItem.getHandle();
}
else if (owningItem != null)
{
identifier = "item/" + owningItem.getID();
}
else
{
identifier = "id/" + bit.getID();
}
String url = ConfigurationManager.getProperty("dspace.url") + "/bitstream/"+identifier+"/";
// If we can put the pretty name of the bitstream on the end of the URL
try
{
if (bit.getName() != null)
{
url += Util.encodeBitstreamName(bit.getName(), "UTF-8");
}
}
catch (UnsupportedEncodingException uee)
{
// Just ignore it: we don't have to have a pretty
// name on the end of the URL because the sequence id will
// locate it. However it means that links in this file might
// not work....
}
url += "?sequence="+bit.getSequenceID();
attrs.put("url", url);
break;
case Constants.ITEM:
Item item = (Item) dso;
attrs.put("url", HandleManager.resolveToURL(context, item.getHandle()));
break;
case Constants.COLLECTION:
Collection coll = (Collection) dso;
attrs.put("url", HandleManager.resolveToURL(context, coll.getHandle()));
break;
case Constants.COMMUNITY:
Community comm = (Community) dso;
attrs.put("url", HandleManager.resolveToURL(context, comm.getHandle()));
break;
}
}
}
return attrs;
}
private ObjectCount[] queryFacetField(DatasetQuery dataset, String query,
String filterQuery) throws SolrServerException
{
String facetType = dataset.getFacetField() == null ? "id" : dataset
.getFacetField();
return SolrLogger.queryFacetField(query, filterQuery, facetType,
dataset.getMax(), false, null);
}
public static class DatasetQuery {
private String name;
private int max;
private String facetField;
private List<Query> queries;
public DatasetQuery() {
queries = new ArrayList<Query>();
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
public void addQuery(Query q){
queries.add(q);
}
public List<Query> getQueries() {
return queries;
}
public String getFacetField() {
return facetField;
}
public void setFacetField(String facetField) {
this.facetField = facetField;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Query {
private int dsoType;
private int dsoId;
private int dsoLength;
private DSpaceObject owningDso;
public Query() {
dsoId = -1;
dsoType = -1;
dsoLength = -1;
owningDso = null;
}
public void setOwningDso(DSpaceObject owningDso) {
this.owningDso = owningDso;
}
public void setDso(int dsoId, int dsoType){
this.dsoId = dsoId;
this.dsoType = dsoType;
}
public void setDso(int dsoId, int dsoType, int length){
this.dsoId = dsoId;
this.dsoType = dsoType;
this.dsoLength = length;
}
public void setDsoType(int dsoType) {
this.dsoType = dsoType;
}
public int getDsoLength() {
return dsoLength;
}
public void setDsoLength(int dsoLength) {
this.dsoLength = dsoLength;
}
public int getDsoId() {
return dsoId;
}
public int getDsoType(){
return dsoType;
}
public String getQueryResultName(){
//TODO: This has got to be done differently in case we have a string query.
//This is just a temporary solution so we can get on with our work.
return dsoType + ":" + dsoId;
}
public String getQuery() {
//Time to construct our query
String query = "";
//Check (& add if needed) the dsoType
if(dsoType != -1)
{
query += "type: " + dsoType;
}
//Check (& add if needed) the dsoId
if(dsoId != -1)
{
query += (query.equals("") ? "" : " AND ") + " id:" + dsoId;
}
if(owningDso != null && currentDso != null){
query += (query.equals("") ? "" : " AND " );
String owningStr = "";
switch(currentDso.getType()){
case Constants.ITEM:
owningStr = "owningItem";
break;
case Constants.COLLECTION:
owningStr = "owningColl";
break;
case Constants.COMMUNITY:
owningStr = "owningComm";
break;
}
owningStr += ":" + currentDso.getID();
query += owningStr;
}
if(query.equals(""))
{
query = "*:*";
}
return query;
}
}
}