Skip to content
Snippets Groups Projects
Commit c7b5bc63 authored by Chris Hallberg's avatar Chris Hallberg Committed by Demian Katz
Browse files

Refactored Solr tree data source to use formatters.

- Faster, less redundant code.
parent 84bbf37d
No related merge requests found
...@@ -59,9 +59,12 @@ class Factory ...@@ -59,9 +59,12 @@ class Factory
$filters = isset($hierarchyFilters->HierarchyTree->filterQueries) $filters = isset($hierarchyFilters->HierarchyTree->filterQueries)
? $hierarchyFilters->HierarchyTree->filterQueries->toArray() ? $hierarchyFilters->HierarchyTree->filterQueries->toArray()
: []; : [];
$solr = $sm->getServiceLocator()->get('VuFind\Search\BackendManager')
->get('Solr')->getConnector();
$formatterManager = $sm->getServiceLocator()
->get('VuFind\HierarchyTreeDataFormatterPluginManager');
return new Solr( return new Solr(
$sm->getServiceLocator()->get('VuFind\Search'), $solr, $formatterManager, rtrim($cacheDir, '/') . '/hierarchy',
rtrim($cacheDir, '/') . '/hierarchy',
$filters $filters
); );
} }
......
...@@ -26,8 +26,9 @@ ...@@ -26,8 +26,9 @@
* @link http://vufind.org/wiki/vufind2:hierarchy_components Wiki * @link http://vufind.org/wiki/vufind2:hierarchy_components Wiki
*/ */
namespace VuFind\Hierarchy\TreeDataSource; namespace VuFind\Hierarchy\TreeDataSource;
use VuFind\Hierarchy\TreeDataFormatter\PluginManager as FormatterManager;
use VuFindSearch\Query\Query; use VuFindSearch\Query\Query;
use VuFindSearch\Service as SearchService; use VuFindSearch\Backend\Solr\Connector;
use VuFindSearch\ParamBag; use VuFindSearch\ParamBag;
/** /**
...@@ -46,9 +47,16 @@ class Solr extends AbstractBase ...@@ -46,9 +47,16 @@ class Solr extends AbstractBase
/** /**
* Search service * Search service
* *
* @var SearchService * @var Connector
*/ */
protected $searchService; protected $solrConnector;
/**
* Formatter manager
*
* @var FormatterManager
*/
protected $formatterManager;
/** /**
* Cache directory * Cache directory
...@@ -67,14 +75,16 @@ class Solr extends AbstractBase ...@@ -67,14 +75,16 @@ class Solr extends AbstractBase
/** /**
* Constructor. * Constructor.
* *
* @param SearchService $search Search service * @param Connector $connector Solr connector
* @param string $cacheDir Directory to hold cache results (optional) * @param FormatterManager $fm Formatter manager
* @param array $filters Filters to apply to Solr tree queries * @param string $cacheDir Directory to hold cache results (optional)
* @param array $filters Filters to apply to Solr tree queries
*/ */
public function __construct(SearchService $search, $cacheDir = null, public function __construct(Connector $connector, FormatterManager $fm,
$filters = [] $cacheDir = null, $filters = []
) { ) {
$this->searchService = $search; $this->solrConnector = $connector;
$this->formatterManager = $fm;
if (null !== $cacheDir) { if (null !== $cacheDir) {
$this->cacheDir = rtrim($cacheDir, '/'); $this->cacheDir = rtrim($cacheDir, '/');
} }
...@@ -94,102 +104,97 @@ class Solr extends AbstractBase ...@@ -94,102 +104,97 @@ class Solr extends AbstractBase
*/ */
public function getXML($id, $options = []) public function getXML($id, $options = [])
{ {
$top = $this->searchService->retrieve('Solr', $id)->getRecords(); return $this->getFormattedData($id, 'xml', $options, 'hierarchyTree_%s.xml');
if (!isset($top[0])) { }
return '';
}
$top = $top[0];
$cacheFile = (null !== $this->cacheDir)
? $this->cacheDir . '/hierarchyTree_' . urlencode($id) . '.xml'
: false;
$useCache = isset($options['refresh']) ? !$options['refresh'] : true; /**
$cacheTime = $this->getHierarchyDriver()->getTreeCacheTime(); * Search Solr.
*
* @param string $q Search query
* @param int $rows Max rows to retrieve (default = int max)
*
* @return array
*/
protected function searchSolr($q, $rows = 2147483647)
{
$params = new ParamBag(
[
'q' => [$q],
'fq' => $this->filters,
'hl' => ['false'],
'fl' => ['title,id,hierarchy_parent_id,hierarchy_top_id,'
. 'is_hierarchy_id,hierarchy_sequence,title_in_hierarchy'],
'wt' => ['json'],
'json.nl' => ['arrarr'],
'rows' => [$rows], // Integer max
'start' => [0]
]
);
$response = $this->solrConnector->search($params);
return json_decode($response);
}
if ($useCache && file_exists($cacheFile) /**
&& ($cacheTime < 0 || filemtime($cacheFile) > (time() - $cacheTime)) * Retrieve a map of children for the provided hierarchy.
) { *
$this->debug("Using cached data from $cacheFile"); * @param string $id Record ID
$xml = file_get_contents($cacheFile); *
} else { * @return array
$starttime = microtime(true); */
$isCollection = $top->isCollection() ? "true" : "false"; protected function getMapForHierarchy($id)
$xml = '<root><item id="' . {
htmlspecialchars($id) . // Static cache of last map; if the user requests the same map twice
'" isCollection="' . $isCollection . '">' . // in a row (as when generating XML and JSON in sequence) this will
'<content><name>' . htmlspecialchars($top->getTitle()) . // save a Solr hit.
'</name></content>'; static $map;
$count = 0; static $lastId = null;
$xml .= $this->getChildren($id, $count); if ($id === $lastId) {
$xml .= '</item></root>'; return $map;
if ($cacheFile) { }
if (!file_exists($this->cacheDir)) { $lastId = $id;
mkdir($this->cacheDir);
$results = $this->searchSolr('hierarchy_top_id:"' . $id . '"');
if ($results->response->numFound < 1) {
return [];
}
$map = [$id => []];
foreach ($results->response->docs as $current) {
$parents = isset($current->hierarchy_parent_id)
? $current->hierarchy_parent_id : [];
foreach ($parents as $parentId) {
if (!isset($map[$parentId])) {
$map[$parentId] = [$current];
} else {
$map[$parentId][] = $current;
} }
file_put_contents($cacheFile, $xml);
} }
$this->debug(
"Hierarchy of $count records built in " .
abs(microtime(true) - $starttime)
);
} }
return $xml; return $map;
} }
/** /**
* Get Solr Children * Get a record from Solr (return false if not found).
* *
* @param string $parentID The starting point for the current recursion * @param string $id ID to fetch.
* (equivlent to Solr field hierarchy_parent_id)
* @param string $count The total count of items in the tree
* before this recursion
* *
* @return string * @return array|bool
*/ */
protected function getChildren($parentID, &$count) protected function getRecord($id)
{ {
$query = new Query( // Static cache of last record; if the user requests the same map twice
'hierarchy_parent_id:"' . addcslashes($parentID, '"') . '"' // in a row (as when generating XML and JSON in sequence) this will
); // save a Solr hit.
$results = $this->searchService->search( static $record;
'Solr', $query, 0, 10000, static $lastId = null;
new ParamBag(['fq' => $this->filters, 'hl' => 'false']) if ($id === $lastId) {
); return $record;
if ($results->getTotal() < 1) {
return '';
}
$xml = [];
$sorting = $this->getHierarchyDriver()->treeSorting();
foreach ($results->getRecords() as $current) {
++$count;
$titles = $current->getTitlesInHierarchy();
$title = isset($titles[$parentID])
? $titles[$parentID] : $current->getTitle();
$this->debug("$parentID: " . $current->getUniqueID());
$xmlNode = '';
$isCollection = $current->isCollection() ? "true" : "false";
$xmlNode .= '<item id="' . htmlspecialchars($current->getUniqueID()) .
'" isCollection="' . $isCollection . '"><content><name>' .
htmlspecialchars($title) . '</name></content>';
$xmlNode .= $this->getChildren($current->getUniqueID(), $count);
$xmlNode .= '</item>';
// If we're in sorting mode, we need to create key-value arrays;
// otherwise, we can just collect flat strings.
if ($sorting) {
$positions = $current->getHierarchyPositionsInParents();
$sequence = isset($positions[$parentID]) ? $positions[$parentID] : 0;
$xml[] = [$sequence, $xmlNode];
} else {
$xml[] = $xmlNode;
}
} }
$lastId = $id;
// Assemble the XML, sorting it first if necessary: $recordResults = $this->searchSolr('id:"' . $id . '"', 1);
return implode('', $sorting ? $this->sortNodes($xml) : $xml); $record = isset($recordResults->response->docs[0])
? $recordResults->response->docs[0] : false;
return $record;
} }
/** /**
...@@ -205,13 +210,26 @@ class Solr extends AbstractBase ...@@ -205,13 +210,26 @@ class Solr extends AbstractBase
*/ */
public function getJSON($id, $options = []) public function getJSON($id, $options = [])
{ {
$top = $this->searchService->retrieve('Solr', $id)->getRecords(); return $this->getFormattedData($id, 'json', $options, 'tree_%s.json');
if (!isset($top[0])) { }
return '';
} /**
$top = $top[0]; * Get formatted data for the specified hierarchy ID.
*
* @param string $id Hierarchy ID.
* @param string $format Name of formatter service to use.
* @param array $options Additional options for JSON generation.
* (Currently one option is supported: 'refresh' may be set to true to
* bypass caching).
* @param string $cacheTemplate Template for cache filenames
*
* @return string
*/
public function getFormattedData($id, $format, $options = [],
$cacheTemplate = 'tree_%s'
) {
$cacheFile = (null !== $this->cacheDir) $cacheFile = (null !== $this->cacheDir)
? $this->cacheDir . '/tree_' . urlencode($id) . '.json' ? $this->cacheDir . '/' . sprintf($cacheTemplate, urlencode($id))
: false; : false;
$useCache = isset($options['refresh']) ? !$options['refresh'] : true; $useCache = isset($options['refresh']) ? !$options['refresh'] : true;
...@@ -225,19 +243,23 @@ class Solr extends AbstractBase ...@@ -225,19 +243,23 @@ class Solr extends AbstractBase
return $json; return $json;
} else { } else {
$starttime = microtime(true); $starttime = microtime(true);
$json = [ $map = $this->getMapForHierarchy($id);
'id' => $id, if (empty($map)) {
'type' => $top->isCollection() return '';
? 'collection'
: 'record',
'title' => $top->getTitle()
];
$children = $this->getChildrenJson($id, $count);
if (!empty($children)) {
$json['children'] = $children;
} }
// Get top record's info
$formatter = $this->formatterManager->get($format);
$formatter->setRawData(
$this->getRecord($id), $map,
$this->getHierarchyDriver()->treeSorting(),
$this->getHierarchyDriver()->getCollectionLinkType()
);
$encoded = $formatter->getData();
$count = $formatter->getCount();
$this->debug('Done: ' . abs(microtime(true) - $starttime));
if ($cacheFile) { if ($cacheFile) {
$encoded = json_encode($json);
// Write file // Write file
if (!file_exists($this->cacheDir)) { if (!file_exists($this->cacheDir)) {
mkdir($this->cacheDir); mkdir($this->cacheDir);
...@@ -245,101 +267,13 @@ class Solr extends AbstractBase ...@@ -245,101 +267,13 @@ class Solr extends AbstractBase
file_put_contents($cacheFile, $encoded); file_put_contents($cacheFile, $encoded);
} }
$this->debug( $this->debug(
"Hierarchy of $count records built in " . "Hierarchy of {$count} records built in " .
abs(microtime(true) - $starttime) abs(microtime(true) - $starttime)
); );
return $encoded; return $encoded;
} }
} }
/**
* Get Solr Children for JSON
*
* @param string $parentID The starting point for the current recursion
* (equivlent to Solr field hierarchy_parent_id)
* @param string $count The total count of items in the tree
* before this recursion
*
* @return string
*/
protected function getChildrenJson($parentID, &$count)
{
$query = new Query(
'hierarchy_parent_id:"' . addcslashes($parentID, '"') . '"'
);
$results = $this->searchService->search(
'Solr', $query, 0, 10000,
new ParamBag(['fq' => $this->filters, 'hl' => 'false'])
);
if ($results->getTotal() < 1) {
return '';
}
$json = [];
$sorting = $this->getHierarchyDriver()->treeSorting();
foreach ($results->getRecords() as $current) {
++$count;
$titles = $current->getTitlesInHierarchy();
$title = isset($titles[$parentID])
? $titles[$parentID] : $current->getTitle();
$this->debug("$parentID: " . $current->getUniqueID());
$childNode = [
'id' => $current->getUniqueID(),
'type' => $current->isCollection()
? 'collection'
: 'record',
'title' => $title
];
if ($current->isCollection()) {
$children = $this->getChildrenJson(
$current->getUniqueID(),
$count
);
if (!empty($children)) {
$childNode['children'] = $children;
}
}
// If we're in sorting mode, we need to create key-value arrays;
// otherwise, we can just collect flat values.
if ($sorting) {
$positions = $current->getHierarchyPositionsInParents();
$sequence = isset($positions[$parentID]) ? $positions[$parentID] : 0;
$json[] = [$sequence, $childNode];
} else {
$json[] = $childNode;
}
}
return $sorting ? $this->sortNodes($json) : $json;
}
/**
* Sort Nodes
* Convert an unsorted array of [ key, value ] pairs into a sorted array
* of values.
*
* @param array $array The array of arrays to sort
*
* @return array
*/
protected function sortNodes($array)
{
// Sort arrays based on first element
$sorter = function ($a, $b) {
return strcmp($a[0], $b[0]);
};
usort($array, $sorter);
// Collapse array to remove sort values
$mapper = function ($i) {
return $i[1];
};
return array_map($mapper, $array);
}
/** /**
* Does this data source support the specified hierarchy ID? * Does this data source support the specified hierarchy ID?
* *
...@@ -354,10 +288,7 @@ class Solr extends AbstractBase ...@@ -354,10 +288,7 @@ class Solr extends AbstractBase
if (!isset($settings['checkAvailability']) if (!isset($settings['checkAvailability'])
|| $settings['checkAvailability'] == 1 || $settings['checkAvailability'] == 1
) { ) {
$results = $this->searchService->retrieve( if (!$this->getRecord($id)) {
'Solr', $id, new ParamBag(['fq' => $this->filters])
);
if ($results->getTotal() < 1) {
return false; return false;
} }
} }
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment