/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/citations/trunk/citations-impl/impl/src/java/org/sakaiproject/citation/impl/BaseSearchManager.java $
* $Id: BaseSearchManager.java 119081 2013-01-29 19:11:52Z dgcliff@iu.edu $
***********************************************************************************
*
* Copyright (c) 2006, 2007, 2008, 2009 The Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
package org.sakaiproject.citation.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;
import net.sf.ehcache.Cache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osid.OsidContext;
import org.osid.OsidException;
import org.osid.repository.Asset;
import org.osid.repository.AssetIterator;
import org.osid.repository.Repository;
import org.osid.repository.RepositoryException;
import org.osid.repository.RepositoryIterator;
import org.osid.repository.RepositoryManager;
import org.osid.shared.ObjectIterator;
import org.osid.shared.SharedException;
import org.osid.shared.Type;
import org.osid.shared.TypeIterator;
import org.sakaibrary.xserver.session.MetasearchSessionManager;
import org.sakaiproject.authz.api.SecurityAdvisor;
import org.sakaiproject.authz.api.SecurityAdvisor.SecurityAdvice;
import org.sakaiproject.authz.cover.SecurityService;
import org.sakaiproject.citation.util.api.CQLSearchQuery;
import org.sakaiproject.citation.util.api.OsidConfigurationException;
import org.sakaiproject.citation.util.api.SearchCancelException;
import org.sakaiproject.citation.util.api.SearchQuery;
import org.sakaiproject.citation.api.ActiveSearch;
import org.sakaiproject.citation.api.Citation;
import org.sakaiproject.citation.api.CitationCollection;
import org.sakaiproject.citation.api.CitationIterator;
import org.sakaiproject.citation.api.ConfigurationService;
import org.sakaiproject.citation.api.SearchCategory;
import org.sakaiproject.citation.api.SearchDatabase;
import org.sakaiproject.citation.api.SearchDatabaseHierarchy;
import org.sakaiproject.citation.api.SearchManager;
import org.sakaiproject.citation.cover.CitationService;
import org.sakaiproject.citation.util.api.SearchException;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.content.api.ContentResource;
import org.sakaiproject.content.cover.ContentHostingService;
import org.sakaiproject.entity.api.Entity;
import org.sakaiproject.entity.api.Reference;
import org.sakaiproject.entity.cover.EntityManager;
import org.sakaiproject.event.api.Event;
import org.sakaiproject.event.cover.EventTrackingService;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.exception.ServerOverloadException;
import org.sakaiproject.exception.TypeException;
import org.sakaiproject.time.cover.TimeService;
import org.sakaiproject.tool.api.SessionManager;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import edu.indiana.lib.twinpeaks.util.SessionContext;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
/**
*
*/
public class BaseSearchManager implements SearchManager, Observer
{
/**
* Maximum number of consecutive duplicate result records we'll accept
* before disabling the duplicate record check for the current search.
*
* The idea is to avoid displaying occasional duplicates, but don't
* discard forever in the pathological case where we're only getting
* duplicate results.
*/
protected static int MAX_DUPLICATES = 10;
public class BasicObjectIterator
implements ObjectIterator
{
protected int i = 0;
protected Vector vector = new Vector();
public BasicObjectIterator(List keys)
throws SharedException
{
this.vector = new Vector(keys);
}
public boolean hasNextObject()
throws SharedException
{
return i < vector.size();
}
public java.io.Serializable nextObject()
throws SharedException
{
if (i < vector.size())
{
return (java.io.Serializable)vector.elementAt(i++);
}
else
{
throw new SharedException(SharedException.NO_MORE_ITERATOR_ELEMENTS);
}
}
}
public class BasicSearch implements ActiveSearch
{
protected List m_assets;
protected List m_pageOrder;
protected Set m_duplicateCheck;
protected boolean m_duplicateCheckEnabled;
protected boolean m_firstPage;
protected String m_searchId;
protected String m_searchType;
protected boolean m_lastPage;
protected boolean m_newSearch;
protected Integer m_pageSize;
protected Integer m_startRecord;
protected String[] m_databaseIds;
protected AssetIterator m_assetIterator;
protected Integer m_numRecordsFetched;
protected Integer m_numRecordsFound;
protected Integer m_numRecordsMerged;
protected Repository m_repository;
protected String m_repositoryId;
protected String m_repositoryName;
protected SearchQuery m_basicQuery;
protected SearchQuery m_advancedQuery;
protected String m_sortBy;
protected CitationCollection m_searchResults;
protected CitationCollection m_savedResults;
protected CitationIterator m_resultsIterator;
protected Map m_index;
protected int m_lastPageViewed = -1;
protected CitationIterator m_searchIterator;
protected int start = 1;
protected int end = DEFAULT_PAGE_SIZE;
protected int m_viewPageSize = DEFAULT_PAGE_SIZE;
protected String statusMessage = null;
// saves the thread that the current search is running in
protected Thread m_searchThread;
/**
* Constructor
*/
public BasicSearch()
{
this.m_searchId = newSearchId();
this.m_searchType = null;
this.m_assets = new Vector();
m_pageOrder = new Vector();
this.m_index = new Hashtable();
m_duplicateCheck = new TreeSet();
m_duplicateCheckEnabled = true;
m_savedResults = CitationService.getTemporaryCollection();
m_newSearch = true;
m_firstPage = true;
m_lastPage = false;
m_pageSize = new Integer(DEFAULT_PAGE_SIZE);
m_startRecord = new Integer(DEFAULT_START_RECORD);
m_sortBy = DEFAULT_SORT_BY;
m_databaseIds = null;
}
/**
*/
public BasicSearch(CitationCollection searchResults)
{
this.m_searchId = newSearchId();
this.m_assets = new Vector();
m_pageOrder = new Vector();
this.m_index = new Hashtable();
m_duplicateCheck = new TreeSet();
m_duplicateCheckEnabled = true;
m_savedResults = CitationService.getTemporaryCollection();
m_newSearch = true;
m_firstPage = true;
m_lastPage = false;
m_pageSize = new Integer(DEFAULT_PAGE_SIZE);
m_startRecord = new Integer(DEFAULT_START_RECORD);
m_sortBy = DEFAULT_SORT_BY;
m_databaseIds = null;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getAssetIterator()
*/
protected AssetIterator getAssetIterator()
{
return m_assetIterator;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getAssets()
*/
public List getAssets()
{
return m_assets;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getGuid()
*/
public String getSearchId()
{
return m_searchId;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getNumRecordsFetched()
*/
public Integer getNumRecordsFetched()
{
return m_numRecordsFetched;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getNumRecordsFound()
*/
public Integer getNumRecordsFound()
{
return m_numRecordsFound;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getNumRecordsMerged()
*/
public Integer getNumRecordsMerged()
{
return m_numRecordsMerged;
}
protected void setPageLimits(int page) throws SearchException
{
}
/**
* @param page
* @return
* @throws SearchException
*/
public List viewPage(int page) throws SearchException, SearchCancelException
{
List citations = new Vector();
boolean searchPerformed = false;
if(page < 0)
{
page = 0;
}
int oldStart = this.start;
int oldEnd = this.end;
this.start = page * m_viewPageSize;
this.end = start + m_viewPageSize;
if(start > this.m_pageOrder.size() + 1)
{
throw new SearchException("Request beyond next page");
}
else
{
if(this.m_pageOrder.isEmpty())
{
doSearch(this);
searchPerformed = true;
}
else if(end > this.m_pageOrder.size())
{
try
{
doNextPage(this);
searchPerformed = true;
}
catch (SearchException e)
{
this.start = oldStart;
this.end = oldEnd;
setStatusMessage(m_repository);
throw new SearchException(e.getMessage());
}
}
}
/*
* Determine the proper the "last page" setting.
*/
m_log.debug(">>> viewPage() new page is " + page
+ ", last page is " + m_lastPageViewed);
m_log.debug(">>> viewPage() was a search done? " + searchPerformed);
m_log.debug(">>> viewPage() did we find the last page? " + this.isLastPage());
m_log.debug(">>> viewPage() records found = " + getNumRecordsFetched()
+ ", records rendered = " + m_pageOrder.size());
/*
* Step 1: Previous pages (and the first) are a special case
*/
if (page < m_lastPageViewed)
{
setLastPage(false);
}
/*
* Step 2: Re-evaluate the "last page" status if one of these is true:
*
* o This is a previous (or the first) page
* o This was purely a page size adjustment (no search required)
* o A search was performed (and it didn't hit "end-of-search-results")
*/
if ((page < m_lastPageViewed)
|| (!searchPerformed)
|| (searchPerformed && !isLastPage()))
{
int estimatedHits = getNumRecordsFound();
int hitsRendered = m_pageOrder.size();
int pageHits = (page == 0) ? m_viewPageSize
: ((page + 1) * m_viewPageSize);
/*
* Step 3: This is the last page if:
*
* o The estimated number of possible results will fit on the
* current page
* or
* o The number of results actually rendered is less than the
* current page size (we ran out)
*/
m_log.debug(">>> viewPage() estimate ("
+ estimatedHits
+ ") <= page size (in hits) ("
+ pageHits
+ ") ? "
+ (estimatedHits <= pageHits));
m_log.debug(">>> viewPage() records rendered ("
+ hitsRendered
+ ") < page size (in hits) ("
+ pageHits
+ ") ? "
+ (hitsRendered < pageHits));
if ((estimatedHits <= pageHits) || (hitsRendered < pageHits))
{
setLastPage(true);
}
}
if(end > m_pageOrder.size())
{
end = m_pageOrder.size();
}
Citation citation = null;
for(int i = start; i < end; i++)
{
String id = (String) m_pageOrder.get(i);
try
{
citation = m_searchResults.getCitation(id);
citations.add(citation);
}
catch (IdUnusedException e)
{
m_log.warn("BasicSearch.getPage() unable to retrieve ciataion: " + id);
}
}
m_lastPageViewed = page;
return citations;
}
/**
*
*/
protected void setStatusMessage(Repository repository)
{
try
{
this.statusMessage = getSearchStatusMessage(repository);
}
catch(SearchException e)
{
this.statusMessage = e.getMessage();
}
}
public void setStatusMessage(String msg)
{
this.statusMessage = msg;
}
public void setStatusMessage()
{
this.statusMessage = null;
}
public String getStatusMessage()
{
return this.statusMessage;
}
/**
* Set the selected list of searchable databases
* @param A list of database IDs
*/
public void setDatabaseIds(String[] databaseIds)
{
m_databaseIds = databaseIds;
}
/**
* Fetch the selected list of searchable databases
* @return The list of database IDs (null if none)
*/
public String[] getDatabaseIds()
{
return m_databaseIds;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getPageSize()
*/
public Integer getPageSize()
{
return m_pageSize;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getRepository()
*/
public Repository getRepository()
{
return m_repository;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getRepositoryId()
*/
public String getRepositoryId()
{
return m_repositoryId;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getRepositoryName()
*/
public String getRepositoryName()
{
return m_repositoryName;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getSearchCriteria()
*/
public SearchQuery getBasicQuery()
{
return m_basicQuery;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getSortBy()
*/
public String getSortBy()
{
return m_sortBy.toLowerCase();
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getStartRecord()
*/
public Integer getStartRecord()
{
if(m_startRecord.intValue() < MIN_START_RECORD)
{
m_startRecord = new Integer(MIN_START_RECORD);
}
return m_startRecord;
}
/**
* @return the firstPage
*/
public boolean isFirstPage()
{
return m_firstPage;
}
/**
* @return the lastPage
*/
public boolean isLastPage()
{
return m_lastPage;
}
/**
* @return the newSearch
*/
public boolean isNewSearch()
{
return m_newSearch;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setAssetIterator(org.osid.repository.AssetIterator)
*/
public void setAssetIterator(AssetIterator assetIterator)
{
this.m_assetIterator = assetIterator;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setAssets(java.util.List)
*/
public void setAssets(List assets)
{
this.m_assets = assets;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setFirstPage(boolean)
*/
public void setFirstPage(boolean firstPage)
{
this.m_firstPage = firstPage;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setGuid(java.lang.String)
*/
public void setGuid(String guid)
{
this.m_searchId = guid;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setLastPage(boolean)
*/
public void setLastPage(boolean lastPage)
{
this.m_lastPage = lastPage;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setNewSearch(boolean)
*/
public void setNewSearch(boolean newSearch)
{
this.m_newSearch = newSearch;
}
/**
* @param numRecordsFetched the numRecordsFetched to set
*/
public void setNumRecordsFetched(Integer numRecordsFetched)
{
m_numRecordsFetched = numRecordsFetched;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setNumRecordsFound(java.lang.Integer)
*/
public void setNumRecordsFound(Integer numRecordsFound)
{
this.m_numRecordsFound = numRecordsFound;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setNumRecordsMerged(java.lang.Integer)
*/
public void setNumRecordsMerged(Integer numRecordsMerged)
{
this.m_numRecordsMerged = numRecordsMerged;
}
/**
* @param pageSize the pageSize to set
*/
public void setPageSize(Integer pageSize)
{
if(pageSize == null || pageSize.intValue() < 1)
{
m_pageSize = new Integer(DEFAULT_PAGE_SIZE);
}
else
{
m_pageSize = pageSize;
}
}
/**
* @param pageSize the pageSize to set
*/
public void setPageSize(String pageSize)
{
if(pageSize == null || pageSize.trim().equals(""))
{
m_pageSize = new Integer(DEFAULT_PAGE_SIZE);
}
else
{
try
{
m_pageSize = new Integer(pageSize);
}
catch(NumberFormatException e)
{
m_pageSize = new Integer(DEFAULT_PAGE_SIZE);
}
}
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setRepository(org.osid.repository.Repository)
*/
public void setRepository(Repository repository)
{
this.m_repository = repository;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setRepositoryId(java.lang.String)
*/
public void setRepositoryId(String repositoryId)
{
this.m_repositoryId = repositoryId;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setRepositoryName(java.lang.String)
*/
public void setRepositoryName(String repositoryName)
{
this.m_repositoryName = repositoryName;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setSearchCriteria(java.lang.String)
*/
public void setBasicQuery(SearchQuery basicQuery)
{
this.m_basicQuery = basicQuery;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setSortBy(java.lang.String)
*/
public void setSortBy(String sortBy)
{
this.m_sortBy = sortBy;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setStartRecord(java.lang.Integer)
*/
public void setStartRecord(Integer startRecord)
{
if(startRecord.intValue() < MIN_START_RECORD)
{
this.m_startRecord = new Integer(MIN_START_RECORD);
}
else
{
this.m_startRecord = startRecord;
}
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getSearchResults()
*/
public CitationCollection getSearchResults()
{
return m_searchResults;
}
/**
* @param searchResults the searchResults to set
*/
public void setSearchResults(CitationCollection searchResults)
{
m_searchResults = searchResults;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setStartRecord(java.lang.String)
*/
public void setStartRecord(String startRecord)
{
if(startRecord == null || startRecord.trim().equals(""))
{
m_startRecord = new Integer(DEFAULT_START_RECORD);
}
else
{
try
{
m_startRecord = new Integer(startRecord);
}
catch(NumberFormatException e)
{
m_startRecord = new Integer(DEFAULT_START_RECORD);
}
}
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getIndex()
*/
public Map getIndex()
{
return m_index;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#setIndex(java.util.Map)
*/
public void setIndex(Map index)
{
m_index = new Hashtable( index );
}
/**
* @return
*/
public Set getDuplicateCheck()
{
if(m_duplicateCheck == null)
{
m_duplicateCheck = new TreeSet();
}
return m_duplicateCheck;
}
/**
* Are we checking for duplicate search results?
* @return true if so
*/
public boolean isDuplicateCheckEnabled()
{
return m_duplicateCheckEnabled;
}
/**
* Enable/disable duplicate checking
* @param state true to enable the duplicate check
*/
public void setDuplicateCheckEnabled(boolean state)
{
m_duplicateCheckEnabled = state;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#prepareForNextPage()
*
* As far as I can tell, this is no longer used. At one time it was
* referenced from CitationsHelperAction. SRS, 03/34/09
*/
public void prepareForNextPage()
{
Iterator it = m_searchResults.getCitations().iterator();
while(it.hasNext())
{
Citation citation = (Citation) it.next();
if(! m_pageOrder.contains(citation.getId()))
{
m_pageOrder.add(citation.getId());
}
}
this.m_savedResults.addAll(this.m_searchResults);
this.m_searchResults.clear();
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getLastPageViewed()
*/
public int getViewPageNumber()
{
return m_lastPageViewed ;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#getLastPageViewed()
*/
public int getViewPageSize()
{
return m_viewPageSize ;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.ActiveSearch#viewPage()
*/
public List viewPage() throws SearchException, SearchCancelException
{
return viewPage(0);
}
public int getFirstRecordIndex()
{
return start;
}
public int getLastRecordIndex()
{
return end;
}
public void setViewPageSize(int size)
{
m_viewPageSize = size;
}
public String getSearchType() {
return m_searchType;
}
public void setSearchType(String searchType) {
m_searchType = searchType;
}
public SearchQuery getAdvancedQuery() {
return m_advancedQuery;
}
public void setAdvancedQuery(SearchQuery advancedQuery) {
m_advancedQuery = advancedQuery;
}
public Thread getSearchThread() {
return m_searchThread;
}
public void setSearchThread(Thread searchThread) {
m_searchThread = searchThread;
}
public void resetSearch()
{
this.m_pageOrder.clear();
this.m_searchResults.clear();
}
}
public class BasicSearchProperties implements org.osid.shared.Properties
{
protected List keys;
protected java.util.Properties properties;
protected Type type = new BasicType( "sakaibrary", "properties", "asynchMetasearch" );
public BasicSearchProperties( java.util.Properties properties )
{
this.keys = new Vector();
this.properties = properties;
Enumeration keyNames = properties.keys();
while( keyNames.hasMoreElements() ) {
this.keys.add( (java.io.Serializable)keyNames.nextElement() );
}
}
public ObjectIterator getKeys()
throws SharedException
{
return new BasicObjectIterator( keys );
}
public java.io.Serializable getProperty( java.io.Serializable key )
throws SharedException
{
return (java.io.Serializable)properties.get( key );
}
public Type getType()
throws SharedException
{
return type;
}
}
public class BasicType extends org.osid.shared.Type
{
protected BasicType(String authority
, String domain
, String keyword)
{
super(authority,domain,keyword);
}
public BasicType(String authority
, String domain
, String keyword
, String description)
{
super(authority,domain,keyword,description);
}
// public final Type CITATION = new BasicType("sakaibrary", "recordStructure", "citation");
// public final Type CREATOR = new BasicType("mit.edu", "partStructure", "creator");
// public final Type DATE = new BasicType("mit.edu", "partStructure", "date");
// public final Type DATE_RETRIEVED = new BasicType("sakaibrary", "partStructure", "dateRetrieved");
// public final Type DOI = new BasicType("sakaibrary", "partStructure", "doi");
// public final Type EDITION = new BasicType("sakaibrary", "partStructure", "edition");
// public final Type END_PAGE = new BasicType("sakaibrary", "partStructure", "endPage");
// public final Type INLINE_CITATION = new BasicType("sakaibrary", "partStructure", "inLineCitation");
// public final Type ISN_IDENTIFIER = new BasicType("sakaibrary", "partStructure", "isnIdentifier");
// public final Type ISSUE = new BasicType("sakaibrary", "partStructure", "issue");
// public final Type LANGUAGE = new BasicType("mit.edu", "partStructure", "language");
// public final Type LOC_IDENTIFIER = new BasicType("sakaibrary", "partStructure", "locIdentifier");
// public final Type NOTE = new BasicType("sakaibrary", "partStructure", "note");
// public final Type OPEN_URL = new BasicType("sakaibrary", "partStructure", "openUrl");
// public final Type PAGES = new BasicType("sakaibrary", "partStructure", "pages");
// public final Type PUB_LOCATION = new BasicType("sakaibrary", "partStructure", "publicationLocation");
// public final Type PUBLISHER = new BasicType("mit.edu", "partStructure", "publisher");
// public final Type RIGHTS = new BasicType("sakaibrary", "partStructure", "rights");
// public final Type SOURCE_TITLE = new BasicType("sakaibrary", "partStructure", "sourceTitle");
// public final Type START_PAGE = new BasicType("sakaibrary", "partStructure", "startPage");
// public final Type SUBJECT = new BasicType("mit.edu", "partStructure", "subject");
// public final Type TYPE = new BasicType("mit.edu", "partStructure", "type");
// public final Type URL = new BasicType("mit.edu", "partStructure", "url");
// public final Type URL_FORMAT = new BasicType("sakaibrary", "partStructure", "urlFormat");
// public final Type URL_LABEL = new BasicType("sakaibrary", "partStructure", "urlLabel");
// public final Type VOLUME = new BasicType("sakaibrary", "partStructure", "volume");
// public final Type YEAR = new BasicType("sakaibrary", "partStructure", "year");
}
/**
* @author gbhatnag
*
*/
public class BasicSearchDatabaseHierarchy
extends org.xml.sax.helpers.DefaultHandler
implements SearchDatabaseHierarchy
{
public class BasicSearchCategory implements SearchCategory
{
private String id;
private String displayName;
private String description;
private boolean defaultStatus;
// list of sub-categories contained in this category (could be null)
private java.util.List<SearchCategory> subcategoryList;
// list of database ids contained in this category (could be null)
private java.util.List<String> databaseList;
// list of database ids that are recommended within this category
// (could be null)
private java.util.List<String> recommendedDatabases;
// map of databases with alternate metadata within this category
// keyed using database id
private java.util.Map<String, SearchDatabase> altDatabases;
/**
* BasicSearchCategory constructor creates a BasicSearchCategory
* with the given name and id
*
* @param name display name for this category
* @param id unique identifier for this category
*/
protected BasicSearchCategory( String name, String id )
{
this.id = id;
this.displayName = name;
this.description = null;
this.defaultStatus = false;
}
protected void updateDescription( String description )
{
this.description = description;
}
protected void addSubcategory( SearchCategory subcategory )
{
if( subcategory != null )
{
if( subcategoryList == null )
{
subcategoryList = new Vector<SearchCategory>();
}
subcategoryList.add( subcategory );
}
else
{
m_log.warn( "BasicSearchCategory.addSubCategory() was " +
"passed a null subcategory to add" );
}
}
protected void addDatabase( String databaseId )
{
if( databaseId != null )
{
if( databaseList == null )
{
databaseList = new Vector<String>();
}
databaseList.add( databaseId );
}
else
{
m_log.warn( "BasicSearchCategory.addDatabase() was " +
"passed a null databaseId to add" );
}
}
protected void addRecommendedDatabase( String databaseId )
{
if( databaseId != null )
{
if( recommendedDatabases == null )
{
recommendedDatabases = new Vector<String>();
}
recommendedDatabases.add( databaseId );
// if this database is not in the overall list of databases,
// add it
if( databaseList == null )
{
databaseList = new Vector<String>();
}
if( !databaseList.contains( databaseId ) )
{
databaseList.add( databaseId );
}
}
else
{
m_log.warn( "BasicSearchCategory.addRecommendedDatabase()" +
" was passed a null databaseId to add" );
}
}
protected void addAlternateDatabase( SearchDatabase altDatabase )
{
if( altDatabase != null )
{
if( altDatabases == null )
{
altDatabases = new Hashtable<String, SearchDatabase>();
}
altDatabases.put( altDatabase.getId(), altDatabase );
// if this database is not in the overall list of databases,
// add it
if( !databaseList.contains( altDatabase.getId() ) )
{
databaseList.add( altDatabase.getId() );
}
}
else
{
m_log.warn( "BasicSearchCategory.addAlternateDatabase() " +
"was passed a null SearchDatabase to add" );
}
}
protected void setDefault( boolean value )
{
this.defaultStatus = value;
}
protected boolean isDefault()
{
return defaultStatus;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchCategory#hasDatabases()
*/
public boolean hasDatabases() {
return ( databaseList != null && !databaseList.isEmpty() );
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchCategory#getDatabases()
*/
public List<SearchDatabase> getDatabases() {
// List to be returned
Vector<SearchDatabase> databases = new Vector<SearchDatabase>();
// make sure this category has databases in it
if( !hasDatabases() )
{
m_log.warn( "Search Library Resources Category: '" +
displayName + "' contains no databases." );
}
else
{
for( int i = 0; i < databaseList.size(); i++ )
{
String databaseId = databaseList.get(i);
SearchDatabase database;
// check if there is an alternate for this database
if( altDatabases != null &&
altDatabases.containsKey( databaseId ) )
{
database = altDatabases.get( databaseId );
}
else
{
// get the database from the global map of databases
database = databaseMap.get( databaseId );
}
// make sure we have found the database
if( database == null )
{
// database not found
m_log.warn( "Unidentified Search Libary Resources " +
"database: '" + databaseId +
"' in category: " + displayName );
}
else
{
// check if the database is a member of authorized groups
for( String groupId : groups )
{
if( database.isGroupMember( groupId ) )
{
// add to the return List
databases.add( database );
break;
}
}
}
}
}
return databases;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchCategory#getDescription()
*/
public String getDescription() {
return description;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchCategory#getDisplayName()
*/
public String getDisplayName() {
return displayName;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchCategory#getId()
*/
public String getId() {
return id;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchCategory#hasSubCategories()
*/
public boolean hasSubCategories() {
return ( subcategoryList != null && !subcategoryList.isEmpty() );
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchCategory#getSubCategories()
*/
public List<SearchCategory> getSubCategories() {
return subcategoryList;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchCategory#isDatabaseRecommended()
*/
public boolean isDatabaseRecommended( String databaseId ) {
return recommendedDatabases.contains( databaseId );
}
} // public class BasicSearchCategory
public class BasicSearchDatabase implements SearchDatabase
{
private String id;
private String displayName;
private String description;
// groups this database belongs to
private List<String> groups;
protected BasicSearchDatabase( String name, String id )
{
this.displayName = name;
this.id = id;
this.description = null;
}
protected void updateDescription( String description )
{
this.description = description;
}
protected void addGroup( String groupId )
{
if( groupId != null )
{
if( groups == null )
{
groups = new Vector<String>();
}
groups.add( groupId );
}
else
{
m_log.warn( "BasicSearchDatabase.addGroup() " +
"was passed a null groupId to add" );
}
}
public String getDescription() {
return description;
}
public String getDisplayName() {
return displayName;
}
public String getId() {
return id;
}
public boolean isGroupMember( String groupId ) {
return groups.contains( groupId );
}
} // public class BasicSearchDatabase
/* org.sakaiproject.citation
* BasicSearchDatabaseHierarchy instance variables
*/
// this user's repository and groups
protected String repositoryPkgName;
protected List<String> groups;
// root category (contains top level categories)
protected BasicSearchCategory rootCategory;
// map containing all databases, keyed by database id
protected java.util.Map<String, SearchDatabase> databaseMap;
// map containing all categories, keyed by category id
protected java.util.Map<String, SearchCategory> categoryMap;
// default category
protected SearchCategory defaultCategory;
// configured flag
protected boolean isConfigured;
// for SAX parsing
protected StringBuilder textBuffer;
protected boolean recommendedDatabaseFlag;
protected int hierarchyDepth;
protected java.util.Stack<BasicSearchCategory> categoryStack;
protected BasicSearchDatabase currentDatabase;
protected String currentDatabaseId;
public BasicSearchDatabaseHierarchy(String xmlContent)
{
/*
* Any basic user authn/authz things we can check to not go
* further than we have to?
*/
// get a ConfigurationService instance
if( m_configService == null )
{
m_log.warn( "BasicSearchDatabaseHierarchy() m_configService is " +
"null - components.xml injection did not work... getting instance from cover" );
m_configService = org.sakaiproject.citation.cover.ConfigurationService.getInstance();
}
isConfigured = false;
try
{
/*
* Determine which repository implementation this user should get
* access to
* - ip-based
* - other things?
*
* (currently assuming X-Server)
*/
// repositoryPkgName = "org.sakaibrary.osid.repository.xserver";
repositoryPkgName = m_configService.getSiteConfigOsidPackageName();
if(isNull(repositoryPkgName))
{
// cannot continue
return;
}
/*
* Now we know which metasearch engine, get the corresponding XML
* for that database
* - XML describes all accessible databases/categories for a given
* metasearch engine
*
* (currently assuming CATEGORIES_XML for all users)
*/
/*
* Determine which groups this user is a member of
* - should get an array of strings with group names/ids
* which should appear in the XML
*/
// String[] tempGroups = { "all", "free" };
// groups = tempGroups;
groups = m_configService.getGroupIds();
/*
* Parse the XML using the group information to build a hierarchy
* of categories and databases this user has access to
*/
recommendedDatabaseFlag = false;
hierarchyDepth = 0;
databaseMap = new java.util.Hashtable<String, SearchDatabase>();
categoryMap = new java.util.Hashtable<String, SearchCategory>();
categoryStack = new java.util.Stack<BasicSearchCategory>();
parseXML(xmlContent);
}
catch (Exception exception)
{
m_log.warn("Exception seen in BasicSearchDatabaseHierarchy() constructor", exception);
}
}
protected void parseXML(String xmlContent)
{
// Use the default (non-validating) parser
SAXParserFactory factory = SAXParserFactory.newInstance();
InputSource source = new InputSource(new StringReader(xmlContent));
try {
// Parse the input
SAXParser saxParser = factory.newSAXParser();
saxParser.parse( source, this );
m_log.debug("After parse, categories found = " + categoryMap.size());
isConfigured = (this.categoryMap.size() > 0) ? true : false;
} catch (SAXParseException spe) {
// Use the contained exception, if any
Exception x = spe;
if (spe.getException() != null) {
x = spe.getException();
}
// Error generated by the parser
m_log.warn("parseXML() parsing exception: " +
spe.getMessage() + " - xml line " + spe.getLineNumber()
+ ", uri " + spe.getSystemId(), x);
// unset configuration flag
isConfigured = false;
} catch (SAXException sxe) {
// Error generated by this application
// (or a parser-initialization error)
Exception x = sxe;
if (sxe.getException() != null) {
x = sxe.getException();
}
m_log.warn( "parseXML() SAX exception: " +
sxe.getMessage(), x );
// unset configuration flag
isConfigured = false;
} catch (ParserConfigurationException pce) {
// Parser with specified options can't be built
m_log.warn( "parseXML() SAX parser cannot be built " +
"with specified options" );
// unset configuration flag
isConfigured = false;
} catch (IOException ioe) {
// I/O error
m_log.warn( "parseXML() IO exception", ioe );
// unset configuration flag
isConfigured = false;
} catch (Throwable t) {
m_log.warn( "parseXML() exception", t );
// unset configuration flag
isConfigured = false;
}
}
protected void setDefaultCategory( SearchCategory defaultCategory )
{
if( defaultCategory != null )
{
this.defaultCategory = defaultCategory;
}
else
{
m_log.warn( "BasicSearchDatabaseHierarchy.setDefaultCategory()"+
" was passed a null SearchCategory to set" );
}
}
protected void addTopLevelCategory( SearchCategory topLevelCategory )
{
if( topLevelCategory != null )
{
if( rootCategory == null )
{
rootCategory = new BasicSearchCategory(
SearchDatabaseHierarchy.ROOT_CATEGORY_NAME,
SearchDatabaseHierarchy.ROOT_CATEGORY_ID );
}
rootCategory.addSubcategory( topLevelCategory );
}
else
{
m_log.warn( "BasicSearchDatabaseHierarchy.addTopLevelCategory()"+
" was passed a null SearchCategory to add" );
}
}
public void startElement( String namespaceURI, String sName,
String qName, Attributes attrs ) throws SAXException
{
String eName = sName; // element name
if( eName.equals( "" ) )
{
eName = qName; // not namespaceAware
}
if( eName.equals( "category" ) )
{
// create a new category with the given attribute info
BasicSearchCategory newCategory = new BasicSearchCategory(
attrs.getValue( "name" ), attrs.getValue( "id" ) );
// check if this is the default category
if( attrs.getValue( "default" ) != null )
{
newCategory.setDefault( true );
}
// add new category to the stack
categoryStack.push( newCategory );
}
else if( eName.equals( "database" ) )
{
// create a new database with the given attribute info
currentDatabase = new BasicSearchDatabase(
attrs.getValue( "name" ), attrs.getValue( "id" ) );
}
else if( eName.equals( "category_database" ) )
{
// determine whether this is a "recommended" database
if( attrs.getValue( "recommended" ) != null )
{
recommendedDatabaseFlag = true;
}
}
}
public void endElement( String namespaceURI, String sName,
String qName ) throws SAXException
{
String eName = sName; // element name
if( eName.equals( "" ) )
{
eName = qName; // not namespaceAware
}
parseData( eName );
}
public void characters( char[] buf, int offset, int len )
throws SAXException
{
String s = new String( buf, offset, len );
if( textBuffer == null )
{
textBuffer = new StringBuilder( s );
}
else
{
textBuffer.append( s );
}
}
protected String getAttribute( Attributes attrs, String attrName )
{
if( attrs != null )
{
for( int i = 0; i < attrs.getLength(); i++ )
{
String name = attrs.getLocalName( i );
if( name.equals( "" ) )
{
name = attrs.getQName(i);
}
if( name.equals( attrName ) )
{
return attrs.getValue( i );
}
}
}
return null;
}
protected void parseData( String endElement )
{
String text = null;
if( textBuffer != null )
{
text = textBuffer.toString().trim();
}
/*
* category elements
*/
if( endElement.equals( "category_description" ) )
{
BasicSearchCategory temp = categoryStack.pop();
temp.updateDescription( text );
categoryStack.push( temp );
}
else if( endElement.equals( "category" ) )
{
// a category has just ended
// attach it to its proper hierarchy container
if( !categoryStack.peek().isDefault() )
{
// add category to the category map
categoryMap.put( categoryStack.peek().getId(),
categoryStack.peek() );
if( categoryStack.size() == 1 )
{
// at the top level
addTopLevelCategory( categoryStack.pop() );
}
else
{
// not at the top level
// determine hierarchy depth
if( hierarchyDepth < categoryStack.size() )
{
hierarchyDepth = categoryStack.size();
}
// add current subcategory to parent category
BasicSearchCategory subcategory = categoryStack.pop();
BasicSearchCategory parentCategory = categoryStack.pop();
parentCategory.addSubcategory( subcategory );
categoryStack.push( parentCategory );
}
}
else
{
// TODO this assumes the default category is outside of the
// hierarchy (it is not attached to any parent container)
defaultCategory = categoryStack.pop();
}
}
/*
* category_database elements
*/
else if( endElement.equals( "id" ) )
{
currentDatabaseId = text;
// is this database recommended?
if( recommendedDatabaseFlag )
{
recommendedDatabaseFlag = false;
BasicSearchCategory temp = categoryStack.pop();
temp.addRecommendedDatabase( text );
categoryStack.push( temp );
}
else
{
BasicSearchCategory temp = categoryStack.pop();
temp.addDatabase( text );
categoryStack.push( temp );
}
}
else if( endElement.equals( "alt_name" ) )
{
currentDatabase = new BasicSearchDatabase( text,
currentDatabaseId );
}
else if( endElement.equals( "alt_description" ) )
{
currentDatabase.updateDescription( text );
}
else if( endElement.equals( "category_database" ) )
{
if( currentDatabase != null )
{
BasicSearchCategory temp = categoryStack.pop();
temp.addAlternateDatabase( currentDatabase );
categoryStack.push( temp );
currentDatabase = null;
}
}
/*
* database elements
*/
else if( endElement.equals( "database_description" ) )
{
currentDatabase.updateDescription( text );
}
else if( endElement.equals( "database_group" ) )
{
currentDatabase.addGroup( text );
}
else if( endElement.equals( "database" ) )
{
// a database has just ended - add to databaseMap
databaseMap.put( currentDatabase.getId(), currentDatabase );
currentDatabase = null;
}
textBuffer = null;
}
public SearchCategory getCategory( String categoryId ) {
if( categoryId.equals( defaultCategory.getId() ) )
{
return defaultCategory;
}
else
{
return categoryMap.get( categoryId );
}
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchDatabaseHierarchy#getNumLevels()
*/
public int getNumLevels() {
return hierarchyDepth;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchDatabaseHierarchy#getNumMaxSearchableDb()
*/
public int getNumMaxSearchableDb()
{
int number = m_configService.getSiteConfigMaximumSearchableDBs();
m_log.debug("getNumMaxSearchableDb() returns " + number);
return number;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchDatabaseHierarchy#getTopLevelCategories()
*/
public List<SearchCategory> getCategoryListing() {
// return list
List<SearchCategory> categoryListing = new java.util.ArrayList<SearchCategory>();
// add root category to return list
categoryListing.add( rootCategory );
// iterate through all categories (starting at root)
// and add them to the return list
for( int i = 0; i <= categoryMap.size(); i++ )
{
SearchCategory category = categoryListing.get( i );
if( category.hasSubCategories() )
{
for( SearchCategory cat : category.getSubCategories() )
{
categoryListing.add( cat );
}
}
}
return categoryListing;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchDatabaseHierarchy#getRepository()
*/
public Repository getRepository()
{
Repository repository = null;
// get a RepositoryManager
RepositoryManager repositoryManager = null;
try
{
repositoryManager = ( RepositoryManager )
SakaiOsidLoader.getManager(
"org.osid.repository.RepositoryManager",
repositoryPkgName,
new OsidContext(), null );
RepositoryIterator rit = null;
if( repositoryManager == null )
{
m_log.warn( "getRepository() failed getting RepositoryManager from SakaiOsidLoader" );
}
else
{
rit = repositoryManager.getRepositoriesByType( repositoryType );
}
// get repositories of type sakaibrary/repository/metasearch
if( rit == null )
{
m_log.warn( "getRepository() failed getting RepositoryIterator of type sakaibrary/repository/metasearch from RepositoryManager" );
return null;
}
else
{
// only one repository should be in the iterator
repository = rit.nextRepository();
String extendedId = m_configService.getSiteConfigExtendedRepositoryId();
if ((extendedId != null) && (extendedId.length() > 0))
{
while (repository != null)
{
m_log.debug("Matching Repositories? "
+ repository.getId().getIdString()
+ " VS "
+ extendedId);
if (repository.getId().getIdString().equals(extendedId))
{
break;
}
repository = rit.nextRepository();
}
}
}
if( repository == null )
{
m_log.warn( "getRepository() failed getting repository from RepositoryIterator" );
}
}
catch( OsidException oe )
{
m_log.warn( "getRepository threw OsidException: ", oe );
}
return repository;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchDatabaseHierarchy#getDefaultCategory()
*/
public SearchCategory getDefaultCategory() {
return defaultCategory;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchDatabaseHierarchy#isSearchableDatabase(java.lang.String)
*/
public boolean isSearchableDatabase(String databaseId) {
return databaseMap.containsKey( databaseId );
}
public boolean isConfigured() {
return isConfigured;
}
} // public class BasicSearchDatabaseHierarchy
/** Our logger. */
private static Log m_log = LogFactory.getLog(BaseSearchManager.class);
// our ConfigurationService (gets set in BaseSearchDatabaseHierarchy)
// google scholar constants
public static final String SAKAI_SESSION = "sakai.session.key";
public static final String SAKAI_KEY = "sakai.key";
public static final String SAKAI_HOST = "sakai.host";
//public static final String SERVLET_NAME = "savecite";
public static final String SERVLET_NAME = "sakai-citations-servlet";
public static final String WINDOW_PREFIX = "WebLearn Solo - ";
// Our types (defined in setupTypes())
protected static BasicType categoryAssetType;
protected static BasicType databaseAssetType;
protected static BasicType searchType;
protected static BasicType repositoryType;
// String array for databases being searched and database hierarchy
protected Map<String, String> hierarchyMap = new HashMap<String, String>();
protected SortedSet<String> updatableResources = Collections.synchronizedSortedSet(new TreeSet<String>());
private static Random m_generator;
/*
* necessary services and managers (provided by components.xml)
*/
protected SessionManager m_sessionManager = null;
protected ConfigurationService m_configService = null;
/** Dependency: ServerConfigurationService. */
protected ServerConfigurationService serverConfigurationService = null;
protected String databaseHierarchyResourceRef;
private Cache sessionContextCache;
private Cache metasearchSessionManagerCache;
public void setSessionManager(SessionManager sessionManager)
{
m_sessionManager = sessionManager;
}
public void setConfigurationService( ConfigurationService configService )
{
m_configService = configService;
}
public void destroy()
{
m_log.info("BaseSearchManager.destroy()");
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.impl.SearchManager#doNextPage(org.sakaiproject.citation.api.ActiveSearch)
*/
public ActiveSearch doNextPage(ActiveSearch search)
throws SearchException
{
Repository repository = ((BasicSearch) search).getRepository();
AssetIterator assetIterator = ((BasicSearch) search).getAssetIterator();
int last = search.getLastRecordIndex();
CitationCollection citations = search.getSearchResults();
if(citations == null)
{
citations = CitationService.getTemporaryCollection();
((BasicSearch) search).setSearchResults(citations);
}
Set duplicateCheck = ((BasicSearch) search).getDuplicateCheck();
int duplicateCount = 0;
boolean done = false;
boolean moreResults = false;
try
{
// poll until we get pageSize results (or run out of results)
moreResults = assetIterator.hasNextAsset();
while( !done && moreResults )
{
try
{
Asset asset = assetIterator.nextAsset();
Citation citation = CitationService.getTemporaryCitation(asset);
String dupCheckCriteria = citation.hasPreferredUrl()
? citation.getPrimaryUrl()
: citation.getOpenurlParameters();
m_log.debug("DUP CHECK: " + dupCheckCriteria);
if (((BasicSearch) search).isDuplicateCheckEnabled() &&
duplicateCheck.contains(dupCheckCriteria))
{
m_log.debug("Duplicate #" + (duplicateCount + 1) + " found");
if (duplicateCount++ >= MAX_DUPLICATES)
{
((BasicSearch) search).setDuplicateCheckEnabled(false);
}
// make sure we have more search results
moreResults = assetIterator.hasNextAsset();
continue;
}
else
{
((BasicSearch) search).m_pageOrder.add(citation.getId());
citations.add(citation);
duplicateCheck.add(dupCheckCriteria);
duplicateCount = 0;
}
// check if we've got enough to return
done = (citations.size() >= last);
}
catch( RepositoryException re )
{
if( re.getMessage().equals( SESSION_TIMED_OUT ) ||
re.getMessage().equals( METASEARCH_ERROR ) ||
re.getMessage().equals( SharedException.NO_MORE_ITERATOR_ELEMENTS ) ||
re.getMessage().equals( OsidException.OPERATION_FAILED ) )
{
// search is over, all assets that have been retrieved have been
// optionally check searchStatus Properties for further details or information to present in UI
search.setLastPage(true);
m_log.warn("doNextPage -- RepositoryException nextAsset(): " + re.getMessage());
String message = getSearchStatusMessage(repository);
if(message == null)
{
throw new SearchException(re.getMessage());
}
throw new SearchException( message );
}
else if( re.getMessage().equals( ASSET_NOT_FETCHED ) )
{
// need to wait some time and then try again
try
{
Thread.sleep( 2500 ); // sleep 2.5 seconds
}
catch( InterruptedException ie )
{
search.setLastPage(true);
m_log.warn("doNextPage -- InterruptedException nextAsset(): ", ie);
String message = getSearchStatusMessage(repository);
throw new SearchException( message );
}
}
}
// make sure we have more search results
moreResults = assetIterator.hasNextAsset();
}
}
catch( RepositoryException re )
{
if( re.getMessage().equals( SESSION_TIMED_OUT ) ||
re.getMessage().equals( METASEARCH_ERROR ) )
{
search.setLastPage(true);
// search is over, all assets that have been retrieved have been
// optionally check searchStatus Properties for further details or information to present in UI
m_log.warn("doNextPage -- RepositoryException hasNextAsset(): " + re.getMessage());
String message = getSearchStatusMessage(repository);
throw new SearchException( message );
}
}
// get search status properties
Type statusType = getPropertyType( repository );
org.osid.shared.Properties statusProperties = null;
try
{
statusProperties = repository.getPropertiesByType( statusType );
}
catch( RepositoryException re )
{
search.setLastPage(true);
String message = getSearchStatusMessage(repository);
throw new SearchException( message );
}
Integer numRecordsFound = null;
Integer numRecordsFetched = null;
Integer numRecordsMerged = null;
try
{
numRecordsFound = ( Integer ) statusProperties.getProperty("numRecordsFound");
numRecordsFetched = ( Integer ) statusProperties.getProperty("numRecordsFetched");
numRecordsMerged = ( Integer ) statusProperties.getProperty("numRecordsMerged");
}
catch( SharedException se )
{
search.setLastPage(true);
String message = getSearchStatusMessage(repository);
throw new SearchException( message );
}
search.setNumRecordsFound( numRecordsFound );
search.setNumRecordsFetched( numRecordsFetched );
search.setNumRecordsMerged( numRecordsMerged );
search.setNewSearch(false);
search.setFirstPage(false);
/*
* disable the "next page" arrow if we've exhausted the search results
*/
if (!moreResults)
{
search.setLastPage(true);
}
else if(done)
{
search.setLastPage(false);
}
else
{
search.setLastPage(true);
}
return search;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.impl.SearchManager#doPrevPage(org.sakaiproject.citation.api.ActiveSearch)
*/
public ActiveSearch doPrevPage(ActiveSearch search)
throws SearchException
{
return search;
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.impl.SearchManager#doSearch(org.sakaiproject.citation.api.ActiveSearch)
*/
public ActiveSearch doSearch(ActiveSearch search)
throws SearchException, SearchCancelException
{
// search parameters
Integer pageSize = search.getPageSize();
Integer startRecord = search.getStartRecord();
String sortBy = search.getSortBy();
String guid = search.getSearchId();
String[] searchDbs = search.getDatabaseIds();
/*
* Repository set up
*/
SearchDatabaseHierarchy hierarchy = getSearchHierarchy();
if (hierarchy == null)
{
throw new SearchException("ERROR: No appropriate database hierarchy available");
}
Repository repository = hierarchy.getRepository();
// CQL search query setup
String cqlQuery = null;
CQLSearchQuery cqlSearch = new org.sakaiproject.citation.util.impl.CQLSearchQuery();
// determine whether this is an advanced or basic search
if( search.getSearchType().equalsIgnoreCase( ActiveSearch.BASIC_SEARCH_TYPE ) )
{
// get search criteria in CQL
cqlQuery = cqlSearch.getCQLSearchQueryString( search.getBasicQuery() );
}
else
{
// get search criteria in CQL
cqlQuery = cqlSearch.getCQLSearchQueryString( search.getAdvancedQuery() );
}
m_log.debug( "CQL query: " + cqlQuery );
// initiate the search
try
{
if( cqlQuery == null )
{
// something went horrible
throw new SearchException( "ERROR: could not properly " +
"convert search criteria to CQL." );
}
// set up search properties
java.util.Properties properties = new java.util.Properties();
properties.put( "guid", guid );
properties.put( "baseUrl", m_configService.getSiteConfigMetasearchBaseUrl() );
properties.put( "username", m_configService.getSiteConfigMetasearchUsername() );
properties.put( "password", m_configService.getSiteConfigMetasearchPassword() );
properties.put( "sortBy", sortBy );
properties.put( "pageSize", pageSize );
properties.put( "startRecord", startRecord );
// put selected databases
List<String> databaseIds = new java.util.ArrayList<String>();
for( String databaseId : searchDbs )
{
databaseIds.add( databaseId );
}
properties.put( "databaseIds", databaseIds );
// create OSID Properties
org.osid.shared.Properties searchProperties = new BasicSearchProperties( properties );
// get "sakaibrary / search / asynchMetasearch" search type
Type searchType = getSearchType( repository );
// call getAssetsBySearch
AssetIterator assetIterator =
repository.getAssetsBySearch( cqlQuery, searchType,
searchProperties );
CitationCollection citations = search.getSearchResults();
if(citations == null)
{
citations = CitationService.getTemporaryCollection();
((BasicSearch) search).setSearchResults(citations);
}
Set duplicateCheck = ((BasicSearch) search).getDuplicateCheck();
int duplicateCount = 0;
int assetsRetrieved = 0;
boolean done = false;
boolean moreResults = false;
try
{
// poll until we get pageSize results (or run out of results)
moreResults = assetIterator.hasNextAsset();
while( !done && moreResults )
{
try
{
Asset asset = assetIterator.nextAsset();
Citation citation = CitationService.getTemporaryCitation(asset);
String dupCheckCriteria = citation.hasPreferredUrl()
? citation.getPrimaryUrl()
: citation.getOpenurlParameters();
m_log.debug("DUP CHECK: " + dupCheckCriteria);
if (((BasicSearch) search).isDuplicateCheckEnabled() &&
duplicateCheck.contains(dupCheckCriteria))
{
m_log.debug("Duplicate #" + (duplicateCount + 1) + " found");
if (duplicateCount++ >= MAX_DUPLICATES)
{
((BasicSearch) search).setDuplicateCheckEnabled(false);
}
// make sure we have more search results
moreResults = assetIterator.hasNextAsset();
continue;
}
else
{
((BasicSearch) search).m_pageOrder.add(citation.getId());
citations.add(citation);
duplicateCheck.add(dupCheckCriteria);
duplicateCount = 0;
}
// check if we've got enough to return
if( ++assetsRetrieved >= pageSize.intValue() )
{
done = true;
}
}
catch( RepositoryException re )
{
if( re.getMessage().equals( SESSION_TIMED_OUT ) ||
re.getMessage().equals( METASEARCH_ERROR ) ||
re.getMessage().equals( SharedException.NO_MORE_ITERATOR_ELEMENTS ) ||
re.getMessage().equals( OsidException.OPERATION_FAILED ) )
{
search.setLastPage(true);
search.resetSearch();
// search is over, all assets that have been retrieved have been
// optionally check searchStatus Properties for further details or information to present in UI
String message = getSearchStatusMessage(repository);
m_log.warn("doSearch -- RepositoryException nextAsset(): " + re.getMessage());
throw new SearchException( message );
}
else if( re.getMessage().equals( ASSET_NOT_FETCHED ) )
{
// need to wait some time and then try again
try
{
Thread.sleep( 2500 ); // sleep 2.5 seconds
}
catch( InterruptedException ie )
{
// search canceled
throw new SearchCancelException();
}
}
}
// make sure we have more search results
moreResults = assetIterator.hasNextAsset();
}
}
catch( RepositoryException re )
{
if( re.getMessage().equals( SESSION_TIMED_OUT ) ||
re.getMessage().equals( METASEARCH_ERROR ) )
{
search.setLastPage(true);
// search is over, all assets that have been retrieved have been
// optionally check searchStatus Properties for further details or information to present in UI
String message = getSearchStatusMessage(repository);
m_log.warn("doSearch -- RepositoryException hasNextAsset(): " + re.getMessage());
throw new SearchException( message );
}
}
// get search status properties
Type statusType = getPropertyType( repository );
org.osid.shared.Properties statusProperties =
repository.getPropertiesByType( statusType );
Integer numRecordsFound = null;
Integer numRecordsFetched = null;
Integer numRecordsMerged = null;
try
{
numRecordsFetched = ( Integer ) statusProperties.getProperty(
"numRecordsFetched" );
numRecordsFound = ( Integer ) statusProperties.getProperty(
"numRecordsFound" );
numRecordsMerged = ( Integer ) statusProperties.getProperty(
"numRecordsMerged" );
}
catch( SharedException se )
{
search.setLastPage(true);
String message = getSearchStatusMessage(repository);
throw new SearchException( message );
}
/*
* forward results handling
*/
search.setNumRecordsFound( numRecordsFound );
search.setNumRecordsFetched( numRecordsFetched );
search.setNumRecordsMerged( numRecordsMerged );
search.setNewSearch(false);
search.setFirstPage(false);
/*
* disable the "next page" arrow if we've exhausted the search results
*/
if (!moreResults)
{
search.setLastPage(true);
}
else
{
search.setLastPage(!done);
}
((BasicSearch) search).setRepository(repository);
((BasicSearch) search).setAssetIterator(assetIterator);
return search;
}
catch( RepositoryException re )
{
m_log.warn("doSearch -- RepositoryException: " + re.getMessage());
throw new SearchException( re.getMessage() );
}
}
protected String newSearchId()
{
/******* A unique ID per-session ********/
return m_sessionManager.getCurrentSession().getId();
/******* Unique ID per-search (original)
String sessionId = m_sessionManager.getCurrentSession().getId();
long number = m_generator.nextLong();
String hexString = Long.toHexString(number);
m_log.debug("getSearchId: " + sessionId + hexString);
return sessionId + hexString;
*************************************************************************/
}
protected Type getPropertyType(Repository repository)
throws SearchException
{
TypeIterator propertyTypes = null;
Type propertyType = null;
try
{
propertyTypes = repository.getPropertyTypes();
while( propertyTypes.hasNextType() )
{
Type tempType = propertyTypes.nextType();
if( tempType.getAuthority().equals( "sakaibrary" ) &&
tempType.getDomain().equals( "properties" ) &&
tempType.getKeyword().equals( "metasearchStatus" ) )
{
propertyType = tempType;
break;
}
}
}
catch( OsidException oe )
{
m_log.warn("getPropertyType -- OsidException: " + oe.getMessage());
throw new SearchException( "ERROR in getting search types: " + oe.getMessage() );
}
return propertyType;
}
protected Type getCategoryType( Repository repository ) throws SearchException
{
TypeIterator assetTypes = null;
try
{
assetTypes = repository.getAssetTypes();
while( assetTypes.hasNextType() )
{
Type tempType = assetTypes.nextType();
if( tempType.isEqual( categoryAssetType ) )
{
return tempType;
}
}
}
catch( OsidException oe )
{
m_log.warn("getCategoryType -- OsidException: ", oe);
throw new SearchException( "ERROR in getting category type: " + oe.getMessage() );
}
return null;
}
protected Type getSearchType( Repository repository ) throws SearchException
{
TypeIterator searchTypes = null;
try
{
searchTypes = repository.getSearchTypes();
while( searchTypes.hasNextType() )
{
Type tempType = searchTypes.nextType();
if( tempType.isEqual( searchType ) )
{
return tempType;
}
}
}
catch( OsidException oe )
{
m_log.warn("getSearchType -- OsidException: ", oe);
throw new SearchException( "ERROR in getting search types: " + oe.getMessage() );
}
return null;
}
protected String getSearchStatusMessage( Repository repository ) throws SearchException
{
BasicType statusType = new BasicType("sakaibrary", "properties", "metasearchStatus");
String message = null;
try
{
org.osid.shared.Properties statusProperties = repository.getPropertiesByType(statusType);
message = (String) statusProperties.getProperty("statusMessage");
}
catch(RepositoryException e)
{
// let the message remain null but log this exception
m_log.warn("getSearchStatusMessage RepositoryException getting properties " + e.getMessage());
}
catch(SharedException e)
{
// let the message remain null but log this exception
m_log.warn("getSearchStatusMessage SharedException getting property " + e.getMessage());
}
return message;
}
public void init()
{
SessionContext.setCache(sessionContextCache);
MetasearchSessionManager.setCache(metasearchSessionManagerCache);
m_log.info("BaseSearchManager.init()");
EventTrackingService.addObserver(this);
long seed = TimeService.newTime().getTime();
m_generator = new Random(seed);
setupTypes();
String configFolderRef = m_configService.getConfigFolderReference();
Collection<String> hierarchyIds = m_configService.getAllCategoryXml();
for(String hierarchyId : hierarchyIds)
{
this.updateHierarchy(configFolderRef + hierarchyId);
this.updatableResources.add(configFolderRef + hierarchyId);
}
}
protected void setupTypes() {
categoryAssetType = new BasicType( "sakaibrary", "asset", "category" );
databaseAssetType = new BasicType( "sakaibrary", "asset", "database" );
searchType = new BasicType( "sakaibrary", "search", "asynchMetasearch" );
repositoryType = new BasicType( "sakaibrary", "repository", "metasearch" );
}
/*
* Fetch the appropriate database hierarchy resource from our cache (a new
* hiearchy resource is created if none exists).
*
* @see org.sakaiproject.citation.api.SearchManager#listRepositories()
*/
public SearchDatabaseHierarchy getSearchHierarchy() throws SearchException
{
SearchDatabaseHierarchy hierarchy;
try
{
String configFolderRef = m_configService.getConfigFolderReference();
String hierarchyXml = m_configService.getDatabaseHierarchyXml();
if(!isNull(configFolderRef) && !isNull(hierarchyXml))
{
String hierarchyRef = configFolderRef + hierarchyXml;
String xmlContent;
/*
* Look up the requested hierarchy
*/
m_log.debug("Looking for hierarchy: " + hierarchyRef);
synchronized (this)
{
if ((xmlContent = this.hierarchyMap.get(hierarchyRef)) == null)
{
return null;
}
}
/*
* Set up the per-user database hierarchy
*/
hierarchy = new BasicSearchDatabaseHierarchy(xmlContent);
return hierarchy.isConfigured() ? hierarchy : null;
}
}
catch (OsidConfigurationException exception)
{
m_log.warn("Failed to get configuration details: " + exception);
}
return null;
}
public synchronized void updateHierarchy(String databaseXmlReference)
{
try
{
String xmlContent = getResourceContent(databaseXmlReference);
if (xmlContent != null)
{
this.hierarchyMap.put(databaseXmlReference, xmlContent);
}
}
catch (Exception exception)
{
m_log.warn("Failed to load "
+ databaseXmlReference
+ " (no changes made): "
+ exception);
}
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchManager#newSearch(java.lang.String)
*/
public ActiveSearch newSearch()
{
return new BasicSearch();
}
/* (non-Javadoc)
* @see org.sakaiproject.citation.api.SearchManager#newSearch(java.lang.String)
*/
public ActiveSearch newSearch(CitationCollection savedResults)
{
return new BasicSearch(savedResults);
}
protected boolean paramIsEmpty( String param )
{
return param.trim().equals("");
}
/**
*
*/
public String getGoogleScholarUrl(String resourceId)
{
String serverUrl = serverConfigurationService.getServerUrl();
//SessionManager sessionManager = (SessionManager) ComponentManager.get("org.sakaiproject.tool.api.SessionManager");
//String sessionId = sessionManager.getCurrentSession().getId();
try
{
return ( m_configService.getSiteConfigGoogleBaseUrl()
+ "?sciui=2&as_sdt=0,15&"
+ "linkurl_base="
+ java.net.URLEncoder.encode( serverUrl +
Entity.SEPARATOR +
SERVLET_NAME +
Entity.SEPARATOR +
resourceId +
"?" +
SAKAI_SESSION +
"=nada&", "UTF-8" )
+ "&linkurl_id="
+ java.net.URLEncoder.encode( m_configService.getSiteConfigSakaiServerKey(), "UTF-8" ) );
}
catch( Exception e )
{
m_log.warn( "getGoogleScholarUrl encoding failed", e );
return null;
}
}
/**
* Supply the url for the savecite servlet to add a citation to a particular citation list.
* @param resourceId The identifier for the citation list.
*/
public String getSaveciteUrl(String resourceId, String saveciteClientId) {
StringBuilder buf = new StringBuilder();
String serverUrl = serverConfigurationService.getServerUrl();
buf.append(serverUrl);
buf.append(Entity.SEPARATOR);
buf.append(SERVLET_NAME);
buf.append(Entity.SEPARATOR);
buf.append(resourceId);
buf.append('?');
buf.append(SAKAI_SESSION);
buf.append("=nada&client=");
buf.append(saveciteClientId);
//buf.append("&");
return buf.toString();
}
public String getExternalSearchWindowName(String resourceId)
{
String serverUrl = serverConfigurationService.getServerUrl() + Entity.SEPARATOR + SERVLET_NAME + Entity.SEPARATOR + resourceId;
try {
String encodedUrl = URLEncoder.encode(serverUrl, "UTF-8");
return WINDOW_PREFIX + encodedUrl;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Does anywhere not have UTF-8?", e);
}
}
/**
* @return the serverConfigurationService
*/
public ServerConfigurationService getServerConfigurationService()
{
return serverConfigurationService;
}
/**
* @param serverConfigurationService the serverConfigurationService to set
*/
public void setServerConfigurationService(ServerConfigurationService serverConfigurationService)
{
this.serverConfigurationService = serverConfigurationService;
}
public void update(Observable arg0, Object arg1)
{
if (arg1 instanceof Event)
{
Event event = (Event) arg1;
/*
* Modified? If so, reload if it's one of our hierarchy files
*/
if (event.getModify())
{
String refstr = event.getResource();
synchronized (this)
{
if (this.updatableResources.contains(refstr))
{
m_log.debug("Updating configuration from " + refstr);
this.updateHierarchy(refstr);
}
}
}
}
}
/**
* Establish a security advisor to allow the "embedded" azg work to occur
* with no need for additional security permissions.
*/
protected SecurityAdvisor enableSecurityAdvisor()
{
SecurityAdvisor advisor = new SecurityAdvisor() {
public SecurityAdvice isAllowed(String userId, String function, String reference)
{
return SecurityAdvice.ALLOWED;
}
};
// put in a security advisor so we can create citationAdmin site without need
// of further permissions
SecurityService.pushAdvisor(advisor);
return advisor;
}
/**
* Fetch the content from a Resource
* @param resource Content hosting resource
*/
public String getResourceContent(ContentResource resource)
throws IOException, ServerOverloadException
{
InputStream input = resource.streamContent();
StringBuilder content = new StringBuilder();
byte[] bytesIn = new byte[1024 * 8];
int count;
while ((count = input.read(bytesIn)) != -1)
{
content.append(new String(bytesIn, 0, count, "UTF-8"));
}
return content.toString();
}
/**
* Fetch content from a resource reference (a named resource)
* @param resourceReference Resource reference
* @return Resource content (null if none)
*/
public String getResourceContent(String resourceReference)
throws IOException,
IdUnusedException,
PermissionException,
ServerOverloadException,
TypeException
{
String content = null;
Reference reference = EntityManager.newReference(resourceReference);
if (reference == null)
{
return null;
}
SecurityAdvisor pushed = enableSecurityAdvisor();
try {
ContentResource resource = ContentHostingService.getResource(reference.getId());
if(resource != null) {
content = getResourceContent(resource);
}
} catch(Exception e) {
m_log.warn("getReourceContent() " + e);
} finally {
if(pushed != null) {
boolean found = false;
while(SecurityService.hasAdvisors() && ! found) {
SecurityAdvisor popped = SecurityService.popAdvisor();
found = popped == pushed;
}
}
}
return content;
}
/**
* Null (or empty) String?
* @param string String to check
* @return true if so
*/
private boolean isNull(String string)
{
return (string == null) || (string.trim().equals(""));
}
/**
* @return the metasearchSessionManagerCache
*/
public Cache getMetasearchSessionManagerCache()
{
return metasearchSessionManagerCache;
}
/**
* @param metasearchSessionManagerCache the metasearchSessionManagerCache to set
*/
public void setMetasearchSessionManagerCache(Cache metasearchSessionManagerCache)
{
this.metasearchSessionManagerCache = metasearchSessionManagerCache;
}
/**
* @return the sessionContextCache
*/
public Cache getSessionContextCache()
{
return sessionContextCache;
}
/**
* @param sessionContextCache the sessionContextCache to set
*/
public void setSessionContextCache(Cache sessionContextCache)
{
this.sessionContextCache = sessionContextCache;
}
}