/** * 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; } } }