Import first version of fulltextsearch module

This commit is contained in:
Hamish Friedlander 2011-05-02 16:33:05 +12:00
parent 85dfbaaf32
commit 17be5a3e63
82 changed files with 17656 additions and 0 deletions

View File

@ -0,0 +1,48 @@
# WARNING: Heavily experimental API. Likely to change without notice.
# FullTextSearch module
An attempt to add stable support for Fulltext Search engines like Sphinx and Solr to SilverStripe CMS
## Maintainer Contact
* Hamish Friedlander <hamish (at) silverstripe (dot) com>
## Requirements
* SilverStripe 2.4. Untested in 3, but probably won't work.
## Documentation
See docs/README.md
## TODO
* Get rid of includeSubclasses - isn't actually used in practise, makes the codebase uglier, and ClassHierarchy can be
used at query time for most of the same use cases
* Fix field referencing in queries. Should be able to do `$query->search('Text', 'Content')`, not
`$query->search('Text', 'SiteTree_Content')` like you have to do now
- Make sure that when field exists in multiple classes, searching against bare fields searches all of them
- Allow searching against specific instances too
* Make fields restrictable by class in an index - 'SiteTree#Content' to limit fields to a particular class,
maybe 'Content->Summary' to allow calling a specific method on the field object to get the text
* Allow following user relationships (Children.Foo for example)
* Be clearer about what happens with relationships to stateful objects (e.g. Parent.Foo where Parent is versioned)
* Improvements to SearchUpdater
- Make it work properly when in-between objects (the A in A.B.Foo) update
- Allow user logic to cause triggering reindex of documents when field is user generated
* Add sphinx connector
* Add generic APIs for spell correction, file text extraction and snippet generation
* Better docs

3
_config.php Normal file
View File

@ -0,0 +1,3 @@
<?php
SearchUpdater::bind_manipulation_capture();

View File

@ -0,0 +1,88 @@
<?php
/**
* Base class to manage active search indexes.
*/
class FullTextSearch {
static protected $all_indexes = null;
static protected $indexes_by_subclass = array();
/**
* Get all the instantiable search indexes (so all the user created indexes, but not the connector or library level
* abstract indexes). Can optionally be filtered to only return indexes that are subclasses of some class
*
* @static
* @param String $class - Class name to filter indexes by, so that all returned indexes are subclasses of provided class
* @param bool $rebuild - If true, don't use cached values
*/
static function get_indexes($class = null, $rebuild = false) {
if ($rebuild) { self::$all_indexes = null; self::$indexes_by_subclass = array(); }
if (!$class) {
if (self::$all_indexes === null) {
$classes = ClassInfo::subclassesFor('SearchIndex');
$concrete = array();
foreach ($classes as $class) {
$ref = new ReflectionClass($class);
if ($ref->isInstantiable()) $concrete[$class] = singleton($class);
}
self::$all_indexes = $concrete;
}
return self::$all_indexes;
}
else {
if (!isset(self::$indexes_by_subclass[$class])) {
$all = self::get_indexes();
$valid = array();
foreach ($all as $indexclass => $instance) {
if (ClassInfo::is_subclass_of($indexclass, $class)) $valid[$indexclass] = $instance;
}
self::$indexes_by_subclass[$class] = $valid;
}
return self::$indexes_by_subclass[$class];
}
}
/**
* Sometimes, like when in tests, you want to restrain the actual indexes to a subset
*
* Call with one argument - an array of class names, index instances or classname => indexinstance pairs (can be mixed).
* Alternatively call with multiple arguments, each of which is a class name or index instance
*
* From then on, fulltext search system will only see those indexes passed in this most recent call.
*
* Passing in no arguments resets back to automatic index list
*/
static function force_index_list() {
$indexes = func_get_args();
// No arguments = back to automatic
if (!$indexes) {
self::get_indexes(null, true);
return;
}
// Arguments can be a single array
if (is_array($indexes[0])) $indexes = $indexes[0];
// Reset to empty first
self::$all_indexes = array(); self::$indexes_by_subclass = array();
// And parse out alternative type combos for arguments and add to allIndexes
foreach ($indexes as $class => $index) {
if (is_string($index)) { $class = $index; $index = singleton($class); }
if (is_numeric($class)) $class = get_class($index);
self::$all_indexes[$class] = $index;
}
}
}

548
code/search/SearchIndex.php Normal file
View File

@ -0,0 +1,548 @@
<?php
/**
* SearchIndex is the base index class. Each connector will provide a subclass of this that
* provides search engine specific behavior.
*
* This class is responsible for:
*
* - Taking index calls adding classes and fields, and resolving those to value sources and types
*
* - Determining which records in this index need updating when a DataObject is changed
*
* - Providing utilities to the connector indexes
*
* The connector indexes are responsible for
*
* - Mapping types to index configuration
*
* - Adding and removing items to index
*
* - Parsing and converting SearchQueries into a form the engine will understand, and executing those queries
*
* The user indexes are responsible for
*
* - Specifying which classes and fields this index contains
*
* - Specifying update rules that are not extractable from metadata (because the values come from functions for instance)
*
*/
abstract class SearchIndex extends ViewableData {
function __construct() {
$this->init();
foreach ($this->getClasses() as $class => $options) {
SearchVariant::with($class, $options['include_children'])->call('alterDefinition', $class, $this);
}
$this->buildDependancyList();
}
function __toString() {
return 'Search Index ' . get_class($this);
}
/**
* Examines the classes this index is built on to try and find defined fields in the class hierarchy for those classes.
* Looks for db and viewable-data fields, although can't nessecarily find type for viewable-data fields.
*/
function fieldData($field, $forceType = null) {
$fullfield = str_replace(".", "_", $field);
$sources = $this->getClasses();
foreach ($sources as $source => $options) {
$sources[$source]['base'] = $source;
$sources[$source]['lookup_chain'] = array();
}
$found = array();
if (strpos($field, '.') !== false) {
$lookups = explode(".", $field);
$field = array_pop($lookups);
foreach ($lookups as $lookup) {
$next = array();
foreach ($sources as $source => $options) {
$class = null;
foreach (SearchIntrospection::hierarchy($source, $options['include_children']) as $dataclass) {
$singleton = singleton($dataclass);
if ($hasOne = $singleton->has_one($lookup)) {
$class = $hasOne;
$options['lookup_chain'][] = array(
'call' => 'method', 'method' => $lookup,
'through' => 'has_one', 'class' => $dataclass, 'otherclass' => $class, 'foreignkey' => "{$lookup}ID"
);
}
else if ($hasMany = $singleton->has_many($lookup)) {
$class = $hasMany;
$options['multi_valued'] = true;
$options['lookup_chain'][] = array(
'call' => 'method', 'method' => $lookup,
'through' => 'has_many', 'class' => $dataclass, 'otherclass' => $class, 'foreignkey' => $singleton->getRemoteJoinField($lookup, 'has_many')
);
}
else if ($manyMany = $singleton->many_many($lookup)) {
$class = $manyMany[0];
$options['multi_valued'] = true;
$options['lookup_chain'][] = array(
'call' => 'method', 'method' => $lookup,
'through' => 'many_many', 'class' => $dataclass, 'otherclass' => $class, 'details' => $manyMany
);
}
if ($class) {
if (!isset($options['origin'])) $options['origin'] = $dataclass;
$next[$class] = $options;
continue 2;
}
}
}
if (!$next) return $next; // Early out to avoid excessive empty looping
$sources = $next;
}
}
foreach ($sources as $class => $options) {
$dataclasses = SearchIntrospection::hierarchy($class, $options['include_children']);
while (count($dataclasses)) {
$dataclass = array_shift($dataclasses);
$type = null; $fieldoptions = $options;
$fields = DataObject::database_fields($dataclass);
if (isset($fields[$field])) {
$type = $fields[$field];
$fieldoptions['lookup_chain'][] = array('call' => 'property', 'property' => $field);
}
else {
$singleton = singleton($dataclass);
if ($singleton->hasMethod("get$field") || $singleton->hasField($field)) {
$type = $singleton->castingClass($field);
if (!$type) $type = 'String';
if ($singleton->hasMethod("get$field")) $fieldoptions['lookup_chain'][] = array('call' => 'method', 'method' => "get$field");
else $fieldoptions['lookup_chain'][] = array('call' => 'property', 'property' => $field);
}
}
if ($type) {
// Don't search through child classes of a class we matched on. TODO: Should we?
$dataclasses = array_diff($dataclasses, array_values(ClassInfo::subclassesFor($dataclass)));
// Trim arguments off the type string
if (preg_match('/^(\w+)\(/', $type, $match)) $type = $match[1];
// Get the origin
$origin = isset($fieldoptions['origin']) ? $fieldoptions['origin'] : $dataclass;
$found["{$origin}_{$fullfield}"] = array(
'name' => "{$origin}_{$fullfield}",
'field' => $field,
'fullfield' => $fullfield,
'base' => $fieldoptions['base'],
'origin' => $origin,
'class' => $dataclass,
'lookup_chain' => $fieldoptions['lookup_chain'],
'type' => $forceType ? $forceType : $type,
'multi_valued' => isset($fieldoptions['multi_valued']) ? true : false
);
}
}
}
return $found;
}
/** Public, but should only be altered by variants */
protected $classes = array();
protected $fulltextFields = array();
public $filterFields = array();
protected $sortFields = array();
/**
* Add a DataObject subclass whose instances should be included in this index
*
* Can only be called when addFulltextField, addFilterField, addSortField and addAllFulltextFields have not
* yet been called for this index instance
*
* @throws Exception
* @param String $class - The class to include
* @param array $options - TODO: Remove
*/
public function addClass($class, $options = array()) {
if ($this->fulltextFields || $this->filterFields || $this->sortFields) {
throw new Exception('Can\'t add class to Index after fields have already been added');
}
$options = array_merge(array(
'include_children' => true
), $options);
$this->classes[$class] = $options;
}
/**
* Get the classes added by addClass
*/
public function getClasses() { return $this->classes; }
/**
* Add a field that should be fulltext searchable
* @param String $field - The field to add
* @param String $forceType - The type to force this field as (required in some cases, when not detectable from metadata)
*/
public function addFulltextField($field, $forceType = null) {
$this->fulltextFields = array_merge($this->fulltextFields, $this->fieldData($field, $forceType));
}
public function getFulltextFields() { return $this->fulltextFields; }
/**
* Add a field that should be filterable
* @param String $field - The field to add
* @param String $forceType - The type to force this field as (required in some cases, when not detectable from metadata)
*/
public function addFilterField($field, $forceType = null) {
$this->filterFields = array_merge($this->filterFields, $this->fieldData($field, $forceType));
}
public function getFilterFields() { return $this->filterFields; }
/**
* Add a field that should be sortable
* @param String $field - The field to add
* @param String $forceType - The type to force this field as (required in some cases, when not detectable from metadata)
*/
public function addSortField($field, $forceType = null) {
$this->sortFields = array_merge($this->sortFields, $this->fieldData($field, $forceType));
}
public function getSortFields() { return $this->sortFields; }
/**
* Add all database-backed text fields as fulltext searchable fields.
*
* For every class included in the index, examines those classes and all subclasses looking for "Text" database
* fields (Varchar, Text, HTMLText, etc) and adds them all as fulltext searchable fields.
*/
public function addAllFulltextFields($includeSubclasses = true) {
foreach ($this->getClasses() as $class => $options) {
foreach (SearchIntrospection::hierarchy($class, $includeSubclasses, true) as $dataclass) {
$fields = DataObject::database_fields($dataclass);
foreach ($fields as $field => $type) {
if (preg_match('/^(\w+)\(/', $type, $match)) $type = $match[1];
if (ClassInfo::is_subclass_of($type, 'StringField')) $this->addFulltextField($field);
}
}
}
}
/**
* Returns an interator that will let you interate through all added fields, regardless of whether they
* were added as fulltext, filter or sort fields.
*
* @return MultipleArrayIterator
*/
public function getFieldsIterator() {
return new MultipleArrayIterator($this->fulltextFields, $this->filterFields, $this->sortFields);
}
public $dependancyList = array();
function buildDependancyList() {
$this->dependancyList = array_keys($this->getClasses());
foreach ($this->getFieldsIterator() as $name => $field) {
if (!isset($field['class'])) continue;
SearchIntrospection::add_unique_by_ancestor($this->dependancyList, $field['class']);
}
}
public $derivedFields = null;
/**
* Returns an array where each member is all the fields and the classes that are at the end of some
* specific lookup chain from one of the base classes
*/
function getDerivedFields() {
if ($this->derivedFields === null) {
$this->derivedFields = array();
foreach ($this->getFieldsIterator() as $name => $field) {
if (count($field['lookup_chain']) < 2) continue;
$key = sha1($field['base'].serialize($field['lookup_chain']));
$fieldname = "{$field['class']}:{$field['field']}";
if (isset($this->derivedFields[$key])) {
$this->derivedFields[$key]['fields'][$fieldname] = $fieldname;
SearchIntrospection::add_unique_by_ancestor($this->derivedFields['classes'], $field['class']);
}
else {
$chain = array_reverse($field['lookup_chain']);
array_shift($chain);
$this->derivedFields[$key] = array(
'base' => $field['base'],
'fields' => array($fieldname => $fieldname),
'classes' => array($field['class']),
'chain' => $chain
);
}
}
}
return $this->derivedFields;
}
/**
* Get the "document ID" (a database & variant unique id) given some "Base" class, DataObject ID and state array
*
* @param String $base - The base class of the object
* @param Integer $id - The ID of the object
* @param Array $state - The variant state of the object
* @return string - The document ID as a string
*/
function getDocumentIDForState($base, $id, $state) {
ksort($state);
$parts = array('id' => $id, 'base' => $base, 'state' => json_encode($state));
return implode('-', array_values($parts));
}
/**
* Get the "document ID" (a database & variant unique id) given some "Base" class and DataObject
*
* @param DataObject $object - The object
* @param String $base - The base class of the object
* @param Boolean $includesubs - TODO: Probably going away
* @return string - The document ID as a string
*/
function getDocumentID($object, $base, $includesubs) {
return $this->getDocumentIDForState($base, $object->ID, SearchVariant::current_state($base, $includesubs));
}
/**
* Given an object and a field definition (as returned by fieldData) get the current value of that field on that object
*
* @param DataObject $object - The object to get the value from
* @param Array $field - The field definition to use
* @return Mixed - The value of the field, or null if we couldn't look it up for some reason
*/
protected function _getFieldValue($object, $field) {
set_error_handler(create_function('$no, $str', 'throw new Exception("HTML Parse Error: ".$str);'), E_ALL);
try {
foreach ($field['lookup_chain'] as $step) {
// Just fail if we've fallen off the end of the chain
if (!$object) return null;
// If we're looking up this step on an array or DataObjectSet, do the step on every item, merge result
if (is_array($object) || $object instanceof DataObjectSet) {
$next = array();
foreach ($object as $item) {
if ($step['call'] == 'method') {
$method = $step['method'];
$item = $item->$method();
}
else {
$property = $step['property'];
$item = $item->$property;
}
if ($item instanceof DataObjectSet) $next = array_merge($next, $item->toArray());
elseif (is_array($item)) $next = array_merge($next, $item);
else $next[] = $item;
}
$object = $next;
}
// Otherwise, just call
else {
if ($step['call'] == 'method') {
$method = $step['method'];
$object = $object->$method();
}
elseif ($step['call'] == 'variant') {
$variants = SearchVariant::variants($field['base'], true);
$variant = $variants[$step['variant']]; $method = $step['method'];
$object = $variant->$method($object);
}
else {
$property = $step['property'];
$object = $object->$property;
}
}
}
}
catch (Exception $e) {
$object = null;
}
restore_error_handler();
return $object;
}
/**
* Given a class, object id, set of stateful ids and a list of changed fields (in a special format),
* return what statefulids need updating in this index
*
* Internal function used by SearchUpdater.
*
* @param $class
* @param $id
* @param $statefulids
* @param $fields
* @return array
*/
function getDirtyIDs($class, $id, $statefulids, $fields) {
$dirty = array();
// First, if this object is directly contained in the index, add it
foreach ($this->classes as $searchclass => $options) {
if ($searchclass == $class || ($options['include_children'] && ClassInfo::is_subclass_of($class, $searchclass))) {
$dirty[$searchclass] = array();
foreach ($statefulids as $statefulid) {
$key = serialize($statefulid);
$dirty[$searchclass][$key] = $statefulid;
}
}
}
$current = SearchVariant::current_state();
// Then, for every derived field
foreach ($this->getDerivedFields() as $derivation) {
// If the this object is a subclass of any of the classes we want a field from
if (!SearchIntrospection::is_subclass_of($class, $derivation['classes'])) continue;
if (!array_intersect_key($fields, $derivation['fields'])) continue;
foreach (SearchVariant::reindex_states($class, false) as $state) {
SearchVariant::activate_state($state);
$ids = array($id);
foreach ($derivation['chain'] as $step) {
if ($step['through'] == 'has_one') {
$sql = new SQLQuery('ID', $step['class'], $step['foreignkey'].' IN ('.implode(',', $ids).')');
singleton($step['class'])->extend('augmentSQL', $sql);
$ids = $sql->execute()->column();
}
else if ($step['through'] == 'has_many') {
$sql = new SQLQuery('"'.$step['class'].'"."ID"', $step['class'], '"'.$step['otherclass'].'"."ID" IN ('.implode(',', $ids).')');
$sql->innerJoin($step['otherclass'], '"'.$step['class'].'"."ID" = "'.$step['otherclass'].'"."'.$step['foreignkey'].'"');
singleton($step['class'])->extend('augmentSQL', $sql);
$ids = $sql->execute()->column();
}
}
SearchVariant::activate_state($current);
if ($ids) {
$base = $derivation['base'];
if (!isset($dirty[$base])) $dirty[$base] = array();
foreach ($ids as $id) {
$statefulid = array('id' => $id, 'state' => $state);
$key = serialize($statefulid);
$dirty[$base][$key] = $statefulid;
}
}
}
}
return $dirty;
}
/** !! These should be implemented by the full text search engine */
abstract function add($object) ;
abstract function delete($base, $id, $state) ;
abstract function commit();
/** !! These should be implemented by the specific index */
/**
* Called during construction, this is the method that builds the structure.
* Used instead of overriding __construct as we have specific execution order - code that has
* to be run before _and/or_ after this.
*/
abstract function init();
}
/**
* A search index that does nothing. Useful for testing
*/
abstract class SearchIndex_Null extends SearchIndex {
function add($object) { }
function delete($base, $id, $state) { }
function commit() { }
}
/**
* A search index that just records actions. Useful for testing
*/
abstract class SearchIndex_Recording extends SearchIndex {
public $added = array();
public $deleted = array();
function reset() {
$this->added = array();
$this->deleted = array();
}
function add($object) {
$res = array();
$res['ID'] = $object->ID;
foreach ($this->getFieldsIterator() as $name => $field) {
$val = $this->_getFieldValue($object, $field);
$res[$name] = $val;
}
$this->added[] = $res;
}
function getAdded($fields = array()) {
$res = array();
foreach ($this->added as $added) {
$filtered = array();
foreach ($fields as $field) {
if (isset($added[$field])) $filtered[$field] = $added[$field];
}
$res[] = $filtered;
}
return $res;
}
function delete($base, $id, $state) {
$this->deleted[] = array('base' => $base, 'id' => $id, 'state' => $state);
}
function commit() { }
}

View File

@ -0,0 +1,78 @@
<?php
/**
* Some additional introspection tools that are used often by the fulltext search code
*/
class SearchIntrospection {
protected static $ancestry = array();
/**
* Check if class is subclass of (a) the class in $of, or (b) any of the classes in the array $of
* @static
* @param $class
* @param $of
* @return bool
*/
static function is_subclass_of ($class, $of) {
$ancestry = isset(self::$ancestry[$class]) ? self::$ancestry[$class] : (self::$ancestry[$class] = ClassInfo::ancestry($class));
return is_array($of) ? (bool)array_intersect($of, $ancestry) : array_key_exists($of, $ancestry);
}
protected static $hierarchy = array();
/**
* Get all the classes involved in a DataObject hierarchy - both super and optionally subclasses
*
* @static
* @param String $class - The class to query
* @param bool $includeSubclasses - True to return subclasses as well as super classes
* @param bool $dataOnly - True to only return classes that have tables
* @return Array - Integer keys, String values as classes sorted by depth (most super first)
*/
static function hierarchy ($class, $includeSubclasses = true, $dataOnly = false) {
$key = "$class!" . ($includeSubclasses ? 'sc' : 'an') . '!' . ($dataOnly ? 'do' : 'al');
if (!isset(self::$hierarchy[$key])) {
$classes = array_values(ClassInfo::ancestry($class));
if ($includeSubclasses) $classes = array_unique(array_merge($classes, array_values(ClassInfo::subclassesFor($class))));
$idx = array_search('DataObject', $classes);
if ($idx !== false) array_splice($classes, 0, $idx+1);
if ($dataOnly) foreach($classes as $i => $class) {
if (!DataObject::has_own_table($class)) unset($classes[$i]);
}
self::$hierarchy[$key] = $classes;
}
return self::$hierarchy[$key];
}
/**
* Add classes to list, keeping only the parent when parent & child are both in list after add
*/
static function add_unique_by_ancestor(&$list, $class) {
// If class already has parent in list, just ignore
if (self::is_subclass_of($class, $list)) return;
// Strip out any subclasses of $class already in the list
$children = ClassInfo::subclassesFor($class);
$list = array_diff($list, $children);
// Then add the class in
$list[] = $class;
}
/**
* Does this class, it's parent (or optionally one of it's children) have the passed extension attached?
*/
static function has_extension($class, $extension, $includeSubclasses = true) {
foreach (self::hierarchy($class, $includeSubclasses) as $relatedclass) {
if (Object::has_extension($relatedclass, $extension)) return true;
}
return false;
}
}

105
code/search/SearchQuery.php Normal file
View File

@ -0,0 +1,105 @@
<?php
/**
* Represents a search query
*
* API very much still in flux. Generally, calling with multiple arguments = OR, calling multiple times = AND.
*/
class SearchQuery extends ViewableData {
static $missing = null;
static $present = null;
static $default_page_size = 10;
/** These are public, but only for index & variant access - API users should not manually access these */
public $search = array();
public $classes = array();
public $require = array();
public $exclude = array();
protected $start = 0;
protected $limit = -1;
/** These are the API functions */
function __construct() {
if (self::$missing === null) self::$missing = new stdClass();
if (self::$present === null) self::$present = new stdClass();
}
function search($text, $fields = null, $boost = 1) {
$this->search[] = array('text' => $text, 'fields' => $fields ? (array)$fields : null, 'boost' => $boost, 'fuzzy' => false);
}
function fuzzysearch($text, $fields = null, $boost = 1) {
$this->search[] = array('text' => $text, 'fields' => $fields ? (array)$fields : null, 'boost' => $boost, 'fuzzy' => true);
}
function inClass($class, $includeSubclasses = true) {
$this->classes[] = array('class' => $class, 'includeSubclasses' => $includeSubclasses);
}
function filter($field, $values) {
$requires = isset($this->require[$field]) ? $this->require[$field] : array();
$values = is_array($values) ? $values : array($values);
$this->require[$field] = array_merge($requires, $values);
}
function exclude($field, $values) {
$excludes = isset($this->exclude[$field]) ? $this->exclude[$field] : array();
$values = is_array($values) ? $values : array($values);
$this->exclude[$field] = array_merge($excludes, $values);
}
function start($start) {
$this->start = $start;
}
function limit($limit) {
$this->limit = $limit;
}
function page($page) {
$this->start = $page * self::$default_page_size;
$this->limit = self::$default_page_size;
}
function isfiltered() {
return $this->search || $this->classes || $this->require || $this->exclude;
}
function __toString() {
return "Search Query\n";
}
}
/**
* Create one of these and pass as one of the values in filter or exclude to filter or exclude by a (possibly
* open ended) range
*/
class SearchQuery_Range {
public $start = null;
public $end = null;
function __construct($start = null, $end = null) {
$this->start = $start;
$this->end = $end;
}
function start($start) {
$this->start = $start;
}
function end($end) {
$this->end = $end;
}
function isfiltered() {
return $this->start !== null || $this->end !== null;
}
}

View File

@ -0,0 +1,257 @@
<?php
/**
* This class is responsible for capturing changes to DataObjects and triggering index updates of the resulting dirty index
* items.
*
* Attached automatically by _config calling SearchUpdater#bind_manipulation_capture. Overloads the current database connector's
* manipulate method - basically we need to capture a manipulation _after_ all the augmentManipulation code (for instance Version's)
* is run
*
* Pretty closely tied to the field structure of SearchIndex.
*
* TODO: The way we bind in is awful hacky. The config stuff in 3 will hopefully allow us to force ourselves as the very last
* augmentManipulation.
*/
class SearchUpdater extends Object {
const AUTO = 0;
const DEFERRED = 1;
const IMMEDIATE = 2;
const DISABLED = 3;
/**
* How to schedule index updates at the end of the request.
*
* AUTO = IMMEDIATE if not _many_ dirty records, DEFERRED if _many_ where many is self::$auto_threshold
* DEFERRED = Use messagequeue to trigger updating indexes sometime soonish
* IMMEDIATE = Update indexes at end of request
* DISABLE = Dont update indexes
*
* If messagequeue module not installed, AUTO => IMMEDIATE and DEFERRED => DISABLED
*/
static $update_method = SearchUpdater::DEFERRED;
// How many items can be dirty before we defer updates
static $auto_threshold = 6;
// The indexing message queue
static $reindex_queue = "search_indexing";
static function set_reindexing_queue($queue) { self::$reindex_queue = $queue; }
/**
* Replace the database object with a subclass that captures all manipulations and passes them to us
*/
static function bind_manipulation_capture() {
global $databaseConfig;
$type = $databaseConfig['type'];
$file = TEMP_FOLDER."/.cache.SMC.$type";
if (!is_file($file)) {
file_put_contents($file, "<?php
class SearchManipulateCapture_$type extends $type {
function manipulate(\$manipulation) {
\$res = parent::manipulate(\$manipulation);
SearchUpdater::handle_manipulation(\$manipulation);
return \$res;
}
}
");
}
require_once($file);
$databaseConfig['type'] = 'SearchManipulateCapture_'.$type;
}
static $dirty = array(); static $dirtycount = 0;
static function add_dirty_ids($class, $statefulids, $index) {
$base = ClassInfo::baseDataClass($class);
$forclass = isset(self::$dirty[$base]) ? self::$dirty[$base] : array();
foreach ($statefulids as $statefulid) {
$id = $statefulid['id'];
$state = $statefulid['state']; $statekey = serialize($state);
if (!isset($forclass[$statekey])) {
$forclass[$statekey] = array('state' => $state, 'ids' => array($id => array($index)));
self::$dirtycount += 1;
}
else if (!isset($forclass[$statekey]['ids'][$id])) {
$forclass[$statekey]['ids'][$id] = array($index);
self::$dirtycount += 1;
}
else if (array_search($index, $forclass[$statekey]['ids'][$id]) === false) {
$forclass[$statekey]['ids'][$id][] = $index;
// dirty count stays the same
}
}
self::$dirty[$base] = $forclass;
}
static $registered = false;
/**
* Called by the SearchManiplateCapture database adapter with every manipulation made against the database.
*
* Check every index to see what objects need re-inserting into what indexes to keep the index fresh,
* but doesn't actually do it yet.
*
* TODO: This is pretty sensitive to the format of manipulation that DataObject::write produces. Specifically,
* it expects the actual class of the object to be present as a table, regardless of if any fields changed in that table
* (so a class => array( 'fields' => array() ) item), in order to find the actual class for a set of table manipulations
*/
static function handle_manipulation($manipulation) {
// First, extract any state that is in the manipulation itself
foreach ($manipulation as $table => $details) {
$manipulation[$table]['class'] = $table;
$manipulation[$table]['state'] = array();
}
SearchVariant::call('extractManipulationState', $manipulation);
// Then combine the manipulation back into object field sets
$writes = array();
foreach ($manipulation as $table => $details) {
if (!isset($details['id']) || !isset($details['fields'])) continue;
$id = $details['id'];
$state = $details['state'];
$class = $details['class'];
$fields = $details['fields'];
$base = ClassInfo::baseDataClass($class);
$key = "$id:$base:".serialize($state);
$statefulids = array(array('id' => $id, 'state' => $state));
// Is this the first table for this particular object? Then add an item to $writes
if (!isset($writes[$key])) $writes[$key] = array('base' => $base, 'class' => $class, 'id' => $id, 'statefulids' => $statefulids, 'fields' => array());
// Otherwise update the class label if it's more specific than the currently recorded one
else if (ClassInfo::is_subclass_of($class, $writes[$key]['class'])) $writes[$key]['class'] = $class;
// Update the fields
foreach ($fields as $field => $value) {
$writes[$key]['fields']["$class:$field"] = $value;
}
}
// Then extract any state that is needed for the writes
SearchVariant::call('extractManipulationWriteState', $writes);
// Then for each write, figure out what objects need updating
foreach ($writes as $write) {
// For every index
foreach (FullTextSearch::get_indexes() as $index => $instance) {
// If that index as a field from this class
if (SearchIntrospection::is_subclass_of($write['class'], $instance->dependancyList)) {
// Get the dirty IDs
$dirtyids = $instance->getDirtyIDs($write['class'], $write['id'], $write['statefulids'], $write['fields']);
// Then add then then to the global list to deal with later
foreach ($dirtyids as $dirtyclass => $ids) {
if ($ids) self::add_dirty_ids($dirtyclass, $ids, $index);
}
}
}
}
// Finally, if we do have some work to do register the shutdown function to actually do the work
// Don't do it if we're testing - there's no database connection outside the test methods, so we'd
// just get errors
if (self::$dirty && !self::$registered && !(class_exists('SapphireTest',false) && SapphireTest::is_running_test())) {
register_shutdown_function(array("SearchUpdater", "flush_dirty_indexes"));
self::$registered = true;
}
}
/**
* Throw away the recorded dirty IDs without doing anything with them.
*/
static function clear_dirty_indexes() {
self::$dirty = array(); self::$dirtycount = 0;
}
/**
* Do something with the recorded dirty IDs, where that "something" depends on the value of self::$update_method,
* either immediately update the indexes, queue a messsage to update the indexes at some point in the future, or
* just throw the dirty IDs away.
*/
static function flush_dirty_indexes() {
if (!self::$dirty) return;
$method = self::$update_method;
if (class_exists("MessageQueue")) {
if ($method == self::AUTO) $method = self::$dirtycount < self::$auto_threshold ? self::IMMEDIATE : self::DEFERRED;
}
else {
if ($method == self::AUTO) $method = self::IMMEDIATE;
elseif ($method == self::DEFERRED) $method = self::DISABLED;
}
switch ($method) {
case self::IMMEDIATE:
self::process_dirty_indexes(self::$dirty);
break;
case self::DEFERRED:
MessageQueue::send(
self::$reindex_queue,
new MethodInvocationMessage("SearchUpdater", "process_dirty_indexes", self::$dirty)
);
break;
case self::DISABLED:
// NOP
break;
}
self::clear_dirty_indexes();
}
/**
* Internal function. Process the passed list of dirty ids. Split from flush_dirty_indexes so it can be called both
* directly and via messagequeue message.
*
* WARNING: Changes state (subsite, stage) and doesn't reset it. Should only be called after request has ended
*/
static function process_dirty_indexes($dirty) {
$indexes = FullTextSearch::get_indexes();
$dirtyindexes = array();
foreach ($dirty as $base => $statefulids) {
if (!$statefulids) continue;
foreach ($statefulids as $statefulid) {
$state = $statefulid['state'];
$ids = $statefulid['ids'];
SearchVariant::activate_state($state);
$objs = DataObject::get($base, '"'.$base.'"."ID" IN ('.implode(',', array_keys($ids)).')');
if ($objs) foreach ($objs as $obj) {
foreach ($ids[$obj->ID] as $index) { $indexes[$index]->add($obj); $dirtyindexes[$index] = $index; }
unset($ids[$obj->ID]);
}
foreach ($ids as $id => $fromindexes) {
foreach ($fromindexes as $index) { $indexes[$index]->delete($base, $id, $state); $dirtyindexes[$index] = $index; }
}
}
}
foreach ($dirtyindexes as $index) {
$indexes[$index]->commit();
}
}
}

View File

@ -0,0 +1,191 @@
<?php
/**
* A Search Variant handles decorators and other situations where the items to reindex or search through are modified
* from the default state - for instance, dealing with Versioned or Subsite
*/
abstract class SearchVariant {
function __construct() {}
/*** OVERRIDES start here */
/**
* Variants can provide any functions they want, but they _must_ override these functions
* with specific ones
*/
/**
* Return true if this variant applies to the passed class & subclass
*/
abstract function appliesTo($class, $includeSubclasses);
/**
* Return the current state
*/
abstract function currentState();
/**
* Return all states to step through to reindex all items
*/
abstract function reindexStates();
/**
* Activate the passed state
*/
abstract function activateState($state);
/*** OVERRIDES end here*/
/** Holds a cache of all variants */
protected static $variants = null;
/** Holds a cache of the variants keyed by "class!" "1"? (1 = include subclasses) */
protected static $class_variants = array();
/**
* Returns an array of variants.
*
* With no arguments, returns all variants
*
* With a classname as the first argument, returns the variants that apply to that class
* (optionally including subclasses)
*
* @static
* @param string $class - The class name to get variants for
* @param bool $includeSubclasses - True if variants should be included if they apply to at least one subclass of $class
* @return array - An array of (string)$variantClassName => (Object)$variantInstance pairs
*/
public static function variants($class = null, $includeSubclasses = true) {
if (!$class) {
if (self::$variants === null) {
$classes = ClassInfo::subclassesFor('SearchVariant');
$concrete = array();
foreach ($classes as $variantclass) {
$ref = new ReflectionClass($variantclass);
if ($ref->isInstantiable()) $concrete[$variantclass] = singleton($variantclass);
}
self::$variants = $concrete;
}
return self::$variants;
}
else {
$key = $class . '!' . $includeSubclasses;
if (!isset(self::$class_variants[$key])) {
self::$class_variants[$key] = array();
foreach (self::variants() as $variantclass => $instance) {
if ($instance->appliesTo($class, $includeSubclasses)) self::$class_variants[$key][$variantclass] = $instance;
}
}
return self::$class_variants[$key];
}
}
/** Holds a cache of SearchVariant_Caller instances, one for each class/includeSubclasses setting */
protected static $call_instances = array();
/**
* Lets you call any function on all variants that support it, in the same manner as "Object#extend" calls
* a method from extensions.
*
* Usage: SearchVariant::with(...)->call($method, $arg1, ...);
*
* @static
*
* @param string $class - (Optional) a classname. If passed, only variants that apply to that class will be checked / called
*
* @param bool $includeSubclasses - (Optional) If false, only variants that apply strictly to the passed class or its super-classes
* will be checked. If true (the default), variants that apply to any sub-class of the passed class with also be checked
*
* @return An object with one method, call()
*/
static function with($class = null, $includeSubclasses = true) {
// Make the cache key
$key = $class ? $class . '!' . $includeSubclasses : '!';
// If no SearchVariant_Caller instance yet, create it
if (!isset(self::$call_instances[$key])) self::$call_instances[$key] = new SearchVariant_Caller(self::variants($class, $includeSubclasses));
// Then return it
return self::$call_instances[$key];
}
/**
* A shortcut to with when calling without passing in a class,
*
* SearchVariant::call(...) ==== SearchVariant::with()->call(...);
*/
static function call($method, &$a1=null, &$a2=null, &$a3=null, &$a4=null, &$a5=null, &$a6=null, &$a7=null) {
return self::with()->call($method, $a1, $a2, $a3, $a4, $a5, $a6, $a7);
}
/**
* Get the current state of every variant
* @static
* @return array
*/
static function current_state($class = null, $includeSubclasses = true) {
$state = array();
foreach (self::variants($class, $includeSubclasses) as $variant => $instance) {
$state[$variant] = $instance->currentState();
}
return $state;
}
/**
* Activate all the states in the passed argument
* @static
* @param (array) $state. A set of (string)$variantClass => (any)$state pairs , e.g. as returned by
* SearchVariant::current_state()
* @return void
*/
static function activate_state($state) {
foreach (self::variants() as $variant => $instance) {
if (isset($state[$variant])) $instance->activateState($state[$variant]);
}
}
/**
* Return an iterator that, when used in a for loop, activates one combination of reindex states per loop, and restores
* back to the original state at the end
* @static
* @param string $class - The class name to get variants for
* @param bool $includeSubclasses - True if variants should be included if they apply to at least one subclass of $class
* @return SearchVariant_ReindexStateIteratorRet - The iterator to foreach loop over
*/
static function reindex_states($class = null, $includeSubclasses = true) {
$allstates = array();
foreach (self::variants($class, $includeSubclasses) as $variant => $instance) {
if ($states = $instance->reindexStates()) $allstates[$variant] = $states;
}
return $allstates ? new CombinationsArrayIterator($allstates) : array(array());
}
}
/**
* Internal utility class used to hold the state of the SearchVariant::with call
*/
class SearchVariant_Caller {
protected $variants = null;
function __construct($variants) {
$this->variants = $variants;
}
function call($method, &$a1=null, &$a2=null, &$a3=null, &$a4=null, &$a5=null, &$a6=null, &$a7=null) {
$values = array();
foreach ($this->variants as $variant) {
if (method_exists($variant, $method)) {
$value = $variant->$method($a1, $a2, $a3, $a4, $a5, $a6, $a7);
if ($value !== null) $values[] = $value;
}
}
return $values;
}
}

View File

@ -0,0 +1,81 @@
<?php
class SearchVariantSiteTreeSubsitesPolyhome extends SearchVariant {
function appliesTo($class, $includeSubclasses) {
return SearchIntrospection::has_extension($class, 'SiteTreeSubsitesPolyhome', $includeSubclasses);
}
function currentState() {
return Subsite::currentSubsiteID();
}
function reindexStates() {
static $ids = null;
if ($ids === null) {
$ids = array(0);
foreach (DataObject::get('Subsite') as $subsite) $ids[] = $subsite->ID;
}
return $ids;
}
function activateState($state) {
if (Controller::has_curr()) {
Subsite::changeSubsite($state);
}
else {
// TODO: This is a nasty hack - calling Subsite::changeSubsite after request ends
// throws error because no current controller to access session on
$_REQUEST['SubsiteID'] = $state;
}
}
function alterDefinition($base, $index) {
$self = get_class($this);
$index->filterFields['_subsite'] = array(
'name' => '_subsite',
'field' => '_subsite',
'fullfield' => '_subsite',
'base' => $base,
'origin' => $base,
'type' => 'Int',
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState'))
);
}
function alterQuery($query, $index) {
$subsite = Subsite::currentSubsiteID();
$query->filter('_subsite', array($subsite, SearchQuery::$missing));
}
static $subsites = null;
/**
* We need _really_ complicated logic to find just the changed subsites (because we use versions there's no explicit
* deletes, just new versions with different members) so just always use all of them
*/
function extractManipulationWriteState(&$writes) {
$self = get_class($this);
foreach ($writes as $key => $write) {
if (!$this->appliesTo($write['class'], true)) continue;
if (self::$subsites === null) {
$query = new SQLQuery('ID', 'Subsite');
self::$subsites = array_merge(array('0'), $query->execute()->column());
}
$next = array();
foreach ($write['statefulids'] as $i => $statefulid) {
foreach (self::$subsites as $subsiteID) {
$next[] = array('id' => $statefulid['id'], 'state' => array_merge($statefulid['state'], array($self => $subsiteID)));
}
}
$writes[$key]['statefulids'] = $next;
}
}
}

View File

@ -0,0 +1,66 @@
<?php
class SearchVariantVersioned extends SearchVariant {
function appliesTo($class, $includeSubclasses) {
return SearchIntrospection::has_extension($class, 'Versioned', $includeSubclasses);
}
function currentState() { return Versioned::current_stage(); }
function reindexStates() { return array('Stage', 'Live'); }
function activateState($state) { Versioned::reading_stage($state); }
function alterDefinition($base, $index) {
$self = get_class($this);
$index->filterFields['_versionedstage'] = array(
'name' => '_versionedstage',
'field' => '_versionedstage',
'fullfield' => '_versionedstage',
'base' => $base,
'origin' => $base,
'type' => 'String',
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState'))
);
}
function alterQuery($query) {
$stage = Versioned::current_stage();
$query->filter('_versionedstage', array($stage, SearchQuery::$missing));
}
function extractManipulationState(&$manipulation) {
$self = get_class($this);
foreach ($manipulation as $table => $details) {
$class = $details['class'];
$stage = 'Stage';
if (preg_match('/^(.*)_Live$/', $table, $matches)) {
$class = $matches[1];
$stage = 'Live';
}
if (ClassInfo::exists($class) && $this->appliesTo($class, false)) {
$manipulation[$table]['class'] = $class;
$manipulation[$table]['state'][$self] = $stage;
}
}
}
function extractStates(&$table, &$ids, &$fields) {
$class = $table;
$suffix = null;
if (ClassInfo::exists($class) && $this->appliesTo($class, false)) {
$table = $class;
$self = get_class($this);
foreach ($ids as $i => $statefulid) {
$ids[$i]['state'][$self] = $suffix ? $suffix : 'Stage';
}
}
}
}

219
code/solr/Solr.php Normal file
View File

@ -0,0 +1,219 @@
<?php
class Solr {
/**
* Configuration on where to find the solr server and how to get new index configurations into it.
*
* Required fields:
* host (default: localhost) - The host or IP Solr is listening on
* port (default: 8983) - The port Solr is listening on
* path (default: /solr) - The suburl the solr service is available on
*
* indexstore => an array with
*
* mode - 'file' or 'webdav'
*
* When mode == file (indexes should be written on a local filesystem)
* path - The (locally accessible) path to write the index configurations to.
* remotepath (default: the same as indexpath) - The path that the Solr server will read the index configurations from
*
* When mode == webdav (indexes should stored on a remote Solr server via webdav)
* auth (default: none) - A username:password pair string to use to auth against the webdav server
* path (default: /solrindex) - The suburl on the solr host that is set up to accept index configurations via webdav
* remotepath - The path that the Solr server will read the index configurations from
*/
static $solr_options = array();
static function configure_server($options = array()) {
self::$solr_options = array_merge(array(
'host' => 'localhost',
'port' => 8983,
'path' => '/solr'
), self::$solr_options, $options);
}
static protected $service_class = 'SolrService';
static function set_service_class($class) {
self::$service_class = $class;
self::$service = null;
}
static protected $service = null;
static function service($core = null) {
if (!self::$service) {
if (!self::$solr_options) user_error('No configuration for Solr server provided', E_USER_ERROR);
$class = self::$service_class;
self::$service = new $class(self::$solr_options['host'], self::$solr_options['port'], self::$solr_options['path']);
}
return $core ? self::$service->serviceForCore($core) : self::$service;
}
static function get_indexes() {
return FullTextSearch::get_indexes('SolrIndex');
}
/**
* Include the thirdparty Solr client api library. Done this way to avoid issues where code is called in mysite/_config
* before solr/_config has a change to update the include path.
*/
static function include_client_api() {
static $included = false;
if (!$included) {
set_include_path(get_include_path() . PATH_SEPARATOR . Director::baseFolder() . '/solr/thirdparty/solr-php-client');
require_once('Apache/Solr/Service.php');
require_once('Apache/Solr/Document.php');
$included = true;
}
}
}
class Solr_Configure extends BuildTask {
public function run($request) {
$service = Solr::service();
if (!isset(Solr::$solr_options['indexstore']) || !($index = Solr::$solr_options['indexstore'])) {
user_error('No index configuration for Solr provided', E_USER_ERROR);
}
$remote = null;
switch ($index['mode']) {
case 'file':
$local = $index['path'];
$remote = isset($index['remotepath']) ? $index['remotepath'] : $local;
foreach (Solr::get_indexes() as $index => $instance) {
$confdir = "$local/$index/conf";
if (!is_dir($confdir)) mkdir($confdir, 0770, true);
file_put_contents("$confdir/schema.xml", $instance->generateSchema());
foreach (glob(Director::baseFolder().'/solr/conf/extras/*') as $file) {
if (is_file($file)) copy($file, $confdir.'/'.basename($file));
}
}
break;
case 'webdav':
$url = implode('', array(
'http://',
isset($index['auth']) ? $index['auth'].'@' : '',
Solr::$solr_options['host'] . ':' . Solr::$solr_options['port'],
$index['path']
));
$remote = $index['remotepath'];
foreach (Solr::get_indexes() as $index => $instance) {
$indexdir = "$url/$index";
if (!WebDAV::exists($indexdir)) WebDAV::mkdir($indexdir);
$confdir = "$url/$index/conf";
if (!WebDAV::exists($confdir)) WebDAV::mkdir($confdir);
WebDAV::upload_from_string($instance->generateSchema(), "$confdir/schema.xml");
foreach (glob(Director::baseFolder().'/solr/conf/extras/*') as $file) {
if (is_file($file)) WebDAV::upload_from_file($file, $confdir.'/'.basename($file));
}
}
break;
default:
user_error('Unknown Solr index mode '.$index['mode'], E_USER_ERROR);
}
if ($service->coreIsActive($index)) $service->coreReload($index);
else $service->coreCreate($index, "$remote/$index");
}
}
class Solr_Reindex extends BuildTask {
static $recordsPerRequest = 200;
public function run($request) {
increase_time_limit_to();
$self = get_class($this);
$originalState = SearchVariant::current_state();
if (isset($_GET['start'])) {
$this->runFrom(singleton($_GET['index']), $_GET['class'], $_GET['start'], json_decode($_GET['variantstate'], true));
}
else {
$script = sprintf('%s%ssapphire%scli-script.php', BASE_PATH, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR);
$class = get_class($this);
foreach (Solr::get_indexes() as $index => $instance) {
echo "Rebuilding {$instance->getIndexName()}\n\n";
Solr::service($index)->deleteByQuery('*:*');
foreach ($instance->getClasses() as $class => $options) {
$includeSubclasses = $options['include_children'];
foreach (SearchVariant::reindex_states($class, $includeSubclasses) as $state) {
SearchVariant::activate_state($state);
$filter = $includeSubclasses ? "" : '"ClassName" = \''.$class."'";
$singleton = singleton($class);
$query = $singleton->buildSQL($filter);
$query->select('COUNT("'.$class.'"."ID")');
$query->orderby = null;
$singleton->extend('augmentSQL', $query);
$total = $query->execute()->value();
$statevar = json_encode($state);
echo "Class: $class, total: $total in state $statevar\n";
if (strpos(PHP_OS, "WIN") !== false) $statevar = '"'.str_replace('"', '\\"', $statevar).'"';
else $statevar = "'".$statevar."'";
for ($offset = 0; $offset < $total; $offset += $this->stat('recordsPerRequest')) {
echo "$offset..";
$res = `php $script dev/tasks/$self index=$index class=$class start=$offset variantstate=$statevar`;
if (isset($_GET['verbose'])) echo "\n ".preg_replace('/\r\n|\n/', '$0 ', $res)."\n";
// If we're in dev mode, commit more often for fun and profit
if (Director::isDev()) Solr::service($index)->commit();
}
}
}
Solr::service($index)->commit();
}
}
$originalState = SearchVariant::current_state();
}
protected function runFrom($index, $class, $start, $variantstate) {
$classes = $index->getClasses();
$options = $classes[$class];
echo "Variant: "; print_r($variantstate);
SearchVariant::activate_state($variantstate);
$includeSubclasses = $options['include_children'];
$filter = $includeSubclasses ? "" : '"ClassName" = \''.$class."'";
$items = DataObject::get($class, $filter, "", "", array('limit' => $this->stat('recordsPerRequest'), 'start' => $start));
foreach ($items as $item) { $index->add($item); $item->destroy(); }
}
}

280
code/solr/SolrIndex.php Normal file
View File

@ -0,0 +1,280 @@
<?php
Solr::include_client_api();
abstract class SolrIndex extends SearchIndex {
static $fulltextTypeMap = array(
'*' => 'text',
'HTMLVarchar' => 'htmltext',
'HTMLText' => 'htmltext'
);
static $filterTypeMap = array(
'*' => 'string',
'Boolean' => 'boolean',
'Date' => 'tdate',
'SSDatetime' => 'tdate',
'SS_Datetime' => 'tdate',
'ForeignKey' => 'tint',
'Int' => 'tint',
'Float' => 'tfloat',
'Double' => 'tdouble'
);
function generateSchema() {
return $this->renderWith(Director::baseFolder() . '/solr/conf/templates/schema.ss');
}
function getIndexName() {
return get_class($this);
}
function getTypes() {
return $this->renderWith(Director::baseFolder() . '/solr/conf/templates/types.ss');
}
function getFieldDefinitions() {
$xml = array();
$stored = Director::isDev() ? "stored='true'" : "stored='false'";
$xml[] = "";
// Add the hardcoded field definitions
$xml[] = "<field name='_documentid' type='string' indexed='true' stored='true' required='true' />";
$xml[] = "<field name='ID' type='tint' indexed='true' stored='true' required='true' />";
$xml[] = "<field name='ClassName' type='string' indexed='true' stored='true' required='true' />";
$xml[] = "<field name='ClassHierarchy' type='string' indexed='true' stored='true' required='true' multiValued='true' />";
// Add the fulltext collation field
$xml[] = "<field name='_text' type='htmltext' indexed='true' $stored multiValued='true' />" ;
// Add the user-specified fields
foreach ($this->fulltextFields as $name => $field) {
$type = isset(self::$fulltextTypeMap[$field['type']]) ? self::$fulltextTypeMap[$field['type']] : self::$fulltextTypeMap['*'];
$xml[] = "<field name='{$name}' type='$type' indexed='true' $stored />";
}
foreach ($this->filterFields as $name => $field) {
if ($field['fullfield'] == 'ID' || $field['fullfield'] == 'ClassName') continue;
$multiValued = (isset($field['multi_valued']) && $field['multi_valued']) ? "multiValued='true'" : '';
$type = isset(self::$filterTypeMap[$field['type']]) ? self::$filterTypeMap[$field['type']] : self::$filterTypeMap['*'];
$xml[] = "<field name='{$name}' type='{$type}' indexed='true' $stored $multiValued />";
}
foreach ($this->sortFields as $name => $field) {
if ($field['fullfield'] == 'ID' || $field['fullfield'] == 'ClassName') continue;
$multiValued = (isset($field['multi_valued']) && $field['multi_valued']) ? "multiValued='true'" : '';
$type = self::$sortTypeMap[$field['type']];
$xml[] = "<field name='{$name}' type='{$type}' indexed='true' $stored $multiValued />";
}
return implode("\n\t\t", $xml);
}
function getCopyFieldDefinitions() {
$xml = array();
foreach ($this->fulltextFields as $name => $field) {
$xml[] = "<copyField source='{$name}' dest='_text' />";
}
return implode("\n\t", $xml);
}
protected function _addField($doc, $object, $field) {
$class = get_class($object);
if ($class != $field['origin'] && !ClassInfo::is_subclass_of($class, $field['origin'])) return;
$value = $this->_getFieldValue($object, $field);
$type = isset(self::$filterTypeMap[$field['type']]) ? self::$filterTypeMap[$field['type']] : self::$filterTypeMap['*'];
if (is_array($value)) foreach($value as $sub) {
/* Solr requires dates in the form 1995-12-31T23:59:59Z */
if ($type == 'tdate') $sub = gmdate('Y-m-d\TH:i:s\Z', strtotime($sub));
/* Solr requires numbers to be valid if presented, not just empty */
if (($type == 'tint' || $type == 'tfloat' || $type == 'tdouble') && !is_numeric($sub)) continue;
$doc->addField($field['name'], $sub);
}
else {
/* Solr requires dates in the form 1995-12-31T23:59:59Z */
if ($type == 'tdate') $value = gmdate('Y-m-d\TH:i:s\Z', strtotime($value));
/* Solr requires numbers to be valid if presented, not just empty */
if (($type == 'tint' || $type == 'tfloat' || $type == 'tdouble') && !is_numeric($value)) return;
$doc->setField($field['name'], $value);
}
}
protected function _addAs($object, $base, $options) {
$includeSubs = $options['include_children'];
$doc = new Apache_Solr_Document();
// Always present fields
$doc->setField('_documentid', $this->getDocumentID($object, $base, $includeSubs));
$doc->setField('ID', $object->ID);
$doc->setField('ClassName', $object->ClassName);
foreach (SearchIntrospection::hierarchy(get_class($object), false) as $class) $doc->addField('ClassHierarchy', $class);
// Add the user-specified fields
foreach ($this->getFieldsIterator() as $name => $field) {
if ($field['base'] == $base) $this->_addField($doc, $object, $field);
}
Solr::service(get_class($this))->addDocument($doc);
}
function add($object) {
$class = get_class($object);
foreach ($this->getClasses() as $searchclass => $options) {
if ($searchclass == $class || ($options['include_children'] && ClassInfo::is_subclass_of($class, $searchclass))) {
$this->_addAs($object, $searchclass, $options);
}
}
}
function canAdd($class) {
foreach ($this->classes as $searchclass => $options) {
if ($searchclass == $class || ($options['include_children'] && ClassInfo::is_subclass_of($class, $searchclass))) return true;
}
return false;
}
function delete($base, $id, $state) {
$documentID = $this->getDocumentIDForState($base, $id, $state);
Solr::service(get_class($this))->deleteById($documentID);
}
function commit() {
Solr::service(get_class($this))->commit(false, false, false);
}
public function search($query, $offset = -1, $limit = -1) {
$service = Solr::service(get_class($this));
SearchVariant::with(count($query->classes) == 1 ? $query->classes[0]['class'] : null)->call('alterQuery', $query, $this);
$q = array();
$fq = array();
// Build the search itself
foreach ($query->search as $search) {
$text = $search['text'];
preg_match_all('/"[^"]*"|\S+/', $text, $parts);
$fuzzy = $search['fuzzy'] ? '~' : '';
foreach ($parts[0] as $part) {
if ($search['fields']) {
$searchq = array();
foreach ($search['fields'] as $field) {
$searchq[] = "{$field}:".$part.$fuzzy;
}
$q[] = '+('.implode(' ', $searchq).')';
}
else {
$q[] = '+'.$part;
}
}
}
// Filter by class if requested
$classq = array();
foreach ($query->classes as $class) {
if ($class['includeSubclasses']) $classq[] = 'ClassHierarchy:'.$class['class'];
else $classq[] = 'ClassName:'.$class['class'];
}
if ($classq) $fq[] = '+('.implode(' ', $classq).')';
// Filter by filters
foreach ($query->require as $field => $values) {
$requireq = array();
foreach ($values as $value) {
if ($value === SearchQuery::$missing) {
$requireq[] = "(*:* -{$field}:[* TO *])";
}
else if ($value === SearchQuery::$present) {
$requireq[] = "{$field}:[* TO *]";
}
else if ($value instanceof SearchQuery_Range) {
$start = $value->start; if ($start === null) $start = '*';
$end = $value->end; if ($end === null) $end = '*';
$requireq[] = "$field:[$start TO $end]";
}
else {
$requireq[] = $field.':"'.$value.'"';
}
}
$fq[] = '+('.implode(' ', $requireq).')';
}
foreach ($query->exclude as $field => $values) {
$excludeq = array();
$missing = false;
foreach ($values as $value) {
if ($value === SearchQuery::$missing) {
$missing = true;
}
else if ($value === SearchQuery::$present) {
$excludeq[] = "{$field}:[* TO *]";
}
else if ($value instanceof SearchQuery_Range) {
$start = $value->start; if ($start === null) $start = '*';
$end = $value->end; if ($end === null) $end = '*';
$excludeq[] = "$field:[$start TO $end]";
}
else {
$excludeq[] = $field.':"'.$value.'"';
}
}
$fq[] = ($missing ? "+{$field}:[* TO *] " : '') . '-('.implode(' ', $excludeq).')';
}
if ($q) header('X-Query: '.implode(' ', $q));
if ($fq) header('X-Filters: "'.implode('", "', $fq).'"');
if ($offset == -1) $offset = $query->start;
if ($limit == -1) $limit = $query->limit;
if ($limit == -1) $limit = SearchQuery::$default_page_size;
$res = $service->search($q ? implode(' ', $q) : '*:*', $offset, $limit, array('fq' => implode(' ', $fq)), Apache_Solr_Service::METHOD_POST);
$results = array();
foreach ($res->response->docs as $doc) {
$result = DataObject::get_by_id($doc->ClassName, $doc->ID);
if ($result) $results[] = $result;
}
$ret = array();
$ret['Matches'] = new DataObjectSet($results);
$ret['Matches']->setPageLimits($offset, $limit, $res->numFound);
return new ArrayData($ret);
}
}

43
code/solr/SolrService.php Normal file
View File

@ -0,0 +1,43 @@
<?php
Solr::include_client_api();
class SolrService extends Apache_Solr_Service {
protected function coreCommand($command, $core, $params=array()) {
$command = strtoupper($command);
$params = array_merge($params, array('action' => $command, 'wt' => 'json'));
$params[$command == 'CREATE' ? 'name' : 'core'] = $core;
return $this->_sendRawGet($this->_constructUrl('admin/cores', $params));
}
public function coreIsActive($core) {
$result = $this->coreCommand('STATUS', $core);
return isset($result->status->$core->uptime);
}
public function coreCreate($core, $instancedir, $config=null, $schema=null, $datadir=null) {
$args = array('instanceDir' => $instancedir);
if ($config) $args['config'] = $config;
if ($schema) $args['schema'] = $schema;
if ($datadir) $args['dataDir'] = $datadir;
$this->coreCommand('CREATE', $core, $args);
}
public function coreReload($core) {
$this->coreCommand('RELOAD', $core);
}
protected $_serviceCache = array();
public function serviceForCore($core) {
if (!isset($this->_serviceCache[$core])) {
$this->_serviceCache[$core] = new Apache_Solr_Service($this->_host, $this->_port, $this->_path."$core", $this->_httpTransport);
}
return $this->_serviceCache[$core];
}
}

View File

@ -0,0 +1,66 @@
<?php
class CombinationsArrayIterator implements Iterator {
protected $arrays;
protected $keys;
protected $numArrays;
protected $isValid = false;
protected $k = 0;
function __construct($args) {
$this->arrays = array();
$this->keys = array();
$keys = array_keys($args);
$values = array_values($args);
foreach ($values as $i => $arg) {
if (is_array($arg) && count($arg)) {
$this->arrays[] = $arg;
$this->keys[] = $keys[$i];
}
}
$this->numArrays = count($this->arrays);
$this->rewind();
}
function rewind() {
if (!$this->numArrays) {
$this->isValid = false;
}
else {
$this->isValid = true;
$this->k = 0;
for ($i = 0; $i < $this->numArrays; $i++) reset($this->arrays[$i]);
}
}
function valid() {
return $this->isValid;
}
function next() {
$this->k++;
for ($i = 0; $i < $this->numArrays; $i++) {
if (next($this->arrays[$i]) === false) {
if ($i == $this->numArrays-1) $this->isValid = false;
else reset($this->arrays[$i]);
}
else break;
}
}
function current() {
$res = array();
for ($i = 0; $i < $this->numArrays; $i++) $res[$this->keys[$i]] = current($this->arrays[$i]);
return $res;
}
function key() {
return $this->k;
}
}

View File

@ -0,0 +1,44 @@
<?php
class MultipleArrayIterator implements Iterator {
protected $arrays;
protected $active;
function __construct() {
$args = func_get_args();
$this->arrays = array();
foreach ($args as $arg) {
if (is_array($arg) && count($arg)) $this->arrays[] = $arg;
}
$this->rewind();
}
function rewind() {
$this->active = $this->arrays;
if ($this->active) reset($this->active[0]);
}
function current() {
return $this->active ? current($this->active[0]) : false;
}
function key() {
return $this->active ? key($this->active[0]) : false;
}
function next() {
if (!$this->active) return;
if (next($this->active[0]) === false) {
array_shift($this->active);
if ($this->active) reset($this->active[0]);
}
}
function valid() {
return $this->active && (current($this->active[0]) !== false);
}
}

63
code/utils/WebDAV.php Normal file
View File

@ -0,0 +1,63 @@
<?php
class WebDAV {
static function curl_init($url, $method) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
return $ch;
}
static function exists($url) {
$ch = self::curl_init($url, 'PROPFIND');
$res = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($code == 404) return false;
if ($code == 200 || $code == 207) return true;
user_error("Got error from webdav server - ".$code, E_USER_ERROR);
}
static function mkdir($url) {
$ch = self::curl_init(rtrim($url, '/').'/', 'MKCOL');
$res = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return $code == 201;
}
static function put($handle, $url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($ch, CURLOPT_PUT, true);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
curl_setopt($ch, CURLOPT_INFILE, $handle);
$res = curl_exec($ch);
fclose($handle);
return curl_getinfo($ch, CURLINFO_HTTP_CODE);
}
static function upload_from_string($string, $url) {
$fh = tmpfile();
fwrite($fh, $string);
fseek($fh, 0);
return self::put($fh, $url);
}
static function upload_from_file($string, $url) {
return self::put(fopen($string, 'rb'), $url);
}
}

36
conf/extras/elevate.xml Normal file
View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.
-->
<!-- If this file is found in the config directory, it will only be
loaded once at startup. If it is found in Solr's data
directory, it will be re-loaded every commit.
-->
<elevate>
<query text="foo bar">
<doc id="1" />
<doc id="2" />
<doc id="3" />
</query>
<query text="ipod">
<doc id="MA147LL/A" /> <!-- put the actual ipod at the top -->
<doc id="IW-02" exclude="true" /> <!-- exclude this cable -->
</query>
</elevate>

View File

@ -0,0 +1,3813 @@
# The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.
# This map converts alphabetic, numeric, and symbolic Unicode characters
# which are not in the first 127 ASCII characters (the "Basic Latin" Unicode
# block) into their ASCII equivalents, if one exists.
#
# Characters from the following Unicode blocks are converted; however, only
# those characters with reasonable ASCII alternatives are converted:
#
# - C1 Controls and Latin-1 Supplement: http://www.unicode.org/charts/PDF/U0080.pdf
# - Latin Extended-A: http://www.unicode.org/charts/PDF/U0100.pdf
# - Latin Extended-B: http://www.unicode.org/charts/PDF/U0180.pdf
# - Latin Extended Additional: http://www.unicode.org/charts/PDF/U1E00.pdf
# - Latin Extended-C: http://www.unicode.org/charts/PDF/U2C60.pdf
# - Latin Extended-D: http://www.unicode.org/charts/PDF/UA720.pdf
# - IPA Extensions: http://www.unicode.org/charts/PDF/U0250.pdf
# - Phonetic Extensions: http://www.unicode.org/charts/PDF/U1D00.pdf
# - Phonetic Extensions Supplement: http://www.unicode.org/charts/PDF/U1D80.pdf
# - General Punctuation: http://www.unicode.org/charts/PDF/U2000.pdf
# - Superscripts and Subscripts: http://www.unicode.org/charts/PDF/U2070.pdf
# - Enclosed Alphanumerics: http://www.unicode.org/charts/PDF/U2460.pdf
# - Dingbats: http://www.unicode.org/charts/PDF/U2700.pdf
# - Supplemental Punctuation: http://www.unicode.org/charts/PDF/U2E00.pdf
# - Alphabetic Presentation Forms: http://www.unicode.org/charts/PDF/UFB00.pdf
# - Halfwidth and Fullwidth Forms: http://www.unicode.org/charts/PDF/UFF00.pdf
#
# See: http://en.wikipedia.org/wiki/Latin_characters_in_Unicode
#
# The set of character conversions supported by this map is a superset of
# those supported by the map represented by mapping-ISOLatin1Accent.txt.
#
# See the bottom of this file for the Perl script used to generate the contents
# of this file (without this header) from ASCIIFoldingFilter.java.
# Syntax:
# "source" => "target"
# "source".length() > 0 (source cannot be empty.)
# "target".length() >= 0 (target can be empty.)
# À [LATIN CAPITAL LETTER A WITH GRAVE]
"\u00C0" => "A"
# Á [LATIN CAPITAL LETTER A WITH ACUTE]
"\u00C1" => "A"
# Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
"\u00C2" => "A"
# Ã [LATIN CAPITAL LETTER A WITH TILDE]
"\u00C3" => "A"
# Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
"\u00C4" => "A"
# Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
"\u00C5" => "A"
# Ā [LATIN CAPITAL LETTER A WITH MACRON]
"\u0100" => "A"
# Ă [LATIN CAPITAL LETTER A WITH BREVE]
"\u0102" => "A"
# Ą [LATIN CAPITAL LETTER A WITH OGONEK]
"\u0104" => "A"
# Ə http://en.wikipedia.org/wiki/Schwa [LATIN CAPITAL LETTER SCHWA]
"\u018F" => "A"
# Ǎ [LATIN CAPITAL LETTER A WITH CARON]
"\u01CD" => "A"
# Ǟ [LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON]
"\u01DE" => "A"
# Ǡ [LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON]
"\u01E0" => "A"
# Ǻ [LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE]
"\u01FA" => "A"
# Ȁ [LATIN CAPITAL LETTER A WITH DOUBLE GRAVE]
"\u0200" => "A"
# Ȃ [LATIN CAPITAL LETTER A WITH INVERTED BREVE]
"\u0202" => "A"
# Ȧ [LATIN CAPITAL LETTER A WITH DOT ABOVE]
"\u0226" => "A"
# Ⱥ [LATIN CAPITAL LETTER A WITH STROKE]
"\u023A" => "A"
# ᴀ [LATIN LETTER SMALL CAPITAL A]
"\u1D00" => "A"
# Ḁ [LATIN CAPITAL LETTER A WITH RING BELOW]
"\u1E00" => "A"
# Ạ [LATIN CAPITAL LETTER A WITH DOT BELOW]
"\u1EA0" => "A"
# Ả [LATIN CAPITAL LETTER A WITH HOOK ABOVE]
"\u1EA2" => "A"
# Ấ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE]
"\u1EA4" => "A"
# Ầ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE]
"\u1EA6" => "A"
# Ẩ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE]
"\u1EA8" => "A"
# Ẫ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE]
"\u1EAA" => "A"
# Ậ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW]
"\u1EAC" => "A"
# Ắ [LATIN CAPITAL LETTER A WITH BREVE AND ACUTE]
"\u1EAE" => "A"
# Ằ [LATIN CAPITAL LETTER A WITH BREVE AND GRAVE]
"\u1EB0" => "A"
# Ẳ [LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE]
"\u1EB2" => "A"
# Ẵ [LATIN CAPITAL LETTER A WITH BREVE AND TILDE]
"\u1EB4" => "A"
# Ặ [LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW]
"\u1EB6" => "A"
# Ⓐ [CIRCLED LATIN CAPITAL LETTER A]
"\u24B6" => "A"
# [FULLWIDTH LATIN CAPITAL LETTER A]
"\uFF21" => "A"
# à [LATIN SMALL LETTER A WITH GRAVE]
"\u00E0" => "a"
# á [LATIN SMALL LETTER A WITH ACUTE]
"\u00E1" => "a"
# â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
"\u00E2" => "a"
# ã [LATIN SMALL LETTER A WITH TILDE]
"\u00E3" => "a"
# ä [LATIN SMALL LETTER A WITH DIAERESIS]
"\u00E4" => "a"
# å [LATIN SMALL LETTER A WITH RING ABOVE]
"\u00E5" => "a"
# ā [LATIN SMALL LETTER A WITH MACRON]
"\u0101" => "a"
# ă [LATIN SMALL LETTER A WITH BREVE]
"\u0103" => "a"
# ą [LATIN SMALL LETTER A WITH OGONEK]
"\u0105" => "a"
# ǎ [LATIN SMALL LETTER A WITH CARON]
"\u01CE" => "a"
# ǟ [LATIN SMALL LETTER A WITH DIAERESIS AND MACRON]
"\u01DF" => "a"
# ǡ [LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON]
"\u01E1" => "a"
# ǻ [LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE]
"\u01FB" => "a"
# ȁ [LATIN SMALL LETTER A WITH DOUBLE GRAVE]
"\u0201" => "a"
# ȃ [LATIN SMALL LETTER A WITH INVERTED BREVE]
"\u0203" => "a"
# ȧ [LATIN SMALL LETTER A WITH DOT ABOVE]
"\u0227" => "a"
# ɐ [LATIN SMALL LETTER TURNED A]
"\u0250" => "a"
# ə [LATIN SMALL LETTER SCHWA]
"\u0259" => "a"
# ɚ [LATIN SMALL LETTER SCHWA WITH HOOK]
"\u025A" => "a"
# ᶏ [LATIN SMALL LETTER A WITH RETROFLEX HOOK]
"\u1D8F" => "a"
# ᶕ [LATIN SMALL LETTER SCHWA WITH RETROFLEX HOOK]
"\u1D95" => "a"
# ạ [LATIN SMALL LETTER A WITH RING BELOW]
"\u1E01" => "a"
# ả [LATIN SMALL LETTER A WITH RIGHT HALF RING]
"\u1E9A" => "a"
# ạ [LATIN SMALL LETTER A WITH DOT BELOW]
"\u1EA1" => "a"
# ả [LATIN SMALL LETTER A WITH HOOK ABOVE]
"\u1EA3" => "a"
# ấ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE]
"\u1EA5" => "a"
# ầ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE]
"\u1EA7" => "a"
# ẩ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE]
"\u1EA9" => "a"
# ẫ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE]
"\u1EAB" => "a"
# ậ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW]
"\u1EAD" => "a"
# ắ [LATIN SMALL LETTER A WITH BREVE AND ACUTE]
"\u1EAF" => "a"
# ằ [LATIN SMALL LETTER A WITH BREVE AND GRAVE]
"\u1EB1" => "a"
# ẳ [LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE]
"\u1EB3" => "a"
# ẵ [LATIN SMALL LETTER A WITH BREVE AND TILDE]
"\u1EB5" => "a"
# ặ [LATIN SMALL LETTER A WITH BREVE AND DOT BELOW]
"\u1EB7" => "a"
# ₐ [LATIN SUBSCRIPT SMALL LETTER A]
"\u2090" => "a"
# ₔ [LATIN SUBSCRIPT SMALL LETTER SCHWA]
"\u2094" => "a"
# ⓐ [CIRCLED LATIN SMALL LETTER A]
"\u24D0" => "a"
# ⱥ [LATIN SMALL LETTER A WITH STROKE]
"\u2C65" => "a"
# Ɐ [LATIN CAPITAL LETTER TURNED A]
"\u2C6F" => "a"
# [FULLWIDTH LATIN SMALL LETTER A]
"\uFF41" => "a"
# Ꜳ [LATIN CAPITAL LETTER AA]
"\uA732" => "AA"
# Æ [LATIN CAPITAL LETTER AE]
"\u00C6" => "AE"
# Ǣ [LATIN CAPITAL LETTER AE WITH MACRON]
"\u01E2" => "AE"
# Ǽ [LATIN CAPITAL LETTER AE WITH ACUTE]
"\u01FC" => "AE"
# ᴁ [LATIN LETTER SMALL CAPITAL AE]
"\u1D01" => "AE"
# Ꜵ [LATIN CAPITAL LETTER AO]
"\uA734" => "AO"
# Ꜷ [LATIN CAPITAL LETTER AU]
"\uA736" => "AU"
# Ꜹ [LATIN CAPITAL LETTER AV]
"\uA738" => "AV"
# Ꜻ [LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR]
"\uA73A" => "AV"
# Ꜽ [LATIN CAPITAL LETTER AY]
"\uA73C" => "AY"
# ⒜ [PARENTHESIZED LATIN SMALL LETTER A]
"\u249C" => "(a)"
# ꜳ [LATIN SMALL LETTER AA]
"\uA733" => "aa"
# æ [LATIN SMALL LETTER AE]
"\u00E6" => "ae"
# ǣ [LATIN SMALL LETTER AE WITH MACRON]
"\u01E3" => "ae"
# ǽ [LATIN SMALL LETTER AE WITH ACUTE]
"\u01FD" => "ae"
# ᴂ [LATIN SMALL LETTER TURNED AE]
"\u1D02" => "ae"
# ꜵ [LATIN SMALL LETTER AO]
"\uA735" => "ao"
# ꜷ [LATIN SMALL LETTER AU]
"\uA737" => "au"
# ꜹ [LATIN SMALL LETTER AV]
"\uA739" => "av"
# ꜻ [LATIN SMALL LETTER AV WITH HORIZONTAL BAR]
"\uA73B" => "av"
# ꜽ [LATIN SMALL LETTER AY]
"\uA73D" => "ay"
# Ɓ [LATIN CAPITAL LETTER B WITH HOOK]
"\u0181" => "B"
# Ƃ [LATIN CAPITAL LETTER B WITH TOPBAR]
"\u0182" => "B"
# Ƀ [LATIN CAPITAL LETTER B WITH STROKE]
"\u0243" => "B"
# ʙ [LATIN LETTER SMALL CAPITAL B]
"\u0299" => "B"
# ᴃ [LATIN LETTER SMALL CAPITAL BARRED B]
"\u1D03" => "B"
# Ḃ [LATIN CAPITAL LETTER B WITH DOT ABOVE]
"\u1E02" => "B"
# Ḅ [LATIN CAPITAL LETTER B WITH DOT BELOW]
"\u1E04" => "B"
# Ḇ [LATIN CAPITAL LETTER B WITH LINE BELOW]
"\u1E06" => "B"
# Ⓑ [CIRCLED LATIN CAPITAL LETTER B]
"\u24B7" => "B"
# [FULLWIDTH LATIN CAPITAL LETTER B]
"\uFF22" => "B"
# ƀ [LATIN SMALL LETTER B WITH STROKE]
"\u0180" => "b"
# ƃ [LATIN SMALL LETTER B WITH TOPBAR]
"\u0183" => "b"
# ɓ [LATIN SMALL LETTER B WITH HOOK]
"\u0253" => "b"
# ᵬ [LATIN SMALL LETTER B WITH MIDDLE TILDE]
"\u1D6C" => "b"
# ᶀ [LATIN SMALL LETTER B WITH PALATAL HOOK]
"\u1D80" => "b"
# ḃ [LATIN SMALL LETTER B WITH DOT ABOVE]
"\u1E03" => "b"
# ḅ [LATIN SMALL LETTER B WITH DOT BELOW]
"\u1E05" => "b"
# ḇ [LATIN SMALL LETTER B WITH LINE BELOW]
"\u1E07" => "b"
# ⓑ [CIRCLED LATIN SMALL LETTER B]
"\u24D1" => "b"
# [FULLWIDTH LATIN SMALL LETTER B]
"\uFF42" => "b"
# ⒝ [PARENTHESIZED LATIN SMALL LETTER B]
"\u249D" => "(b)"
# Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
"\u00C7" => "C"
# Ć [LATIN CAPITAL LETTER C WITH ACUTE]
"\u0106" => "C"
# Ĉ [LATIN CAPITAL LETTER C WITH CIRCUMFLEX]
"\u0108" => "C"
# Ċ [LATIN CAPITAL LETTER C WITH DOT ABOVE]
"\u010A" => "C"
# Č [LATIN CAPITAL LETTER C WITH CARON]
"\u010C" => "C"
# Ƈ [LATIN CAPITAL LETTER C WITH HOOK]
"\u0187" => "C"
# Ȼ [LATIN CAPITAL LETTER C WITH STROKE]
"\u023B" => "C"
# ʗ [LATIN LETTER STRETCHED C]
"\u0297" => "C"
# [LATIN LETTER SMALL CAPITAL C]
"\u1D04" => "C"
# Ḉ [LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE]
"\u1E08" => "C"
# Ⓒ [CIRCLED LATIN CAPITAL LETTER C]
"\u24B8" => "C"
# [FULLWIDTH LATIN CAPITAL LETTER C]
"\uFF23" => "C"
# ç [LATIN SMALL LETTER C WITH CEDILLA]
"\u00E7" => "c"
# ć [LATIN SMALL LETTER C WITH ACUTE]
"\u0107" => "c"
# ĉ [LATIN SMALL LETTER C WITH CIRCUMFLEX]
"\u0109" => "c"
# ċ [LATIN SMALL LETTER C WITH DOT ABOVE]
"\u010B" => "c"
# č [LATIN SMALL LETTER C WITH CARON]
"\u010D" => "c"
# ƈ [LATIN SMALL LETTER C WITH HOOK]
"\u0188" => "c"
# ȼ [LATIN SMALL LETTER C WITH STROKE]
"\u023C" => "c"
# ɕ [LATIN SMALL LETTER C WITH CURL]
"\u0255" => "c"
# ḉ [LATIN SMALL LETTER C WITH CEDILLA AND ACUTE]
"\u1E09" => "c"
# ↄ [LATIN SMALL LETTER REVERSED C]
"\u2184" => "c"
# ⓒ [CIRCLED LATIN SMALL LETTER C]
"\u24D2" => "c"
# Ꜿ [LATIN CAPITAL LETTER REVERSED C WITH DOT]
"\uA73E" => "c"
# ꜿ [LATIN SMALL LETTER REVERSED C WITH DOT]
"\uA73F" => "c"
# [FULLWIDTH LATIN SMALL LETTER C]
"\uFF43" => "c"
# ⒞ [PARENTHESIZED LATIN SMALL LETTER C]
"\u249E" => "(c)"
# Ð [LATIN CAPITAL LETTER ETH]
"\u00D0" => "D"
# Ď [LATIN CAPITAL LETTER D WITH CARON]
"\u010E" => "D"
# Đ [LATIN CAPITAL LETTER D WITH STROKE]
"\u0110" => "D"
# Ɖ [LATIN CAPITAL LETTER AFRICAN D]
"\u0189" => "D"
# Ɗ [LATIN CAPITAL LETTER D WITH HOOK]
"\u018A" => "D"
# Ƌ [LATIN CAPITAL LETTER D WITH TOPBAR]
"\u018B" => "D"
# ᴅ [LATIN LETTER SMALL CAPITAL D]
"\u1D05" => "D"
# ᴆ [LATIN LETTER SMALL CAPITAL ETH]
"\u1D06" => "D"
# Ḋ [LATIN CAPITAL LETTER D WITH DOT ABOVE]
"\u1E0A" => "D"
# Ḍ [LATIN CAPITAL LETTER D WITH DOT BELOW]
"\u1E0C" => "D"
# Ḏ [LATIN CAPITAL LETTER D WITH LINE BELOW]
"\u1E0E" => "D"
# Ḑ [LATIN CAPITAL LETTER D WITH CEDILLA]
"\u1E10" => "D"
# Ḓ [LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW]
"\u1E12" => "D"
# Ⓓ [CIRCLED LATIN CAPITAL LETTER D]
"\u24B9" => "D"
# Ꝺ [LATIN CAPITAL LETTER INSULAR D]
"\uA779" => "D"
# [FULLWIDTH LATIN CAPITAL LETTER D]
"\uFF24" => "D"
# ð [LATIN SMALL LETTER ETH]
"\u00F0" => "d"
# ď [LATIN SMALL LETTER D WITH CARON]
"\u010F" => "d"
# đ [LATIN SMALL LETTER D WITH STROKE]
"\u0111" => "d"
# ƌ [LATIN SMALL LETTER D WITH TOPBAR]
"\u018C" => "d"
# ȡ [LATIN SMALL LETTER D WITH CURL]
"\u0221" => "d"
# ɖ [LATIN SMALL LETTER D WITH TAIL]
"\u0256" => "d"
# ɗ [LATIN SMALL LETTER D WITH HOOK]
"\u0257" => "d"
# ᵭ [LATIN SMALL LETTER D WITH MIDDLE TILDE]
"\u1D6D" => "d"
# ᶁ [LATIN SMALL LETTER D WITH PALATAL HOOK]
"\u1D81" => "d"
# ᶑ [LATIN SMALL LETTER D WITH HOOK AND TAIL]
"\u1D91" => "d"
# ḋ [LATIN SMALL LETTER D WITH DOT ABOVE]
"\u1E0B" => "d"
# ḍ [LATIN SMALL LETTER D WITH DOT BELOW]
"\u1E0D" => "d"
# ḏ [LATIN SMALL LETTER D WITH LINE BELOW]
"\u1E0F" => "d"
# ḑ [LATIN SMALL LETTER D WITH CEDILLA]
"\u1E11" => "d"
# ḓ [LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW]
"\u1E13" => "d"
# ⓓ [CIRCLED LATIN SMALL LETTER D]
"\u24D3" => "d"
# ꝺ [LATIN SMALL LETTER INSULAR D]
"\uA77A" => "d"
# [FULLWIDTH LATIN SMALL LETTER D]
"\uFF44" => "d"
# DŽ [LATIN CAPITAL LETTER DZ WITH CARON]
"\u01C4" => "DZ"
# DZ [LATIN CAPITAL LETTER DZ]
"\u01F1" => "DZ"
# Dž [LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON]
"\u01C5" => "Dz"
# Dz [LATIN CAPITAL LETTER D WITH SMALL LETTER Z]
"\u01F2" => "Dz"
# ⒟ [PARENTHESIZED LATIN SMALL LETTER D]
"\u249F" => "(d)"
# ȸ [LATIN SMALL LETTER DB DIGRAPH]
"\u0238" => "db"
# dž [LATIN SMALL LETTER DZ WITH CARON]
"\u01C6" => "dz"
# dz [LATIN SMALL LETTER DZ]
"\u01F3" => "dz"
# ʣ [LATIN SMALL LETTER DZ DIGRAPH]
"\u02A3" => "dz"
# ʥ [LATIN SMALL LETTER DZ DIGRAPH WITH CURL]
"\u02A5" => "dz"
# È [LATIN CAPITAL LETTER E WITH GRAVE]
"\u00C8" => "E"
# É [LATIN CAPITAL LETTER E WITH ACUTE]
"\u00C9" => "E"
# Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
"\u00CA" => "E"
# Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
"\u00CB" => "E"
# Ē [LATIN CAPITAL LETTER E WITH MACRON]
"\u0112" => "E"
# Ĕ [LATIN CAPITAL LETTER E WITH BREVE]
"\u0114" => "E"
# Ė [LATIN CAPITAL LETTER E WITH DOT ABOVE]
"\u0116" => "E"
# Ę [LATIN CAPITAL LETTER E WITH OGONEK]
"\u0118" => "E"
# Ě [LATIN CAPITAL LETTER E WITH CARON]
"\u011A" => "E"
# Ǝ [LATIN CAPITAL LETTER REVERSED E]
"\u018E" => "E"
# Ɛ [LATIN CAPITAL LETTER OPEN E]
"\u0190" => "E"
# Ȅ [LATIN CAPITAL LETTER E WITH DOUBLE GRAVE]
"\u0204" => "E"
# Ȇ [LATIN CAPITAL LETTER E WITH INVERTED BREVE]
"\u0206" => "E"
# Ȩ [LATIN CAPITAL LETTER E WITH CEDILLA]
"\u0228" => "E"
# Ɇ [LATIN CAPITAL LETTER E WITH STROKE]
"\u0246" => "E"
# ᴇ [LATIN LETTER SMALL CAPITAL E]
"\u1D07" => "E"
# Ḕ [LATIN CAPITAL LETTER E WITH MACRON AND GRAVE]
"\u1E14" => "E"
# Ḗ [LATIN CAPITAL LETTER E WITH MACRON AND ACUTE]
"\u1E16" => "E"
# Ḙ [LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW]
"\u1E18" => "E"
# Ḛ [LATIN CAPITAL LETTER E WITH TILDE BELOW]
"\u1E1A" => "E"
# Ḝ [LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE]
"\u1E1C" => "E"
# Ẹ [LATIN CAPITAL LETTER E WITH DOT BELOW]
"\u1EB8" => "E"
# Ẻ [LATIN CAPITAL LETTER E WITH HOOK ABOVE]
"\u1EBA" => "E"
# Ẽ [LATIN CAPITAL LETTER E WITH TILDE]
"\u1EBC" => "E"
# Ế [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE]
"\u1EBE" => "E"
# Ề [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE]
"\u1EC0" => "E"
# Ể [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE]
"\u1EC2" => "E"
# Ễ [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE]
"\u1EC4" => "E"
# Ệ [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW]
"\u1EC6" => "E"
# Ⓔ [CIRCLED LATIN CAPITAL LETTER E]
"\u24BA" => "E"
# ⱻ [LATIN LETTER SMALL CAPITAL TURNED E]
"\u2C7B" => "E"
# [FULLWIDTH LATIN CAPITAL LETTER E]
"\uFF25" => "E"
# è [LATIN SMALL LETTER E WITH GRAVE]
"\u00E8" => "e"
# é [LATIN SMALL LETTER E WITH ACUTE]
"\u00E9" => "e"
# ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
"\u00EA" => "e"
# ë [LATIN SMALL LETTER E WITH DIAERESIS]
"\u00EB" => "e"
# ē [LATIN SMALL LETTER E WITH MACRON]
"\u0113" => "e"
# ĕ [LATIN SMALL LETTER E WITH BREVE]
"\u0115" => "e"
# ė [LATIN SMALL LETTER E WITH DOT ABOVE]
"\u0117" => "e"
# ę [LATIN SMALL LETTER E WITH OGONEK]
"\u0119" => "e"
# ě [LATIN SMALL LETTER E WITH CARON]
"\u011B" => "e"
# ǝ [LATIN SMALL LETTER TURNED E]
"\u01DD" => "e"
# ȅ [LATIN SMALL LETTER E WITH DOUBLE GRAVE]
"\u0205" => "e"
# ȇ [LATIN SMALL LETTER E WITH INVERTED BREVE]
"\u0207" => "e"
# ȩ [LATIN SMALL LETTER E WITH CEDILLA]
"\u0229" => "e"
# ɇ [LATIN SMALL LETTER E WITH STROKE]
"\u0247" => "e"
# ɘ [LATIN SMALL LETTER REVERSED E]
"\u0258" => "e"
# ɛ [LATIN SMALL LETTER OPEN E]
"\u025B" => "e"
# ɜ [LATIN SMALL LETTER REVERSED OPEN E]
"\u025C" => "e"
# ɝ [LATIN SMALL LETTER REVERSED OPEN E WITH HOOK]
"\u025D" => "e"
# ɞ [LATIN SMALL LETTER CLOSED REVERSED OPEN E]
"\u025E" => "e"
# ʚ [LATIN SMALL LETTER CLOSED OPEN E]
"\u029A" => "e"
# ᴈ [LATIN SMALL LETTER TURNED OPEN E]
"\u1D08" => "e"
# ᶒ [LATIN SMALL LETTER E WITH RETROFLEX HOOK]
"\u1D92" => "e"
# ᶓ [LATIN SMALL LETTER OPEN E WITH RETROFLEX HOOK]
"\u1D93" => "e"
# ᶔ [LATIN SMALL LETTER REVERSED OPEN E WITH RETROFLEX HOOK]
"\u1D94" => "e"
# ḕ [LATIN SMALL LETTER E WITH MACRON AND GRAVE]
"\u1E15" => "e"
# ḗ [LATIN SMALL LETTER E WITH MACRON AND ACUTE]
"\u1E17" => "e"
# ḙ [LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW]
"\u1E19" => "e"
# ḛ [LATIN SMALL LETTER E WITH TILDE BELOW]
"\u1E1B" => "e"
# ḝ [LATIN SMALL LETTER E WITH CEDILLA AND BREVE]
"\u1E1D" => "e"
# ẹ [LATIN SMALL LETTER E WITH DOT BELOW]
"\u1EB9" => "e"
# ẻ [LATIN SMALL LETTER E WITH HOOK ABOVE]
"\u1EBB" => "e"
# ẽ [LATIN SMALL LETTER E WITH TILDE]
"\u1EBD" => "e"
# ế [LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE]
"\u1EBF" => "e"
# ề [LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE]
"\u1EC1" => "e"
# ể [LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE]
"\u1EC3" => "e"
# ễ [LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE]
"\u1EC5" => "e"
# ệ [LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW]
"\u1EC7" => "e"
# ₑ [LATIN SUBSCRIPT SMALL LETTER E]
"\u2091" => "e"
# ⓔ [CIRCLED LATIN SMALL LETTER E]
"\u24D4" => "e"
# ⱸ [LATIN SMALL LETTER E WITH NOTCH]
"\u2C78" => "e"
# [FULLWIDTH LATIN SMALL LETTER E]
"\uFF45" => "e"
# ⒠ [PARENTHESIZED LATIN SMALL LETTER E]
"\u24A0" => "(e)"
# Ƒ [LATIN CAPITAL LETTER F WITH HOOK]
"\u0191" => "F"
# Ḟ [LATIN CAPITAL LETTER F WITH DOT ABOVE]
"\u1E1E" => "F"
# Ⓕ [CIRCLED LATIN CAPITAL LETTER F]
"\u24BB" => "F"
# ꜰ [LATIN LETTER SMALL CAPITAL F]
"\uA730" => "F"
# Ꝼ [LATIN CAPITAL LETTER INSULAR F]
"\uA77B" => "F"
# ꟻ [LATIN EPIGRAPHIC LETTER REVERSED F]
"\uA7FB" => "F"
# [FULLWIDTH LATIN CAPITAL LETTER F]
"\uFF26" => "F"
# ƒ [LATIN SMALL LETTER F WITH HOOK]
"\u0192" => "f"
# ᵮ [LATIN SMALL LETTER F WITH MIDDLE TILDE]
"\u1D6E" => "f"
# ᶂ [LATIN SMALL LETTER F WITH PALATAL HOOK]
"\u1D82" => "f"
# ḟ [LATIN SMALL LETTER F WITH DOT ABOVE]
"\u1E1F" => "f"
# ẛ [LATIN SMALL LETTER LONG S WITH DOT ABOVE]
"\u1E9B" => "f"
# ⓕ [CIRCLED LATIN SMALL LETTER F]
"\u24D5" => "f"
# ꝼ [LATIN SMALL LETTER INSULAR F]
"\uA77C" => "f"
# [FULLWIDTH LATIN SMALL LETTER F]
"\uFF46" => "f"
# ⒡ [PARENTHESIZED LATIN SMALL LETTER F]
"\u24A1" => "(f)"
# ff [LATIN SMALL LIGATURE FF]
"\uFB00" => "ff"
# ffi [LATIN SMALL LIGATURE FFI]
"\uFB03" => "ffi"
# ffl [LATIN SMALL LIGATURE FFL]
"\uFB04" => "ffl"
# fi [LATIN SMALL LIGATURE FI]
"\uFB01" => "fi"
# fl [LATIN SMALL LIGATURE FL]
"\uFB02" => "fl"
# Ĝ [LATIN CAPITAL LETTER G WITH CIRCUMFLEX]
"\u011C" => "G"
# Ğ [LATIN CAPITAL LETTER G WITH BREVE]
"\u011E" => "G"
# Ġ [LATIN CAPITAL LETTER G WITH DOT ABOVE]
"\u0120" => "G"
# Ģ [LATIN CAPITAL LETTER G WITH CEDILLA]
"\u0122" => "G"
# Ɠ [LATIN CAPITAL LETTER G WITH HOOK]
"\u0193" => "G"
# Ǥ [LATIN CAPITAL LETTER G WITH STROKE]
"\u01E4" => "G"
# ǥ [LATIN SMALL LETTER G WITH STROKE]
"\u01E5" => "G"
# Ǧ [LATIN CAPITAL LETTER G WITH CARON]
"\u01E6" => "G"
# ǧ [LATIN SMALL LETTER G WITH CARON]
"\u01E7" => "G"
# Ǵ [LATIN CAPITAL LETTER G WITH ACUTE]
"\u01F4" => "G"
# ɢ [LATIN LETTER SMALL CAPITAL G]
"\u0262" => "G"
# ʛ [LATIN LETTER SMALL CAPITAL G WITH HOOK]
"\u029B" => "G"
# Ḡ [LATIN CAPITAL LETTER G WITH MACRON]
"\u1E20" => "G"
# Ⓖ [CIRCLED LATIN CAPITAL LETTER G]
"\u24BC" => "G"
# Ᵹ [LATIN CAPITAL LETTER INSULAR G]
"\uA77D" => "G"
# Ꝿ [LATIN CAPITAL LETTER TURNED INSULAR G]
"\uA77E" => "G"
# [FULLWIDTH LATIN CAPITAL LETTER G]
"\uFF27" => "G"
# ĝ [LATIN SMALL LETTER G WITH CIRCUMFLEX]
"\u011D" => "g"
# ğ [LATIN SMALL LETTER G WITH BREVE]
"\u011F" => "g"
# ġ [LATIN SMALL LETTER G WITH DOT ABOVE]
"\u0121" => "g"
# ģ [LATIN SMALL LETTER G WITH CEDILLA]
"\u0123" => "g"
# ǵ [LATIN SMALL LETTER G WITH ACUTE]
"\u01F5" => "g"
# ɠ [LATIN SMALL LETTER G WITH HOOK]
"\u0260" => "g"
# ɡ [LATIN SMALL LETTER SCRIPT G]
"\u0261" => "g"
# ᵷ [LATIN SMALL LETTER TURNED G]
"\u1D77" => "g"
# ᵹ [LATIN SMALL LETTER INSULAR G]
"\u1D79" => "g"
# [LATIN SMALL LETTER G WITH PALATAL HOOK]
"\u1D83" => "g"
# ḡ [LATIN SMALL LETTER G WITH MACRON]
"\u1E21" => "g"
# ⓖ [CIRCLED LATIN SMALL LETTER G]
"\u24D6" => "g"
# ꝿ [LATIN SMALL LETTER TURNED INSULAR G]
"\uA77F" => "g"
# [FULLWIDTH LATIN SMALL LETTER G]
"\uFF47" => "g"
# ⒢ [PARENTHESIZED LATIN SMALL LETTER G]
"\u24A2" => "(g)"
# Ĥ [LATIN CAPITAL LETTER H WITH CIRCUMFLEX]
"\u0124" => "H"
# Ħ [LATIN CAPITAL LETTER H WITH STROKE]
"\u0126" => "H"
# Ȟ [LATIN CAPITAL LETTER H WITH CARON]
"\u021E" => "H"
# ʜ [LATIN LETTER SMALL CAPITAL H]
"\u029C" => "H"
# Ḣ [LATIN CAPITAL LETTER H WITH DOT ABOVE]
"\u1E22" => "H"
# Ḥ [LATIN CAPITAL LETTER H WITH DOT BELOW]
"\u1E24" => "H"
# Ḧ [LATIN CAPITAL LETTER H WITH DIAERESIS]
"\u1E26" => "H"
# Ḩ [LATIN CAPITAL LETTER H WITH CEDILLA]
"\u1E28" => "H"
# Ḫ [LATIN CAPITAL LETTER H WITH BREVE BELOW]
"\u1E2A" => "H"
# Ⓗ [CIRCLED LATIN CAPITAL LETTER H]
"\u24BD" => "H"
# Ⱨ [LATIN CAPITAL LETTER H WITH DESCENDER]
"\u2C67" => "H"
# Ⱶ [LATIN CAPITAL LETTER HALF H]
"\u2C75" => "H"
# [FULLWIDTH LATIN CAPITAL LETTER H]
"\uFF28" => "H"
# ĥ [LATIN SMALL LETTER H WITH CIRCUMFLEX]
"\u0125" => "h"
# ħ [LATIN SMALL LETTER H WITH STROKE]
"\u0127" => "h"
# ȟ [LATIN SMALL LETTER H WITH CARON]
"\u021F" => "h"
# ɥ [LATIN SMALL LETTER TURNED H]
"\u0265" => "h"
# ɦ [LATIN SMALL LETTER H WITH HOOK]
"\u0266" => "h"
# ʮ [LATIN SMALL LETTER TURNED H WITH FISHHOOK]
"\u02AE" => "h"
# ʯ [LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL]
"\u02AF" => "h"
# ḣ [LATIN SMALL LETTER H WITH DOT ABOVE]
"\u1E23" => "h"
# ḥ [LATIN SMALL LETTER H WITH DOT BELOW]
"\u1E25" => "h"
# ḧ [LATIN SMALL LETTER H WITH DIAERESIS]
"\u1E27" => "h"
# ḩ [LATIN SMALL LETTER H WITH CEDILLA]
"\u1E29" => "h"
# ḫ [LATIN SMALL LETTER H WITH BREVE BELOW]
"\u1E2B" => "h"
# ẖ [LATIN SMALL LETTER H WITH LINE BELOW]
"\u1E96" => "h"
# ⓗ [CIRCLED LATIN SMALL LETTER H]
"\u24D7" => "h"
# ⱨ [LATIN SMALL LETTER H WITH DESCENDER]
"\u2C68" => "h"
# ⱶ [LATIN SMALL LETTER HALF H]
"\u2C76" => "h"
# [FULLWIDTH LATIN SMALL LETTER H]
"\uFF48" => "h"
# Ƕ http://en.wikipedia.org/wiki/Hwair [LATIN CAPITAL LETTER HWAIR]
"\u01F6" => "HV"
# ⒣ [PARENTHESIZED LATIN SMALL LETTER H]
"\u24A3" => "(h)"
# ƕ [LATIN SMALL LETTER HV]
"\u0195" => "hv"
# Ì [LATIN CAPITAL LETTER I WITH GRAVE]
"\u00CC" => "I"
# Í [LATIN CAPITAL LETTER I WITH ACUTE]
"\u00CD" => "I"
# Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
"\u00CE" => "I"
# Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
"\u00CF" => "I"
# Ĩ [LATIN CAPITAL LETTER I WITH TILDE]
"\u0128" => "I"
# Ī [LATIN CAPITAL LETTER I WITH MACRON]
"\u012A" => "I"
# Ĭ [LATIN CAPITAL LETTER I WITH BREVE]
"\u012C" => "I"
# Į [LATIN CAPITAL LETTER I WITH OGONEK]
"\u012E" => "I"
# İ [LATIN CAPITAL LETTER I WITH DOT ABOVE]
"\u0130" => "I"
# Ɩ [LATIN CAPITAL LETTER IOTA]
"\u0196" => "I"
# Ɨ [LATIN CAPITAL LETTER I WITH STROKE]
"\u0197" => "I"
# Ǐ [LATIN CAPITAL LETTER I WITH CARON]
"\u01CF" => "I"
# Ȉ [LATIN CAPITAL LETTER I WITH DOUBLE GRAVE]
"\u0208" => "I"
# Ȋ [LATIN CAPITAL LETTER I WITH INVERTED BREVE]
"\u020A" => "I"
# ɪ [LATIN LETTER SMALL CAPITAL I]
"\u026A" => "I"
# ᵻ [LATIN SMALL CAPITAL LETTER I WITH STROKE]
"\u1D7B" => "I"
# Ḭ [LATIN CAPITAL LETTER I WITH TILDE BELOW]
"\u1E2C" => "I"
# Ḯ [LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE]
"\u1E2E" => "I"
# Ỉ [LATIN CAPITAL LETTER I WITH HOOK ABOVE]
"\u1EC8" => "I"
# Ị [LATIN CAPITAL LETTER I WITH DOT BELOW]
"\u1ECA" => "I"
# Ⓘ [CIRCLED LATIN CAPITAL LETTER I]
"\u24BE" => "I"
# ꟾ [LATIN EPIGRAPHIC LETTER I LONGA]
"\uA7FE" => "I"
# [FULLWIDTH LATIN CAPITAL LETTER I]
"\uFF29" => "I"
# ì [LATIN SMALL LETTER I WITH GRAVE]
"\u00EC" => "i"
# í [LATIN SMALL LETTER I WITH ACUTE]
"\u00ED" => "i"
# î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
"\u00EE" => "i"
# ï [LATIN SMALL LETTER I WITH DIAERESIS]
"\u00EF" => "i"
# ĩ [LATIN SMALL LETTER I WITH TILDE]
"\u0129" => "i"
# ī [LATIN SMALL LETTER I WITH MACRON]
"\u012B" => "i"
# ĭ [LATIN SMALL LETTER I WITH BREVE]
"\u012D" => "i"
# į [LATIN SMALL LETTER I WITH OGONEK]
"\u012F" => "i"
# ı [LATIN SMALL LETTER DOTLESS I]
"\u0131" => "i"
# ǐ [LATIN SMALL LETTER I WITH CARON]
"\u01D0" => "i"
# ȉ [LATIN SMALL LETTER I WITH DOUBLE GRAVE]
"\u0209" => "i"
# ȋ [LATIN SMALL LETTER I WITH INVERTED BREVE]
"\u020B" => "i"
# ɨ [LATIN SMALL LETTER I WITH STROKE]
"\u0268" => "i"
# ᴉ [LATIN SMALL LETTER TURNED I]
"\u1D09" => "i"
# ᵢ [LATIN SUBSCRIPT SMALL LETTER I]
"\u1D62" => "i"
# ᵼ [LATIN SMALL LETTER IOTA WITH STROKE]
"\u1D7C" => "i"
# ᶖ [LATIN SMALL LETTER I WITH RETROFLEX HOOK]
"\u1D96" => "i"
# ḭ [LATIN SMALL LETTER I WITH TILDE BELOW]
"\u1E2D" => "i"
# ḯ [LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE]
"\u1E2F" => "i"
# ỉ [LATIN SMALL LETTER I WITH HOOK ABOVE]
"\u1EC9" => "i"
# ị [LATIN SMALL LETTER I WITH DOT BELOW]
"\u1ECB" => "i"
# ⁱ [SUPERSCRIPT LATIN SMALL LETTER I]
"\u2071" => "i"
# ⓘ [CIRCLED LATIN SMALL LETTER I]
"\u24D8" => "i"
# [FULLWIDTH LATIN SMALL LETTER I]
"\uFF49" => "i"
# IJ [LATIN CAPITAL LIGATURE IJ]
"\u0132" => "IJ"
# ⒤ [PARENTHESIZED LATIN SMALL LETTER I]
"\u24A4" => "(i)"
# ij [LATIN SMALL LIGATURE IJ]
"\u0133" => "ij"
# Ĵ [LATIN CAPITAL LETTER J WITH CIRCUMFLEX]
"\u0134" => "J"
# Ɉ [LATIN CAPITAL LETTER J WITH STROKE]
"\u0248" => "J"
# ᴊ [LATIN LETTER SMALL CAPITAL J]
"\u1D0A" => "J"
# Ⓙ [CIRCLED LATIN CAPITAL LETTER J]
"\u24BF" => "J"
# [FULLWIDTH LATIN CAPITAL LETTER J]
"\uFF2A" => "J"
# ĵ [LATIN SMALL LETTER J WITH CIRCUMFLEX]
"\u0135" => "j"
# ǰ [LATIN SMALL LETTER J WITH CARON]
"\u01F0" => "j"
# ȷ [LATIN SMALL LETTER DOTLESS J]
"\u0237" => "j"
# ɉ [LATIN SMALL LETTER J WITH STROKE]
"\u0249" => "j"
# ɟ [LATIN SMALL LETTER DOTLESS J WITH STROKE]
"\u025F" => "j"
# ʄ [LATIN SMALL LETTER DOTLESS J WITH STROKE AND HOOK]
"\u0284" => "j"
# ʝ [LATIN SMALL LETTER J WITH CROSSED-TAIL]
"\u029D" => "j"
# ⓙ [CIRCLED LATIN SMALL LETTER J]
"\u24D9" => "j"
# ⱼ [LATIN SUBSCRIPT SMALL LETTER J]
"\u2C7C" => "j"
# [FULLWIDTH LATIN SMALL LETTER J]
"\uFF4A" => "j"
# ⒥ [PARENTHESIZED LATIN SMALL LETTER J]
"\u24A5" => "(j)"
# Ķ [LATIN CAPITAL LETTER K WITH CEDILLA]
"\u0136" => "K"
# Ƙ [LATIN CAPITAL LETTER K WITH HOOK]
"\u0198" => "K"
# Ǩ [LATIN CAPITAL LETTER K WITH CARON]
"\u01E8" => "K"
# ᴋ [LATIN LETTER SMALL CAPITAL K]
"\u1D0B" => "K"
# Ḱ [LATIN CAPITAL LETTER K WITH ACUTE]
"\u1E30" => "K"
# Ḳ [LATIN CAPITAL LETTER K WITH DOT BELOW]
"\u1E32" => "K"
# Ḵ [LATIN CAPITAL LETTER K WITH LINE BELOW]
"\u1E34" => "K"
# Ⓚ [CIRCLED LATIN CAPITAL LETTER K]
"\u24C0" => "K"
# Ⱪ [LATIN CAPITAL LETTER K WITH DESCENDER]
"\u2C69" => "K"
# Ꝁ [LATIN CAPITAL LETTER K WITH STROKE]
"\uA740" => "K"
# Ꝃ [LATIN CAPITAL LETTER K WITH DIAGONAL STROKE]
"\uA742" => "K"
# Ꝅ [LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE]
"\uA744" => "K"
# [FULLWIDTH LATIN CAPITAL LETTER K]
"\uFF2B" => "K"
# ķ [LATIN SMALL LETTER K WITH CEDILLA]
"\u0137" => "k"
# ƙ [LATIN SMALL LETTER K WITH HOOK]
"\u0199" => "k"
# ǩ [LATIN SMALL LETTER K WITH CARON]
"\u01E9" => "k"
# ʞ [LATIN SMALL LETTER TURNED K]
"\u029E" => "k"
# ᶄ [LATIN SMALL LETTER K WITH PALATAL HOOK]
"\u1D84" => "k"
# ḱ [LATIN SMALL LETTER K WITH ACUTE]
"\u1E31" => "k"
# ḳ [LATIN SMALL LETTER K WITH DOT BELOW]
"\u1E33" => "k"
# ḵ [LATIN SMALL LETTER K WITH LINE BELOW]
"\u1E35" => "k"
# ⓚ [CIRCLED LATIN SMALL LETTER K]
"\u24DA" => "k"
# ⱪ [LATIN SMALL LETTER K WITH DESCENDER]
"\u2C6A" => "k"
# ꝁ [LATIN SMALL LETTER K WITH STROKE]
"\uA741" => "k"
# ꝃ [LATIN SMALL LETTER K WITH DIAGONAL STROKE]
"\uA743" => "k"
# ꝅ [LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE]
"\uA745" => "k"
# [FULLWIDTH LATIN SMALL LETTER K]
"\uFF4B" => "k"
# ⒦ [PARENTHESIZED LATIN SMALL LETTER K]
"\u24A6" => "(k)"
# Ĺ [LATIN CAPITAL LETTER L WITH ACUTE]
"\u0139" => "L"
# Ļ [LATIN CAPITAL LETTER L WITH CEDILLA]
"\u013B" => "L"
# Ľ [LATIN CAPITAL LETTER L WITH CARON]
"\u013D" => "L"
# Ŀ [LATIN CAPITAL LETTER L WITH MIDDLE DOT]
"\u013F" => "L"
# Ł [LATIN CAPITAL LETTER L WITH STROKE]
"\u0141" => "L"
# Ƚ [LATIN CAPITAL LETTER L WITH BAR]
"\u023D" => "L"
# ʟ [LATIN LETTER SMALL CAPITAL L]
"\u029F" => "L"
# ᴌ [LATIN LETTER SMALL CAPITAL L WITH STROKE]
"\u1D0C" => "L"
# Ḷ [LATIN CAPITAL LETTER L WITH DOT BELOW]
"\u1E36" => "L"
# Ḹ [LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON]
"\u1E38" => "L"
# Ḻ [LATIN CAPITAL LETTER L WITH LINE BELOW]
"\u1E3A" => "L"
# Ḽ [LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW]
"\u1E3C" => "L"
# Ⓛ [CIRCLED LATIN CAPITAL LETTER L]
"\u24C1" => "L"
# Ⱡ [LATIN CAPITAL LETTER L WITH DOUBLE BAR]
"\u2C60" => "L"
# Ɫ [LATIN CAPITAL LETTER L WITH MIDDLE TILDE]
"\u2C62" => "L"
# Ꝇ [LATIN CAPITAL LETTER BROKEN L]
"\uA746" => "L"
# Ꝉ [LATIN CAPITAL LETTER L WITH HIGH STROKE]
"\uA748" => "L"
# Ꞁ [LATIN CAPITAL LETTER TURNED L]
"\uA780" => "L"
# [FULLWIDTH LATIN CAPITAL LETTER L]
"\uFF2C" => "L"
# ĺ [LATIN SMALL LETTER L WITH ACUTE]
"\u013A" => "l"
# ļ [LATIN SMALL LETTER L WITH CEDILLA]
"\u013C" => "l"
# ľ [LATIN SMALL LETTER L WITH CARON]
"\u013E" => "l"
# ŀ [LATIN SMALL LETTER L WITH MIDDLE DOT]
"\u0140" => "l"
# ł [LATIN SMALL LETTER L WITH STROKE]
"\u0142" => "l"
# ƚ [LATIN SMALL LETTER L WITH BAR]
"\u019A" => "l"
# ȴ [LATIN SMALL LETTER L WITH CURL]
"\u0234" => "l"
# ɫ [LATIN SMALL LETTER L WITH MIDDLE TILDE]
"\u026B" => "l"
# ɬ [LATIN SMALL LETTER L WITH BELT]
"\u026C" => "l"
# ɭ [LATIN SMALL LETTER L WITH RETROFLEX HOOK]
"\u026D" => "l"
# ᶅ [LATIN SMALL LETTER L WITH PALATAL HOOK]
"\u1D85" => "l"
# ḷ [LATIN SMALL LETTER L WITH DOT BELOW]
"\u1E37" => "l"
# ḹ [LATIN SMALL LETTER L WITH DOT BELOW AND MACRON]
"\u1E39" => "l"
# ḻ [LATIN SMALL LETTER L WITH LINE BELOW]
"\u1E3B" => "l"
# ḽ [LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW]
"\u1E3D" => "l"
# ⓛ [CIRCLED LATIN SMALL LETTER L]
"\u24DB" => "l"
# ⱡ [LATIN SMALL LETTER L WITH DOUBLE BAR]
"\u2C61" => "l"
# ꝇ [LATIN SMALL LETTER BROKEN L]
"\uA747" => "l"
# ꝉ [LATIN SMALL LETTER L WITH HIGH STROKE]
"\uA749" => "l"
# ꞁ [LATIN SMALL LETTER TURNED L]
"\uA781" => "l"
# [FULLWIDTH LATIN SMALL LETTER L]
"\uFF4C" => "l"
# LJ [LATIN CAPITAL LETTER LJ]
"\u01C7" => "LJ"
# Ỻ [LATIN CAPITAL LETTER MIDDLE-WELSH LL]
"\u1EFA" => "LL"
# Lj [LATIN CAPITAL LETTER L WITH SMALL LETTER J]
"\u01C8" => "Lj"
# ⒧ [PARENTHESIZED LATIN SMALL LETTER L]
"\u24A7" => "(l)"
# lj [LATIN SMALL LETTER LJ]
"\u01C9" => "lj"
# ỻ [LATIN SMALL LETTER MIDDLE-WELSH LL]
"\u1EFB" => "ll"
# ʪ [LATIN SMALL LETTER LS DIGRAPH]
"\u02AA" => "ls"
# ʫ [LATIN SMALL LETTER LZ DIGRAPH]
"\u02AB" => "lz"
# Ɯ [LATIN CAPITAL LETTER TURNED M]
"\u019C" => "M"
# ᴍ [LATIN LETTER SMALL CAPITAL M]
"\u1D0D" => "M"
# Ḿ [LATIN CAPITAL LETTER M WITH ACUTE]
"\u1E3E" => "M"
# Ṁ [LATIN CAPITAL LETTER M WITH DOT ABOVE]
"\u1E40" => "M"
# Ṃ [LATIN CAPITAL LETTER M WITH DOT BELOW]
"\u1E42" => "M"
# Ⓜ [CIRCLED LATIN CAPITAL LETTER M]
"\u24C2" => "M"
# Ɱ [LATIN CAPITAL LETTER M WITH HOOK]
"\u2C6E" => "M"
# ꟽ [LATIN EPIGRAPHIC LETTER INVERTED M]
"\uA7FD" => "M"
# ꟿ [LATIN EPIGRAPHIC LETTER ARCHAIC M]
"\uA7FF" => "M"
# [FULLWIDTH LATIN CAPITAL LETTER M]
"\uFF2D" => "M"
# ɯ [LATIN SMALL LETTER TURNED M]
"\u026F" => "m"
# ɰ [LATIN SMALL LETTER TURNED M WITH LONG LEG]
"\u0270" => "m"
# ɱ [LATIN SMALL LETTER M WITH HOOK]
"\u0271" => "m"
# ᵯ [LATIN SMALL LETTER M WITH MIDDLE TILDE]
"\u1D6F" => "m"
# ᶆ [LATIN SMALL LETTER M WITH PALATAL HOOK]
"\u1D86" => "m"
# ḿ [LATIN SMALL LETTER M WITH ACUTE]
"\u1E3F" => "m"
# ṁ [LATIN SMALL LETTER M WITH DOT ABOVE]
"\u1E41" => "m"
# ṃ [LATIN SMALL LETTER M WITH DOT BELOW]
"\u1E43" => "m"
# ⓜ [CIRCLED LATIN SMALL LETTER M]
"\u24DC" => "m"
# [FULLWIDTH LATIN SMALL LETTER M]
"\uFF4D" => "m"
# ⒨ [PARENTHESIZED LATIN SMALL LETTER M]
"\u24A8" => "(m)"
# Ñ [LATIN CAPITAL LETTER N WITH TILDE]
"\u00D1" => "N"
# Ń [LATIN CAPITAL LETTER N WITH ACUTE]
"\u0143" => "N"
# Ņ [LATIN CAPITAL LETTER N WITH CEDILLA]
"\u0145" => "N"
# Ň [LATIN CAPITAL LETTER N WITH CARON]
"\u0147" => "N"
# Ŋ http://en.wikipedia.org/wiki/Eng_(letter) [LATIN CAPITAL LETTER ENG]
"\u014A" => "N"
# Ɲ [LATIN CAPITAL LETTER N WITH LEFT HOOK]
"\u019D" => "N"
# Ǹ [LATIN CAPITAL LETTER N WITH GRAVE]
"\u01F8" => "N"
# Ƞ [LATIN CAPITAL LETTER N WITH LONG RIGHT LEG]
"\u0220" => "N"
# ɴ [LATIN LETTER SMALL CAPITAL N]
"\u0274" => "N"
# ᴎ [LATIN LETTER SMALL CAPITAL REVERSED N]
"\u1D0E" => "N"
# Ṅ [LATIN CAPITAL LETTER N WITH DOT ABOVE]
"\u1E44" => "N"
# Ṇ [LATIN CAPITAL LETTER N WITH DOT BELOW]
"\u1E46" => "N"
# Ṉ [LATIN CAPITAL LETTER N WITH LINE BELOW]
"\u1E48" => "N"
# Ṋ [LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW]
"\u1E4A" => "N"
# Ⓝ [CIRCLED LATIN CAPITAL LETTER N]
"\u24C3" => "N"
# [FULLWIDTH LATIN CAPITAL LETTER N]
"\uFF2E" => "N"
# ñ [LATIN SMALL LETTER N WITH TILDE]
"\u00F1" => "n"
# ń [LATIN SMALL LETTER N WITH ACUTE]
"\u0144" => "n"
# ņ [LATIN SMALL LETTER N WITH CEDILLA]
"\u0146" => "n"
# ň [LATIN SMALL LETTER N WITH CARON]
"\u0148" => "n"
# ʼn [LATIN SMALL LETTER N PRECEDED BY APOSTROPHE]
"\u0149" => "n"
# ŋ http://en.wikipedia.org/wiki/Eng_(letter) [LATIN SMALL LETTER ENG]
"\u014B" => "n"
# ƞ [LATIN SMALL LETTER N WITH LONG RIGHT LEG]
"\u019E" => "n"
# ǹ [LATIN SMALL LETTER N WITH GRAVE]
"\u01F9" => "n"
# ȵ [LATIN SMALL LETTER N WITH CURL]
"\u0235" => "n"
# ɲ [LATIN SMALL LETTER N WITH LEFT HOOK]
"\u0272" => "n"
# ɳ [LATIN SMALL LETTER N WITH RETROFLEX HOOK]
"\u0273" => "n"
# ᵰ [LATIN SMALL LETTER N WITH MIDDLE TILDE]
"\u1D70" => "n"
# ᶇ [LATIN SMALL LETTER N WITH PALATAL HOOK]
"\u1D87" => "n"
# ṅ [LATIN SMALL LETTER N WITH DOT ABOVE]
"\u1E45" => "n"
# ṇ [LATIN SMALL LETTER N WITH DOT BELOW]
"\u1E47" => "n"
# ṉ [LATIN SMALL LETTER N WITH LINE BELOW]
"\u1E49" => "n"
# ṋ [LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW]
"\u1E4B" => "n"
# ⁿ [SUPERSCRIPT LATIN SMALL LETTER N]
"\u207F" => "n"
# ⓝ [CIRCLED LATIN SMALL LETTER N]
"\u24DD" => "n"
# [FULLWIDTH LATIN SMALL LETTER N]
"\uFF4E" => "n"
# NJ [LATIN CAPITAL LETTER NJ]
"\u01CA" => "NJ"
# Nj [LATIN CAPITAL LETTER N WITH SMALL LETTER J]
"\u01CB" => "Nj"
# ⒩ [PARENTHESIZED LATIN SMALL LETTER N]
"\u24A9" => "(n)"
# nj [LATIN SMALL LETTER NJ]
"\u01CC" => "nj"
# Ò [LATIN CAPITAL LETTER O WITH GRAVE]
"\u00D2" => "O"
# Ó [LATIN CAPITAL LETTER O WITH ACUTE]
"\u00D3" => "O"
# Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
"\u00D4" => "O"
# Õ [LATIN CAPITAL LETTER O WITH TILDE]
"\u00D5" => "O"
# Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
"\u00D6" => "O"
# Ø [LATIN CAPITAL LETTER O WITH STROKE]
"\u00D8" => "O"
# Ō [LATIN CAPITAL LETTER O WITH MACRON]
"\u014C" => "O"
# Ŏ [LATIN CAPITAL LETTER O WITH BREVE]
"\u014E" => "O"
# Ő [LATIN CAPITAL LETTER O WITH DOUBLE ACUTE]
"\u0150" => "O"
# Ɔ [LATIN CAPITAL LETTER OPEN O]
"\u0186" => "O"
# Ɵ [LATIN CAPITAL LETTER O WITH MIDDLE TILDE]
"\u019F" => "O"
# Ơ [LATIN CAPITAL LETTER O WITH HORN]
"\u01A0" => "O"
# Ǒ [LATIN CAPITAL LETTER O WITH CARON]
"\u01D1" => "O"
# Ǫ [LATIN CAPITAL LETTER O WITH OGONEK]
"\u01EA" => "O"
# Ǭ [LATIN CAPITAL LETTER O WITH OGONEK AND MACRON]
"\u01EC" => "O"
# Ǿ [LATIN CAPITAL LETTER O WITH STROKE AND ACUTE]
"\u01FE" => "O"
# Ȍ [LATIN CAPITAL LETTER O WITH DOUBLE GRAVE]
"\u020C" => "O"
# Ȏ [LATIN CAPITAL LETTER O WITH INVERTED BREVE]
"\u020E" => "O"
# Ȫ [LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON]
"\u022A" => "O"
# Ȭ [LATIN CAPITAL LETTER O WITH TILDE AND MACRON]
"\u022C" => "O"
# Ȯ [LATIN CAPITAL LETTER O WITH DOT ABOVE]
"\u022E" => "O"
# Ȱ [LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON]
"\u0230" => "O"
# [LATIN LETTER SMALL CAPITAL O]
"\u1D0F" => "O"
# ᴐ [LATIN LETTER SMALL CAPITAL OPEN O]
"\u1D10" => "O"
# Ṍ [LATIN CAPITAL LETTER O WITH TILDE AND ACUTE]
"\u1E4C" => "O"
# Ṏ [LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS]
"\u1E4E" => "O"
# Ṑ [LATIN CAPITAL LETTER O WITH MACRON AND GRAVE]
"\u1E50" => "O"
# Ṓ [LATIN CAPITAL LETTER O WITH MACRON AND ACUTE]
"\u1E52" => "O"
# Ọ [LATIN CAPITAL LETTER O WITH DOT BELOW]
"\u1ECC" => "O"
# Ỏ [LATIN CAPITAL LETTER O WITH HOOK ABOVE]
"\u1ECE" => "O"
# Ố [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE]
"\u1ED0" => "O"
# Ồ [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE]
"\u1ED2" => "O"
# Ổ [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE]
"\u1ED4" => "O"
# Ỗ [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE]
"\u1ED6" => "O"
# Ộ [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW]
"\u1ED8" => "O"
# Ớ [LATIN CAPITAL LETTER O WITH HORN AND ACUTE]
"\u1EDA" => "O"
# Ờ [LATIN CAPITAL LETTER O WITH HORN AND GRAVE]
"\u1EDC" => "O"
# Ở [LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE]
"\u1EDE" => "O"
# Ỡ [LATIN CAPITAL LETTER O WITH HORN AND TILDE]
"\u1EE0" => "O"
# Ợ [LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW]
"\u1EE2" => "O"
# Ⓞ [CIRCLED LATIN CAPITAL LETTER O]
"\u24C4" => "O"
# Ꝋ [LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY]
"\uA74A" => "O"
# Ꝍ [LATIN CAPITAL LETTER O WITH LOOP]
"\uA74C" => "O"
# [FULLWIDTH LATIN CAPITAL LETTER O]
"\uFF2F" => "O"
# ò [LATIN SMALL LETTER O WITH GRAVE]
"\u00F2" => "o"
# ó [LATIN SMALL LETTER O WITH ACUTE]
"\u00F3" => "o"
# ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
"\u00F4" => "o"
# õ [LATIN SMALL LETTER O WITH TILDE]
"\u00F5" => "o"
# ö [LATIN SMALL LETTER O WITH DIAERESIS]
"\u00F6" => "o"
# ø [LATIN SMALL LETTER O WITH STROKE]
"\u00F8" => "o"
# ō [LATIN SMALL LETTER O WITH MACRON]
"\u014D" => "o"
# ŏ [LATIN SMALL LETTER O WITH BREVE]
"\u014F" => "o"
# ő [LATIN SMALL LETTER O WITH DOUBLE ACUTE]
"\u0151" => "o"
# ơ [LATIN SMALL LETTER O WITH HORN]
"\u01A1" => "o"
# ǒ [LATIN SMALL LETTER O WITH CARON]
"\u01D2" => "o"
# ǫ [LATIN SMALL LETTER O WITH OGONEK]
"\u01EB" => "o"
# ǭ [LATIN SMALL LETTER O WITH OGONEK AND MACRON]
"\u01ED" => "o"
# ǿ [LATIN SMALL LETTER O WITH STROKE AND ACUTE]
"\u01FF" => "o"
# ȍ [LATIN SMALL LETTER O WITH DOUBLE GRAVE]
"\u020D" => "o"
# ȏ [LATIN SMALL LETTER O WITH INVERTED BREVE]
"\u020F" => "o"
# ȫ [LATIN SMALL LETTER O WITH DIAERESIS AND MACRON]
"\u022B" => "o"
# ȭ [LATIN SMALL LETTER O WITH TILDE AND MACRON]
"\u022D" => "o"
# ȯ [LATIN SMALL LETTER O WITH DOT ABOVE]
"\u022F" => "o"
# ȱ [LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON]
"\u0231" => "o"
# ɔ [LATIN SMALL LETTER OPEN O]
"\u0254" => "o"
# ɵ [LATIN SMALL LETTER BARRED O]
"\u0275" => "o"
# ᴖ [LATIN SMALL LETTER TOP HALF O]
"\u1D16" => "o"
# ᴗ [LATIN SMALL LETTER BOTTOM HALF O]
"\u1D17" => "o"
# ᶗ [LATIN SMALL LETTER OPEN O WITH RETROFLEX HOOK]
"\u1D97" => "o"
# ṍ [LATIN SMALL LETTER O WITH TILDE AND ACUTE]
"\u1E4D" => "o"
# ṏ [LATIN SMALL LETTER O WITH TILDE AND DIAERESIS]
"\u1E4F" => "o"
# ṑ [LATIN SMALL LETTER O WITH MACRON AND GRAVE]
"\u1E51" => "o"
# ṓ [LATIN SMALL LETTER O WITH MACRON AND ACUTE]
"\u1E53" => "o"
# ọ [LATIN SMALL LETTER O WITH DOT BELOW]
"\u1ECD" => "o"
# ỏ [LATIN SMALL LETTER O WITH HOOK ABOVE]
"\u1ECF" => "o"
# ố [LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE]
"\u1ED1" => "o"
# ồ [LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE]
"\u1ED3" => "o"
# ổ [LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE]
"\u1ED5" => "o"
# ỗ [LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE]
"\u1ED7" => "o"
# ộ [LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW]
"\u1ED9" => "o"
# ớ [LATIN SMALL LETTER O WITH HORN AND ACUTE]
"\u1EDB" => "o"
# ờ [LATIN SMALL LETTER O WITH HORN AND GRAVE]
"\u1EDD" => "o"
# ở [LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE]
"\u1EDF" => "o"
# ỡ [LATIN SMALL LETTER O WITH HORN AND TILDE]
"\u1EE1" => "o"
# ợ [LATIN SMALL LETTER O WITH HORN AND DOT BELOW]
"\u1EE3" => "o"
# ₒ [LATIN SUBSCRIPT SMALL LETTER O]
"\u2092" => "o"
# ⓞ [CIRCLED LATIN SMALL LETTER O]
"\u24DE" => "o"
# ⱺ [LATIN SMALL LETTER O WITH LOW RING INSIDE]
"\u2C7A" => "o"
# ꝋ [LATIN SMALL LETTER O WITH LONG STROKE OVERLAY]
"\uA74B" => "o"
# ꝍ [LATIN SMALL LETTER O WITH LOOP]
"\uA74D" => "o"
# [FULLWIDTH LATIN SMALL LETTER O]
"\uFF4F" => "o"
# Π[LATIN CAPITAL LIGATURE OE]
"\u0152" => "OE"
# ɶ [LATIN LETTER SMALL CAPITAL OE]
"\u0276" => "OE"
# Ꝏ [LATIN CAPITAL LETTER OO]
"\uA74E" => "OO"
# Ȣ http://en.wikipedia.org/wiki/OU [LATIN CAPITAL LETTER OU]
"\u0222" => "OU"
# ᴕ [LATIN LETTER SMALL CAPITAL OU]
"\u1D15" => "OU"
# ⒪ [PARENTHESIZED LATIN SMALL LETTER O]
"\u24AA" => "(o)"
# œ [LATIN SMALL LIGATURE OE]
"\u0153" => "oe"
# ᴔ [LATIN SMALL LETTER TURNED OE]
"\u1D14" => "oe"
# ꝏ [LATIN SMALL LETTER OO]
"\uA74F" => "oo"
# ȣ http://en.wikipedia.org/wiki/OU [LATIN SMALL LETTER OU]
"\u0223" => "ou"
# Ƥ [LATIN CAPITAL LETTER P WITH HOOK]
"\u01A4" => "P"
# ᴘ [LATIN LETTER SMALL CAPITAL P]
"\u1D18" => "P"
# Ṕ [LATIN CAPITAL LETTER P WITH ACUTE]
"\u1E54" => "P"
# Ṗ [LATIN CAPITAL LETTER P WITH DOT ABOVE]
"\u1E56" => "P"
# Ⓟ [CIRCLED LATIN CAPITAL LETTER P]
"\u24C5" => "P"
# Ᵽ [LATIN CAPITAL LETTER P WITH STROKE]
"\u2C63" => "P"
# Ꝑ [LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER]
"\uA750" => "P"
# Ꝓ [LATIN CAPITAL LETTER P WITH FLOURISH]
"\uA752" => "P"
# Ꝕ [LATIN CAPITAL LETTER P WITH SQUIRREL TAIL]
"\uA754" => "P"
# [FULLWIDTH LATIN CAPITAL LETTER P]
"\uFF30" => "P"
# ƥ [LATIN SMALL LETTER P WITH HOOK]
"\u01A5" => "p"
# ᵱ [LATIN SMALL LETTER P WITH MIDDLE TILDE]
"\u1D71" => "p"
# ᵽ [LATIN SMALL LETTER P WITH STROKE]
"\u1D7D" => "p"
# ᶈ [LATIN SMALL LETTER P WITH PALATAL HOOK]
"\u1D88" => "p"
# ṕ [LATIN SMALL LETTER P WITH ACUTE]
"\u1E55" => "p"
# ṗ [LATIN SMALL LETTER P WITH DOT ABOVE]
"\u1E57" => "p"
# ⓟ [CIRCLED LATIN SMALL LETTER P]
"\u24DF" => "p"
# ꝑ [LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER]
"\uA751" => "p"
# ꝓ [LATIN SMALL LETTER P WITH FLOURISH]
"\uA753" => "p"
# ꝕ [LATIN SMALL LETTER P WITH SQUIRREL TAIL]
"\uA755" => "p"
# ꟼ [LATIN EPIGRAPHIC LETTER REVERSED P]
"\uA7FC" => "p"
# [FULLWIDTH LATIN SMALL LETTER P]
"\uFF50" => "p"
# ⒫ [PARENTHESIZED LATIN SMALL LETTER P]
"\u24AB" => "(p)"
# Ɋ [LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL]
"\u024A" => "Q"
# Ⓠ [CIRCLED LATIN CAPITAL LETTER Q]
"\u24C6" => "Q"
# Ꝗ [LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER]
"\uA756" => "Q"
# Ꝙ [LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE]
"\uA758" => "Q"
# [FULLWIDTH LATIN CAPITAL LETTER Q]
"\uFF31" => "Q"
# ĸ http://en.wikipedia.org/wiki/Kra_(letter) [LATIN SMALL LETTER KRA]
"\u0138" => "q"
# ɋ [LATIN SMALL LETTER Q WITH HOOK TAIL]
"\u024B" => "q"
# ʠ [LATIN SMALL LETTER Q WITH HOOK]
"\u02A0" => "q"
# ⓠ [CIRCLED LATIN SMALL LETTER Q]
"\u24E0" => "q"
# ꝗ [LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER]
"\uA757" => "q"
# ꝙ [LATIN SMALL LETTER Q WITH DIAGONAL STROKE]
"\uA759" => "q"
# [FULLWIDTH LATIN SMALL LETTER Q]
"\uFF51" => "q"
# ⒬ [PARENTHESIZED LATIN SMALL LETTER Q]
"\u24AC" => "(q)"
# ȹ [LATIN SMALL LETTER QP DIGRAPH]
"\u0239" => "qp"
# Ŕ [LATIN CAPITAL LETTER R WITH ACUTE]
"\u0154" => "R"
# Ŗ [LATIN CAPITAL LETTER R WITH CEDILLA]
"\u0156" => "R"
# Ř [LATIN CAPITAL LETTER R WITH CARON]
"\u0158" => "R"
# Ȓ [LATIN CAPITAL LETTER R WITH DOUBLE GRAVE]
"\u0210" => "R"
# Ȓ [LATIN CAPITAL LETTER R WITH INVERTED BREVE]
"\u0212" => "R"
# Ɍ [LATIN CAPITAL LETTER R WITH STROKE]
"\u024C" => "R"
# ʀ [LATIN LETTER SMALL CAPITAL R]
"\u0280" => "R"
# ʁ [LATIN LETTER SMALL CAPITAL INVERTED R]
"\u0281" => "R"
# ᴙ [LATIN LETTER SMALL CAPITAL REVERSED R]
"\u1D19" => "R"
# ᴚ [LATIN LETTER SMALL CAPITAL TURNED R]
"\u1D1A" => "R"
# Ṙ [LATIN CAPITAL LETTER R WITH DOT ABOVE]
"\u1E58" => "R"
# Ṛ [LATIN CAPITAL LETTER R WITH DOT BELOW]
"\u1E5A" => "R"
# Ṝ [LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON]
"\u1E5C" => "R"
# Ṟ [LATIN CAPITAL LETTER R WITH LINE BELOW]
"\u1E5E" => "R"
# Ⓡ [CIRCLED LATIN CAPITAL LETTER R]
"\u24C7" => "R"
# Ɽ [LATIN CAPITAL LETTER R WITH TAIL]
"\u2C64" => "R"
# [LATIN CAPITAL LETTER R ROTUNDA]
"\uA75A" => "R"
# Ꞃ [LATIN CAPITAL LETTER INSULAR R]
"\uA782" => "R"
# [FULLWIDTH LATIN CAPITAL LETTER R]
"\uFF32" => "R"
# ŕ [LATIN SMALL LETTER R WITH ACUTE]
"\u0155" => "r"
# ŗ [LATIN SMALL LETTER R WITH CEDILLA]
"\u0157" => "r"
# ř [LATIN SMALL LETTER R WITH CARON]
"\u0159" => "r"
# ȑ [LATIN SMALL LETTER R WITH DOUBLE GRAVE]
"\u0211" => "r"
# ȓ [LATIN SMALL LETTER R WITH INVERTED BREVE]
"\u0213" => "r"
# ɍ [LATIN SMALL LETTER R WITH STROKE]
"\u024D" => "r"
# ɼ [LATIN SMALL LETTER R WITH LONG LEG]
"\u027C" => "r"
# ɽ [LATIN SMALL LETTER R WITH TAIL]
"\u027D" => "r"
# ɾ [LATIN SMALL LETTER R WITH FISHHOOK]
"\u027E" => "r"
# ɿ [LATIN SMALL LETTER REVERSED R WITH FISHHOOK]
"\u027F" => "r"
# ᵣ [LATIN SUBSCRIPT SMALL LETTER R]
"\u1D63" => "r"
# ᵲ [LATIN SMALL LETTER R WITH MIDDLE TILDE]
"\u1D72" => "r"
# ᵳ [LATIN SMALL LETTER R WITH FISHHOOK AND MIDDLE TILDE]
"\u1D73" => "r"
# ᶉ [LATIN SMALL LETTER R WITH PALATAL HOOK]
"\u1D89" => "r"
# ṙ [LATIN SMALL LETTER R WITH DOT ABOVE]
"\u1E59" => "r"
# ṛ [LATIN SMALL LETTER R WITH DOT BELOW]
"\u1E5B" => "r"
# ṝ [LATIN SMALL LETTER R WITH DOT BELOW AND MACRON]
"\u1E5D" => "r"
# ṟ [LATIN SMALL LETTER R WITH LINE BELOW]
"\u1E5F" => "r"
# ⓡ [CIRCLED LATIN SMALL LETTER R]
"\u24E1" => "r"
# ꝛ [LATIN SMALL LETTER R ROTUNDA]
"\uA75B" => "r"
# ꞃ [LATIN SMALL LETTER INSULAR R]
"\uA783" => "r"
# [FULLWIDTH LATIN SMALL LETTER R]
"\uFF52" => "r"
# ⒭ [PARENTHESIZED LATIN SMALL LETTER R]
"\u24AD" => "(r)"
# Ś [LATIN CAPITAL LETTER S WITH ACUTE]
"\u015A" => "S"
# Ŝ [LATIN CAPITAL LETTER S WITH CIRCUMFLEX]
"\u015C" => "S"
# Ş [LATIN CAPITAL LETTER S WITH CEDILLA]
"\u015E" => "S"
# Š [LATIN CAPITAL LETTER S WITH CARON]
"\u0160" => "S"
# Ș [LATIN CAPITAL LETTER S WITH COMMA BELOW]
"\u0218" => "S"
# Ṡ [LATIN CAPITAL LETTER S WITH DOT ABOVE]
"\u1E60" => "S"
# Ṣ [LATIN CAPITAL LETTER S WITH DOT BELOW]
"\u1E62" => "S"
# Ṥ [LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE]
"\u1E64" => "S"
# Ṧ [LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE]
"\u1E66" => "S"
# Ṩ [LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE]
"\u1E68" => "S"
# Ⓢ [CIRCLED LATIN CAPITAL LETTER S]
"\u24C8" => "S"
# [LATIN LETTER SMALL CAPITAL S]
"\uA731" => "S"
# ꞅ [LATIN SMALL LETTER INSULAR S]
"\uA785" => "S"
# [FULLWIDTH LATIN CAPITAL LETTER S]
"\uFF33" => "S"
# ś [LATIN SMALL LETTER S WITH ACUTE]
"\u015B" => "s"
# ŝ [LATIN SMALL LETTER S WITH CIRCUMFLEX]
"\u015D" => "s"
# ş [LATIN SMALL LETTER S WITH CEDILLA]
"\u015F" => "s"
# š [LATIN SMALL LETTER S WITH CARON]
"\u0161" => "s"
# ſ http://en.wikipedia.org/wiki/Long_S [LATIN SMALL LETTER LONG S]
"\u017F" => "s"
# ș [LATIN SMALL LETTER S WITH COMMA BELOW]
"\u0219" => "s"
# ȿ [LATIN SMALL LETTER S WITH SWASH TAIL]
"\u023F" => "s"
# ʂ [LATIN SMALL LETTER S WITH HOOK]
"\u0282" => "s"
# ᵴ [LATIN SMALL LETTER S WITH MIDDLE TILDE]
"\u1D74" => "s"
# ᶊ [LATIN SMALL LETTER S WITH PALATAL HOOK]
"\u1D8A" => "s"
# ṡ [LATIN SMALL LETTER S WITH DOT ABOVE]
"\u1E61" => "s"
# ṣ [LATIN SMALL LETTER S WITH DOT BELOW]
"\u1E63" => "s"
# ṥ [LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE]
"\u1E65" => "s"
# ṧ [LATIN SMALL LETTER S WITH CARON AND DOT ABOVE]
"\u1E67" => "s"
# ṩ [LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE]
"\u1E69" => "s"
# ẜ [LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE]
"\u1E9C" => "s"
# [LATIN SMALL LETTER LONG S WITH HIGH STROKE]
"\u1E9D" => "s"
# ⓢ [CIRCLED LATIN SMALL LETTER S]
"\u24E2" => "s"
# Ꞅ [LATIN CAPITAL LETTER INSULAR S]
"\uA784" => "s"
# [FULLWIDTH LATIN SMALL LETTER S]
"\uFF53" => "s"
# ẞ [LATIN CAPITAL LETTER SHARP S]
"\u1E9E" => "SS"
# ⒮ [PARENTHESIZED LATIN SMALL LETTER S]
"\u24AE" => "(s)"
# ß [LATIN SMALL LETTER SHARP S]
"\u00DF" => "ss"
# st [LATIN SMALL LIGATURE ST]
"\uFB06" => "st"
# Ţ [LATIN CAPITAL LETTER T WITH CEDILLA]
"\u0162" => "T"
# Ť [LATIN CAPITAL LETTER T WITH CARON]
"\u0164" => "T"
# Ŧ [LATIN CAPITAL LETTER T WITH STROKE]
"\u0166" => "T"
# Ƭ [LATIN CAPITAL LETTER T WITH HOOK]
"\u01AC" => "T"
# Ʈ [LATIN CAPITAL LETTER T WITH RETROFLEX HOOK]
"\u01AE" => "T"
# Ț [LATIN CAPITAL LETTER T WITH COMMA BELOW]
"\u021A" => "T"
# Ⱦ [LATIN CAPITAL LETTER T WITH DIAGONAL STROKE]
"\u023E" => "T"
# ᴛ [LATIN LETTER SMALL CAPITAL T]
"\u1D1B" => "T"
# Ṫ [LATIN CAPITAL LETTER T WITH DOT ABOVE]
"\u1E6A" => "T"
# Ṭ [LATIN CAPITAL LETTER T WITH DOT BELOW]
"\u1E6C" => "T"
# Ṯ [LATIN CAPITAL LETTER T WITH LINE BELOW]
"\u1E6E" => "T"
# Ṱ [LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW]
"\u1E70" => "T"
# Ⓣ [CIRCLED LATIN CAPITAL LETTER T]
"\u24C9" => "T"
# Ꞇ [LATIN CAPITAL LETTER INSULAR T]
"\uA786" => "T"
# [FULLWIDTH LATIN CAPITAL LETTER T]
"\uFF34" => "T"
# ţ [LATIN SMALL LETTER T WITH CEDILLA]
"\u0163" => "t"
# ť [LATIN SMALL LETTER T WITH CARON]
"\u0165" => "t"
# ŧ [LATIN SMALL LETTER T WITH STROKE]
"\u0167" => "t"
# ƫ [LATIN SMALL LETTER T WITH PALATAL HOOK]
"\u01AB" => "t"
# ƭ [LATIN SMALL LETTER T WITH HOOK]
"\u01AD" => "t"
# ț [LATIN SMALL LETTER T WITH COMMA BELOW]
"\u021B" => "t"
# ȶ [LATIN SMALL LETTER T WITH CURL]
"\u0236" => "t"
# ʇ [LATIN SMALL LETTER TURNED T]
"\u0287" => "t"
# ʈ [LATIN SMALL LETTER T WITH RETROFLEX HOOK]
"\u0288" => "t"
# ᵵ [LATIN SMALL LETTER T WITH MIDDLE TILDE]
"\u1D75" => "t"
# ṫ [LATIN SMALL LETTER T WITH DOT ABOVE]
"\u1E6B" => "t"
# ṭ [LATIN SMALL LETTER T WITH DOT BELOW]
"\u1E6D" => "t"
# ṯ [LATIN SMALL LETTER T WITH LINE BELOW]
"\u1E6F" => "t"
# ṱ [LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW]
"\u1E71" => "t"
# ẗ [LATIN SMALL LETTER T WITH DIAERESIS]
"\u1E97" => "t"
# ⓣ [CIRCLED LATIN SMALL LETTER T]
"\u24E3" => "t"
# ⱦ [LATIN SMALL LETTER T WITH DIAGONAL STROKE]
"\u2C66" => "t"
# [FULLWIDTH LATIN SMALL LETTER T]
"\uFF54" => "t"
# Þ [LATIN CAPITAL LETTER THORN]
"\u00DE" => "TH"
# Ꝧ [LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER]
"\uA766" => "TH"
# Ꜩ [LATIN CAPITAL LETTER TZ]
"\uA728" => "TZ"
# ⒯ [PARENTHESIZED LATIN SMALL LETTER T]
"\u24AF" => "(t)"
# ʨ [LATIN SMALL LETTER TC DIGRAPH WITH CURL]
"\u02A8" => "tc"
# þ [LATIN SMALL LETTER THORN]
"\u00FE" => "th"
# ᵺ [LATIN SMALL LETTER TH WITH STRIKETHROUGH]
"\u1D7A" => "th"
# ꝧ [LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER]
"\uA767" => "th"
# ʦ [LATIN SMALL LETTER TS DIGRAPH]
"\u02A6" => "ts"
# ꜩ [LATIN SMALL LETTER TZ]
"\uA729" => "tz"
# Ù [LATIN CAPITAL LETTER U WITH GRAVE]
"\u00D9" => "U"
# Ú [LATIN CAPITAL LETTER U WITH ACUTE]
"\u00DA" => "U"
# Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
"\u00DB" => "U"
# Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
"\u00DC" => "U"
# Ũ [LATIN CAPITAL LETTER U WITH TILDE]
"\u0168" => "U"
# Ū [LATIN CAPITAL LETTER U WITH MACRON]
"\u016A" => "U"
# Ŭ [LATIN CAPITAL LETTER U WITH BREVE]
"\u016C" => "U"
# Ů [LATIN CAPITAL LETTER U WITH RING ABOVE]
"\u016E" => "U"
# Ű [LATIN CAPITAL LETTER U WITH DOUBLE ACUTE]
"\u0170" => "U"
# Ų [LATIN CAPITAL LETTER U WITH OGONEK]
"\u0172" => "U"
# Ư [LATIN CAPITAL LETTER U WITH HORN]
"\u01AF" => "U"
# Ǔ [LATIN CAPITAL LETTER U WITH CARON]
"\u01D3" => "U"
# Ǖ [LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON]
"\u01D5" => "U"
# Ǘ [LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE]
"\u01D7" => "U"
# Ǚ [LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON]
"\u01D9" => "U"
# Ǜ [LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE]
"\u01DB" => "U"
# Ȕ [LATIN CAPITAL LETTER U WITH DOUBLE GRAVE]
"\u0214" => "U"
# Ȗ [LATIN CAPITAL LETTER U WITH INVERTED BREVE]
"\u0216" => "U"
# Ʉ [LATIN CAPITAL LETTER U BAR]
"\u0244" => "U"
# [LATIN LETTER SMALL CAPITAL U]
"\u1D1C" => "U"
# ᵾ [LATIN SMALL CAPITAL LETTER U WITH STROKE]
"\u1D7E" => "U"
# Ṳ [LATIN CAPITAL LETTER U WITH DIAERESIS BELOW]
"\u1E72" => "U"
# Ṵ [LATIN CAPITAL LETTER U WITH TILDE BELOW]
"\u1E74" => "U"
# Ṷ [LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW]
"\u1E76" => "U"
# Ṹ [LATIN CAPITAL LETTER U WITH TILDE AND ACUTE]
"\u1E78" => "U"
# Ṻ [LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS]
"\u1E7A" => "U"
# Ụ [LATIN CAPITAL LETTER U WITH DOT BELOW]
"\u1EE4" => "U"
# Ủ [LATIN CAPITAL LETTER U WITH HOOK ABOVE]
"\u1EE6" => "U"
# Ứ [LATIN CAPITAL LETTER U WITH HORN AND ACUTE]
"\u1EE8" => "U"
# Ừ [LATIN CAPITAL LETTER U WITH HORN AND GRAVE]
"\u1EEA" => "U"
# Ử [LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE]
"\u1EEC" => "U"
# Ữ [LATIN CAPITAL LETTER U WITH HORN AND TILDE]
"\u1EEE" => "U"
# Ự [LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW]
"\u1EF0" => "U"
# Ⓤ [CIRCLED LATIN CAPITAL LETTER U]
"\u24CA" => "U"
# [FULLWIDTH LATIN CAPITAL LETTER U]
"\uFF35" => "U"
# ù [LATIN SMALL LETTER U WITH GRAVE]
"\u00F9" => "u"
# ú [LATIN SMALL LETTER U WITH ACUTE]
"\u00FA" => "u"
# û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
"\u00FB" => "u"
# ü [LATIN SMALL LETTER U WITH DIAERESIS]
"\u00FC" => "u"
# ũ [LATIN SMALL LETTER U WITH TILDE]
"\u0169" => "u"
# ū [LATIN SMALL LETTER U WITH MACRON]
"\u016B" => "u"
# ŭ [LATIN SMALL LETTER U WITH BREVE]
"\u016D" => "u"
# ů [LATIN SMALL LETTER U WITH RING ABOVE]
"\u016F" => "u"
# ű [LATIN SMALL LETTER U WITH DOUBLE ACUTE]
"\u0171" => "u"
# ų [LATIN SMALL LETTER U WITH OGONEK]
"\u0173" => "u"
# ư [LATIN SMALL LETTER U WITH HORN]
"\u01B0" => "u"
# ǔ [LATIN SMALL LETTER U WITH CARON]
"\u01D4" => "u"
# ǖ [LATIN SMALL LETTER U WITH DIAERESIS AND MACRON]
"\u01D6" => "u"
# ǘ [LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE]
"\u01D8" => "u"
# ǚ [LATIN SMALL LETTER U WITH DIAERESIS AND CARON]
"\u01DA" => "u"
# ǜ [LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE]
"\u01DC" => "u"
# ȕ [LATIN SMALL LETTER U WITH DOUBLE GRAVE]
"\u0215" => "u"
# ȗ [LATIN SMALL LETTER U WITH INVERTED BREVE]
"\u0217" => "u"
# ʉ [LATIN SMALL LETTER U BAR]
"\u0289" => "u"
# ᵤ [LATIN SUBSCRIPT SMALL LETTER U]
"\u1D64" => "u"
# ᶙ [LATIN SMALL LETTER U WITH RETROFLEX HOOK]
"\u1D99" => "u"
# ṳ [LATIN SMALL LETTER U WITH DIAERESIS BELOW]
"\u1E73" => "u"
# ṵ [LATIN SMALL LETTER U WITH TILDE BELOW]
"\u1E75" => "u"
# ṷ [LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW]
"\u1E77" => "u"
# ṹ [LATIN SMALL LETTER U WITH TILDE AND ACUTE]
"\u1E79" => "u"
# ṻ [LATIN SMALL LETTER U WITH MACRON AND DIAERESIS]
"\u1E7B" => "u"
# ụ [LATIN SMALL LETTER U WITH DOT BELOW]
"\u1EE5" => "u"
# ủ [LATIN SMALL LETTER U WITH HOOK ABOVE]
"\u1EE7" => "u"
# ứ [LATIN SMALL LETTER U WITH HORN AND ACUTE]
"\u1EE9" => "u"
# ừ [LATIN SMALL LETTER U WITH HORN AND GRAVE]
"\u1EEB" => "u"
# ử [LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE]
"\u1EED" => "u"
# ữ [LATIN SMALL LETTER U WITH HORN AND TILDE]
"\u1EEF" => "u"
# ự [LATIN SMALL LETTER U WITH HORN AND DOT BELOW]
"\u1EF1" => "u"
# ⓤ [CIRCLED LATIN SMALL LETTER U]
"\u24E4" => "u"
# [FULLWIDTH LATIN SMALL LETTER U]
"\uFF55" => "u"
# ⒰ [PARENTHESIZED LATIN SMALL LETTER U]
"\u24B0" => "(u)"
# ᵫ [LATIN SMALL LETTER UE]
"\u1D6B" => "ue"
# Ʋ [LATIN CAPITAL LETTER V WITH HOOK]
"\u01B2" => "V"
# Ʌ [LATIN CAPITAL LETTER TURNED V]
"\u0245" => "V"
# [LATIN LETTER SMALL CAPITAL V]
"\u1D20" => "V"
# Ṽ [LATIN CAPITAL LETTER V WITH TILDE]
"\u1E7C" => "V"
# Ṿ [LATIN CAPITAL LETTER V WITH DOT BELOW]
"\u1E7E" => "V"
# Ỽ [LATIN CAPITAL LETTER MIDDLE-WELSH V]
"\u1EFC" => "V"
# Ⓥ [CIRCLED LATIN CAPITAL LETTER V]
"\u24CB" => "V"
# Ꝟ [LATIN CAPITAL LETTER V WITH DIAGONAL STROKE]
"\uA75E" => "V"
# Ꝩ [LATIN CAPITAL LETTER VEND]
"\uA768" => "V"
# [FULLWIDTH LATIN CAPITAL LETTER V]
"\uFF36" => "V"
# ʋ [LATIN SMALL LETTER V WITH HOOK]
"\u028B" => "v"
# ʌ [LATIN SMALL LETTER TURNED V]
"\u028C" => "v"
# ᵥ [LATIN SUBSCRIPT SMALL LETTER V]
"\u1D65" => "v"
# [LATIN SMALL LETTER V WITH PALATAL HOOK]
"\u1D8C" => "v"
# ṽ [LATIN SMALL LETTER V WITH TILDE]
"\u1E7D" => "v"
# ṿ [LATIN SMALL LETTER V WITH DOT BELOW]
"\u1E7F" => "v"
# ⓥ [CIRCLED LATIN SMALL LETTER V]
"\u24E5" => "v"
# ⱱ [LATIN SMALL LETTER V WITH RIGHT HOOK]
"\u2C71" => "v"
# ⱴ [LATIN SMALL LETTER V WITH CURL]
"\u2C74" => "v"
# ꝟ [LATIN SMALL LETTER V WITH DIAGONAL STROKE]
"\uA75F" => "v"
# [FULLWIDTH LATIN SMALL LETTER V]
"\uFF56" => "v"
# Ꝡ [LATIN CAPITAL LETTER VY]
"\uA760" => "VY"
# ⒱ [PARENTHESIZED LATIN SMALL LETTER V]
"\u24B1" => "(v)"
# ꝡ [LATIN SMALL LETTER VY]
"\uA761" => "vy"
# Ŵ [LATIN CAPITAL LETTER W WITH CIRCUMFLEX]
"\u0174" => "W"
# Ƿ http://en.wikipedia.org/wiki/Wynn [LATIN CAPITAL LETTER WYNN]
"\u01F7" => "W"
# [LATIN LETTER SMALL CAPITAL W]
"\u1D21" => "W"
# Ẁ [LATIN CAPITAL LETTER W WITH GRAVE]
"\u1E80" => "W"
# Ẃ [LATIN CAPITAL LETTER W WITH ACUTE]
"\u1E82" => "W"
# Ẅ [LATIN CAPITAL LETTER W WITH DIAERESIS]
"\u1E84" => "W"
# Ẇ [LATIN CAPITAL LETTER W WITH DOT ABOVE]
"\u1E86" => "W"
# Ẉ [LATIN CAPITAL LETTER W WITH DOT BELOW]
"\u1E88" => "W"
# Ⓦ [CIRCLED LATIN CAPITAL LETTER W]
"\u24CC" => "W"
# Ⱳ [LATIN CAPITAL LETTER W WITH HOOK]
"\u2C72" => "W"
# [FULLWIDTH LATIN CAPITAL LETTER W]
"\uFF37" => "W"
# ŵ [LATIN SMALL LETTER W WITH CIRCUMFLEX]
"\u0175" => "w"
# ƿ http://en.wikipedia.org/wiki/Wynn [LATIN LETTER WYNN]
"\u01BF" => "w"
# ʍ [LATIN SMALL LETTER TURNED W]
"\u028D" => "w"
# ẁ [LATIN SMALL LETTER W WITH GRAVE]
"\u1E81" => "w"
# ẃ [LATIN SMALL LETTER W WITH ACUTE]
"\u1E83" => "w"
# ẅ [LATIN SMALL LETTER W WITH DIAERESIS]
"\u1E85" => "w"
# ẇ [LATIN SMALL LETTER W WITH DOT ABOVE]
"\u1E87" => "w"
# ẉ [LATIN SMALL LETTER W WITH DOT BELOW]
"\u1E89" => "w"
# ẘ [LATIN SMALL LETTER W WITH RING ABOVE]
"\u1E98" => "w"
# ⓦ [CIRCLED LATIN SMALL LETTER W]
"\u24E6" => "w"
# ⱳ [LATIN SMALL LETTER W WITH HOOK]
"\u2C73" => "w"
# [FULLWIDTH LATIN SMALL LETTER W]
"\uFF57" => "w"
# ⒲ [PARENTHESIZED LATIN SMALL LETTER W]
"\u24B2" => "(w)"
# Ẋ [LATIN CAPITAL LETTER X WITH DOT ABOVE]
"\u1E8A" => "X"
# Ẍ [LATIN CAPITAL LETTER X WITH DIAERESIS]
"\u1E8C" => "X"
# Ⓧ [CIRCLED LATIN CAPITAL LETTER X]
"\u24CD" => "X"
# [FULLWIDTH LATIN CAPITAL LETTER X]
"\uFF38" => "X"
# ᶍ [LATIN SMALL LETTER X WITH PALATAL HOOK]
"\u1D8D" => "x"
# ẋ [LATIN SMALL LETTER X WITH DOT ABOVE]
"\u1E8B" => "x"
# ẍ [LATIN SMALL LETTER X WITH DIAERESIS]
"\u1E8D" => "x"
# ₓ [LATIN SUBSCRIPT SMALL LETTER X]
"\u2093" => "x"
# ⓧ [CIRCLED LATIN SMALL LETTER X]
"\u24E7" => "x"
# [FULLWIDTH LATIN SMALL LETTER X]
"\uFF58" => "x"
# ⒳ [PARENTHESIZED LATIN SMALL LETTER X]
"\u24B3" => "(x)"
# Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
"\u00DD" => "Y"
# Ŷ [LATIN CAPITAL LETTER Y WITH CIRCUMFLEX]
"\u0176" => "Y"
# Ÿ [LATIN CAPITAL LETTER Y WITH DIAERESIS]
"\u0178" => "Y"
# Ƴ [LATIN CAPITAL LETTER Y WITH HOOK]
"\u01B3" => "Y"
# Ȳ [LATIN CAPITAL LETTER Y WITH MACRON]
"\u0232" => "Y"
# Ɏ [LATIN CAPITAL LETTER Y WITH STROKE]
"\u024E" => "Y"
# ʏ [LATIN LETTER SMALL CAPITAL Y]
"\u028F" => "Y"
# Ẏ [LATIN CAPITAL LETTER Y WITH DOT ABOVE]
"\u1E8E" => "Y"
# Ỳ [LATIN CAPITAL LETTER Y WITH GRAVE]
"\u1EF2" => "Y"
# Ỵ [LATIN CAPITAL LETTER Y WITH DOT BELOW]
"\u1EF4" => "Y"
# Ỷ [LATIN CAPITAL LETTER Y WITH HOOK ABOVE]
"\u1EF6" => "Y"
# Ỹ [LATIN CAPITAL LETTER Y WITH TILDE]
"\u1EF8" => "Y"
# Ỿ [LATIN CAPITAL LETTER Y WITH LOOP]
"\u1EFE" => "Y"
# Ⓨ [CIRCLED LATIN CAPITAL LETTER Y]
"\u24CE" => "Y"
# [FULLWIDTH LATIN CAPITAL LETTER Y]
"\uFF39" => "Y"
# ý [LATIN SMALL LETTER Y WITH ACUTE]
"\u00FD" => "y"
# ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
"\u00FF" => "y"
# ŷ [LATIN SMALL LETTER Y WITH CIRCUMFLEX]
"\u0177" => "y"
# ƴ [LATIN SMALL LETTER Y WITH HOOK]
"\u01B4" => "y"
# ȳ [LATIN SMALL LETTER Y WITH MACRON]
"\u0233" => "y"
# ɏ [LATIN SMALL LETTER Y WITH STROKE]
"\u024F" => "y"
# ʎ [LATIN SMALL LETTER TURNED Y]
"\u028E" => "y"
# ẏ [LATIN SMALL LETTER Y WITH DOT ABOVE]
"\u1E8F" => "y"
# ẙ [LATIN SMALL LETTER Y WITH RING ABOVE]
"\u1E99" => "y"
# ỳ [LATIN SMALL LETTER Y WITH GRAVE]
"\u1EF3" => "y"
# ỵ [LATIN SMALL LETTER Y WITH DOT BELOW]
"\u1EF5" => "y"
# ỷ [LATIN SMALL LETTER Y WITH HOOK ABOVE]
"\u1EF7" => "y"
# ỹ [LATIN SMALL LETTER Y WITH TILDE]
"\u1EF9" => "y"
# ỿ [LATIN SMALL LETTER Y WITH LOOP]
"\u1EFF" => "y"
# ⓨ [CIRCLED LATIN SMALL LETTER Y]
"\u24E8" => "y"
# [FULLWIDTH LATIN SMALL LETTER Y]
"\uFF59" => "y"
# ⒴ [PARENTHESIZED LATIN SMALL LETTER Y]
"\u24B4" => "(y)"
# Ź [LATIN CAPITAL LETTER Z WITH ACUTE]
"\u0179" => "Z"
# Ż [LATIN CAPITAL LETTER Z WITH DOT ABOVE]
"\u017B" => "Z"
# Ž [LATIN CAPITAL LETTER Z WITH CARON]
"\u017D" => "Z"
# Ƶ [LATIN CAPITAL LETTER Z WITH STROKE]
"\u01B5" => "Z"
# Ȝ http://en.wikipedia.org/wiki/Yogh [LATIN CAPITAL LETTER YOGH]
"\u021C" => "Z"
# Ȥ [LATIN CAPITAL LETTER Z WITH HOOK]
"\u0224" => "Z"
# [LATIN LETTER SMALL CAPITAL Z]
"\u1D22" => "Z"
# Ẑ [LATIN CAPITAL LETTER Z WITH CIRCUMFLEX]
"\u1E90" => "Z"
# Ẓ [LATIN CAPITAL LETTER Z WITH DOT BELOW]
"\u1E92" => "Z"
# Ẕ [LATIN CAPITAL LETTER Z WITH LINE BELOW]
"\u1E94" => "Z"
# Ⓩ [CIRCLED LATIN CAPITAL LETTER Z]
"\u24CF" => "Z"
# Ⱬ [LATIN CAPITAL LETTER Z WITH DESCENDER]
"\u2C6B" => "Z"
# Ꝣ [LATIN CAPITAL LETTER VISIGOTHIC Z]
"\uA762" => "Z"
# [FULLWIDTH LATIN CAPITAL LETTER Z]
"\uFF3A" => "Z"
# ź [LATIN SMALL LETTER Z WITH ACUTE]
"\u017A" => "z"
# ż [LATIN SMALL LETTER Z WITH DOT ABOVE]
"\u017C" => "z"
# ž [LATIN SMALL LETTER Z WITH CARON]
"\u017E" => "z"
# ƶ [LATIN SMALL LETTER Z WITH STROKE]
"\u01B6" => "z"
# ȝ http://en.wikipedia.org/wiki/Yogh [LATIN SMALL LETTER YOGH]
"\u021D" => "z"
# ȥ [LATIN SMALL LETTER Z WITH HOOK]
"\u0225" => "z"
# ɀ [LATIN SMALL LETTER Z WITH SWASH TAIL]
"\u0240" => "z"
# ʐ [LATIN SMALL LETTER Z WITH RETROFLEX HOOK]
"\u0290" => "z"
# ʑ [LATIN SMALL LETTER Z WITH CURL]
"\u0291" => "z"
# ᵶ [LATIN SMALL LETTER Z WITH MIDDLE TILDE]
"\u1D76" => "z"
# ᶎ [LATIN SMALL LETTER Z WITH PALATAL HOOK]
"\u1D8E" => "z"
# ẑ [LATIN SMALL LETTER Z WITH CIRCUMFLEX]
"\u1E91" => "z"
# ẓ [LATIN SMALL LETTER Z WITH DOT BELOW]
"\u1E93" => "z"
# ẕ [LATIN SMALL LETTER Z WITH LINE BELOW]
"\u1E95" => "z"
# ⓩ [CIRCLED LATIN SMALL LETTER Z]
"\u24E9" => "z"
# ⱬ [LATIN SMALL LETTER Z WITH DESCENDER]
"\u2C6C" => "z"
# ꝣ [LATIN SMALL LETTER VISIGOTHIC Z]
"\uA763" => "z"
# [FULLWIDTH LATIN SMALL LETTER Z]
"\uFF5A" => "z"
# ⒵ [PARENTHESIZED LATIN SMALL LETTER Z]
"\u24B5" => "(z)"
# ⁰ [SUPERSCRIPT ZERO]
"\u2070" => "0"
# ₀ [SUBSCRIPT ZERO]
"\u2080" => "0"
# ⓪ [CIRCLED DIGIT ZERO]
"\u24EA" => "0"
# ⓿ [NEGATIVE CIRCLED DIGIT ZERO]
"\u24FF" => "0"
# [FULLWIDTH DIGIT ZERO]
"\uFF10" => "0"
# ¹ [SUPERSCRIPT ONE]
"\u00B9" => "1"
# ₁ [SUBSCRIPT ONE]
"\u2081" => "1"
# ① [CIRCLED DIGIT ONE]
"\u2460" => "1"
# ⓵ [DOUBLE CIRCLED DIGIT ONE]
"\u24F5" => "1"
# ❶ [DINGBAT NEGATIVE CIRCLED DIGIT ONE]
"\u2776" => "1"
# ➀ [DINGBAT CIRCLED SANS-SERIF DIGIT ONE]
"\u2780" => "1"
# ➊ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ONE]
"\u278A" => "1"
# [FULLWIDTH DIGIT ONE]
"\uFF11" => "1"
# ⒈ [DIGIT ONE FULL STOP]
"\u2488" => "1."
# ⑴ [PARENTHESIZED DIGIT ONE]
"\u2474" => "(1)"
# ² [SUPERSCRIPT TWO]
"\u00B2" => "2"
# ₂ [SUBSCRIPT TWO]
"\u2082" => "2"
# ② [CIRCLED DIGIT TWO]
"\u2461" => "2"
# ⓶ [DOUBLE CIRCLED DIGIT TWO]
"\u24F6" => "2"
# ❷ [DINGBAT NEGATIVE CIRCLED DIGIT TWO]
"\u2777" => "2"
# ➁ [DINGBAT CIRCLED SANS-SERIF DIGIT TWO]
"\u2781" => "2"
# ➋ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT TWO]
"\u278B" => "2"
# [FULLWIDTH DIGIT TWO]
"\uFF12" => "2"
# ⒉ [DIGIT TWO FULL STOP]
"\u2489" => "2."
# ⑵ [PARENTHESIZED DIGIT TWO]
"\u2475" => "(2)"
# ³ [SUPERSCRIPT THREE]
"\u00B3" => "3"
# ₃ [SUBSCRIPT THREE]
"\u2083" => "3"
# ③ [CIRCLED DIGIT THREE]
"\u2462" => "3"
# ⓷ [DOUBLE CIRCLED DIGIT THREE]
"\u24F7" => "3"
# ❸ [DINGBAT NEGATIVE CIRCLED DIGIT THREE]
"\u2778" => "3"
# ➂ [DINGBAT CIRCLED SANS-SERIF DIGIT THREE]
"\u2782" => "3"
# ➌ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT THREE]
"\u278C" => "3"
# [FULLWIDTH DIGIT THREE]
"\uFF13" => "3"
# ⒊ [DIGIT THREE FULL STOP]
"\u248A" => "3."
# ⑶ [PARENTHESIZED DIGIT THREE]
"\u2476" => "(3)"
# ⁴ [SUPERSCRIPT FOUR]
"\u2074" => "4"
# ₄ [SUBSCRIPT FOUR]
"\u2084" => "4"
# ④ [CIRCLED DIGIT FOUR]
"\u2463" => "4"
# ⓸ [DOUBLE CIRCLED DIGIT FOUR]
"\u24F8" => "4"
# ❹ [DINGBAT NEGATIVE CIRCLED DIGIT FOUR]
"\u2779" => "4"
# ➃ [DINGBAT CIRCLED SANS-SERIF DIGIT FOUR]
"\u2783" => "4"
# ➍ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FOUR]
"\u278D" => "4"
# [FULLWIDTH DIGIT FOUR]
"\uFF14" => "4"
# ⒋ [DIGIT FOUR FULL STOP]
"\u248B" => "4."
# ⑷ [PARENTHESIZED DIGIT FOUR]
"\u2477" => "(4)"
# ⁵ [SUPERSCRIPT FIVE]
"\u2075" => "5"
# ₅ [SUBSCRIPT FIVE]
"\u2085" => "5"
# ⑤ [CIRCLED DIGIT FIVE]
"\u2464" => "5"
# ⓹ [DOUBLE CIRCLED DIGIT FIVE]
"\u24F9" => "5"
# ❺ [DINGBAT NEGATIVE CIRCLED DIGIT FIVE]
"\u277A" => "5"
# ➄ [DINGBAT CIRCLED SANS-SERIF DIGIT FIVE]
"\u2784" => "5"
# ➎ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FIVE]
"\u278E" => "5"
# [FULLWIDTH DIGIT FIVE]
"\uFF15" => "5"
# ⒌ [DIGIT FIVE FULL STOP]
"\u248C" => "5."
# ⑸ [PARENTHESIZED DIGIT FIVE]
"\u2478" => "(5)"
# ⁶ [SUPERSCRIPT SIX]
"\u2076" => "6"
# ₆ [SUBSCRIPT SIX]
"\u2086" => "6"
# ⑥ [CIRCLED DIGIT SIX]
"\u2465" => "6"
# ⓺ [DOUBLE CIRCLED DIGIT SIX]
"\u24FA" => "6"
# ❻ [DINGBAT NEGATIVE CIRCLED DIGIT SIX]
"\u277B" => "6"
# ➅ [DINGBAT CIRCLED SANS-SERIF DIGIT SIX]
"\u2785" => "6"
# ➏ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SIX]
"\u278F" => "6"
# [FULLWIDTH DIGIT SIX]
"\uFF16" => "6"
# ⒍ [DIGIT SIX FULL STOP]
"\u248D" => "6."
# ⑹ [PARENTHESIZED DIGIT SIX]
"\u2479" => "(6)"
# ⁷ [SUPERSCRIPT SEVEN]
"\u2077" => "7"
# ₇ [SUBSCRIPT SEVEN]
"\u2087" => "7"
# ⑦ [CIRCLED DIGIT SEVEN]
"\u2466" => "7"
# ⓻ [DOUBLE CIRCLED DIGIT SEVEN]
"\u24FB" => "7"
# ❼ [DINGBAT NEGATIVE CIRCLED DIGIT SEVEN]
"\u277C" => "7"
# ➆ [DINGBAT CIRCLED SANS-SERIF DIGIT SEVEN]
"\u2786" => "7"
# ➐ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN]
"\u2790" => "7"
# [FULLWIDTH DIGIT SEVEN]
"\uFF17" => "7"
# ⒎ [DIGIT SEVEN FULL STOP]
"\u248E" => "7."
# ⑺ [PARENTHESIZED DIGIT SEVEN]
"\u247A" => "(7)"
# ⁸ [SUPERSCRIPT EIGHT]
"\u2078" => "8"
# ₈ [SUBSCRIPT EIGHT]
"\u2088" => "8"
# ⑧ [CIRCLED DIGIT EIGHT]
"\u2467" => "8"
# ⓼ [DOUBLE CIRCLED DIGIT EIGHT]
"\u24FC" => "8"
# ❽ [DINGBAT NEGATIVE CIRCLED DIGIT EIGHT]
"\u277D" => "8"
# ➇ [DINGBAT CIRCLED SANS-SERIF DIGIT EIGHT]
"\u2787" => "8"
# ➑ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT EIGHT]
"\u2791" => "8"
# [FULLWIDTH DIGIT EIGHT]
"\uFF18" => "8"
# ⒏ [DIGIT EIGHT FULL STOP]
"\u248F" => "8."
# ⑻ [PARENTHESIZED DIGIT EIGHT]
"\u247B" => "(8)"
# ⁹ [SUPERSCRIPT NINE]
"\u2079" => "9"
# ₉ [SUBSCRIPT NINE]
"\u2089" => "9"
# ⑨ [CIRCLED DIGIT NINE]
"\u2468" => "9"
# ⓽ [DOUBLE CIRCLED DIGIT NINE]
"\u24FD" => "9"
# ❾ [DINGBAT NEGATIVE CIRCLED DIGIT NINE]
"\u277E" => "9"
# ➈ [DINGBAT CIRCLED SANS-SERIF DIGIT NINE]
"\u2788" => "9"
# ➒ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT NINE]
"\u2792" => "9"
# [FULLWIDTH DIGIT NINE]
"\uFF19" => "9"
# ⒐ [DIGIT NINE FULL STOP]
"\u2490" => "9."
# ⑼ [PARENTHESIZED DIGIT NINE]
"\u247C" => "(9)"
# ⑩ [CIRCLED NUMBER TEN]
"\u2469" => "10"
# ⓾ [DOUBLE CIRCLED NUMBER TEN]
"\u24FE" => "10"
# ❿ [DINGBAT NEGATIVE CIRCLED NUMBER TEN]
"\u277F" => "10"
# ➉ [DINGBAT CIRCLED SANS-SERIF NUMBER TEN]
"\u2789" => "10"
# ➓ [DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN]
"\u2793" => "10"
# ⒑ [NUMBER TEN FULL STOP]
"\u2491" => "10."
# ⑽ [PARENTHESIZED NUMBER TEN]
"\u247D" => "(10)"
# ⑪ [CIRCLED NUMBER ELEVEN]
"\u246A" => "11"
# ⓫ [NEGATIVE CIRCLED NUMBER ELEVEN]
"\u24EB" => "11"
# ⒒ [NUMBER ELEVEN FULL STOP]
"\u2492" => "11."
# ⑾ [PARENTHESIZED NUMBER ELEVEN]
"\u247E" => "(11)"
# ⑫ [CIRCLED NUMBER TWELVE]
"\u246B" => "12"
# ⓬ [NEGATIVE CIRCLED NUMBER TWELVE]
"\u24EC" => "12"
# ⒓ [NUMBER TWELVE FULL STOP]
"\u2493" => "12."
# ⑿ [PARENTHESIZED NUMBER TWELVE]
"\u247F" => "(12)"
# ⑬ [CIRCLED NUMBER THIRTEEN]
"\u246C" => "13"
# ⓭ [NEGATIVE CIRCLED NUMBER THIRTEEN]
"\u24ED" => "13"
# ⒔ [NUMBER THIRTEEN FULL STOP]
"\u2494" => "13."
# ⒀ [PARENTHESIZED NUMBER THIRTEEN]
"\u2480" => "(13)"
# ⑭ [CIRCLED NUMBER FOURTEEN]
"\u246D" => "14"
# ⓮ [NEGATIVE CIRCLED NUMBER FOURTEEN]
"\u24EE" => "14"
# ⒕ [NUMBER FOURTEEN FULL STOP]
"\u2495" => "14."
# ⒁ [PARENTHESIZED NUMBER FOURTEEN]
"\u2481" => "(14)"
# ⑮ [CIRCLED NUMBER FIFTEEN]
"\u246E" => "15"
# ⓯ [NEGATIVE CIRCLED NUMBER FIFTEEN]
"\u24EF" => "15"
# ⒖ [NUMBER FIFTEEN FULL STOP]
"\u2496" => "15."
# ⒂ [PARENTHESIZED NUMBER FIFTEEN]
"\u2482" => "(15)"
# ⑯ [CIRCLED NUMBER SIXTEEN]
"\u246F" => "16"
# ⓰ [NEGATIVE CIRCLED NUMBER SIXTEEN]
"\u24F0" => "16"
# ⒗ [NUMBER SIXTEEN FULL STOP]
"\u2497" => "16."
# ⒃ [PARENTHESIZED NUMBER SIXTEEN]
"\u2483" => "(16)"
# ⑰ [CIRCLED NUMBER SEVENTEEN]
"\u2470" => "17"
# ⓱ [NEGATIVE CIRCLED NUMBER SEVENTEEN]
"\u24F1" => "17"
# ⒘ [NUMBER SEVENTEEN FULL STOP]
"\u2498" => "17."
# ⒄ [PARENTHESIZED NUMBER SEVENTEEN]
"\u2484" => "(17)"
# ⑱ [CIRCLED NUMBER EIGHTEEN]
"\u2471" => "18"
# ⓲ [NEGATIVE CIRCLED NUMBER EIGHTEEN]
"\u24F2" => "18"
# ⒙ [NUMBER EIGHTEEN FULL STOP]
"\u2499" => "18."
# ⒅ [PARENTHESIZED NUMBER EIGHTEEN]
"\u2485" => "(18)"
# ⑲ [CIRCLED NUMBER NINETEEN]
"\u2472" => "19"
# ⓳ [NEGATIVE CIRCLED NUMBER NINETEEN]
"\u24F3" => "19"
# ⒚ [NUMBER NINETEEN FULL STOP]
"\u249A" => "19."
# ⒆ [PARENTHESIZED NUMBER NINETEEN]
"\u2486" => "(19)"
# ⑳ [CIRCLED NUMBER TWENTY]
"\u2473" => "20"
# ⓴ [NEGATIVE CIRCLED NUMBER TWENTY]
"\u24F4" => "20"
# ⒛ [NUMBER TWENTY FULL STOP]
"\u249B" => "20."
# ⒇ [PARENTHESIZED NUMBER TWENTY]
"\u2487" => "(20)"
# « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
"\u00AB" => "\""
# » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
"\u00BB" => "\""
# “ [LEFT DOUBLE QUOTATION MARK]
"\u201C" => "\""
# ” [RIGHT DOUBLE QUOTATION MARK]
"\u201D" => "\""
# „ [DOUBLE LOW-9 QUOTATION MARK]
"\u201E" => "\""
# ″ [DOUBLE PRIME]
"\u2033" => "\""
# ‶ [REVERSED DOUBLE PRIME]
"\u2036" => "\""
# ❝ [HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT]
"\u275D" => "\""
# ❞ [HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT]
"\u275E" => "\""
# [HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT]
"\u276E" => "\""
# [HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT]
"\u276F" => "\""
# [FULLWIDTH QUOTATION MARK]
"\uFF02" => "\""
# [LEFT SINGLE QUOTATION MARK]
"\u2018" => "\'"
# [RIGHT SINGLE QUOTATION MARK]
"\u2019" => "\'"
# [SINGLE LOW-9 QUOTATION MARK]
"\u201A" => "\'"
# [SINGLE HIGH-REVERSED-9 QUOTATION MARK]
"\u201B" => "\'"
# [PRIME]
"\u2032" => "\'"
# [REVERSED PRIME]
"\u2035" => "\'"
# [SINGLE LEFT-POINTING ANGLE QUOTATION MARK]
"\u2039" => "\'"
# [SINGLE RIGHT-POINTING ANGLE QUOTATION MARK]
"\u203A" => "\'"
# ❛ [HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT]
"\u275B" => "\'"
# ❜ [HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT]
"\u275C" => "\'"
# [FULLWIDTH APOSTROPHE]
"\uFF07" => "\'"
# [HYPHEN]
"\u2010" => "-"
# [NON-BREAKING HYPHEN]
"\u2011" => "-"
# [FIGURE DASH]
"\u2012" => "-"
# [EN DASH]
"\u2013" => "-"
# — [EM DASH]
"\u2014" => "-"
# ⁻ [SUPERSCRIPT MINUS]
"\u207B" => "-"
# ₋ [SUBSCRIPT MINUS]
"\u208B" => "-"
# [FULLWIDTH HYPHEN-MINUS]
"\uFF0D" => "-"
# ⁅ [LEFT SQUARE BRACKET WITH QUILL]
"\u2045" => "["
# [LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT]
"\u2772" => "["
# [FULLWIDTH LEFT SQUARE BRACKET]
"\uFF3B" => "["
# ⁆ [RIGHT SQUARE BRACKET WITH QUILL]
"\u2046" => "]"
# [LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT]
"\u2773" => "]"
# [FULLWIDTH RIGHT SQUARE BRACKET]
"\uFF3D" => "]"
# ⁽ [SUPERSCRIPT LEFT PARENTHESIS]
"\u207D" => "("
# ₍ [SUBSCRIPT LEFT PARENTHESIS]
"\u208D" => "("
# [MEDIUM LEFT PARENTHESIS ORNAMENT]
"\u2768" => "("
# ❪ [MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT]
"\u276A" => "("
# [FULLWIDTH LEFT PARENTHESIS]
"\uFF08" => "("
# ⸨ [LEFT DOUBLE PARENTHESIS]
"\u2E28" => "(("
# ⁾ [SUPERSCRIPT RIGHT PARENTHESIS]
"\u207E" => ")"
# ₎ [SUBSCRIPT RIGHT PARENTHESIS]
"\u208E" => ")"
# [MEDIUM RIGHT PARENTHESIS ORNAMENT]
"\u2769" => ")"
# ❫ [MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT]
"\u276B" => ")"
# [FULLWIDTH RIGHT PARENTHESIS]
"\uFF09" => ")"
# ⸩ [RIGHT DOUBLE PARENTHESIS]
"\u2E29" => "))"
# ❬ [MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT]
"\u276C" => "<"
# ❰ [HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT]
"\u2770" => "<"
# [FULLWIDTH LESS-THAN SIGN]
"\uFF1C" => "<"
# ❭ [MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT]
"\u276D" => ">"
# ❱ [HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT]
"\u2771" => ">"
# [FULLWIDTH GREATER-THAN SIGN]
"\uFF1E" => ">"
# [MEDIUM LEFT CURLY BRACKET ORNAMENT]
"\u2774" => "{"
# [FULLWIDTH LEFT CURLY BRACKET]
"\uFF5B" => "{"
# [MEDIUM RIGHT CURLY BRACKET ORNAMENT]
"\u2775" => "}"
# [FULLWIDTH RIGHT CURLY BRACKET]
"\uFF5D" => "}"
# ⁺ [SUPERSCRIPT PLUS SIGN]
"\u207A" => "+"
# ₊ [SUBSCRIPT PLUS SIGN]
"\u208A" => "+"
# [FULLWIDTH PLUS SIGN]
"\uFF0B" => "+"
# ⁼ [SUPERSCRIPT EQUALS SIGN]
"\u207C" => "="
# ₌ [SUBSCRIPT EQUALS SIGN]
"\u208C" => "="
# [FULLWIDTH EQUALS SIGN]
"\uFF1D" => "="
# [FULLWIDTH EXCLAMATION MARK]
"\uFF01" => "!"
# ‼ [DOUBLE EXCLAMATION MARK]
"\u203C" => "!!"
# ⁉ [EXCLAMATION QUESTION MARK]
"\u2049" => "!?"
# [FULLWIDTH NUMBER SIGN]
"\uFF03" => "#"
# [FULLWIDTH DOLLAR SIGN]
"\uFF04" => "$"
# ⁒ [COMMERCIAL MINUS SIGN]
"\u2052" => "%"
# [FULLWIDTH PERCENT SIGN]
"\uFF05" => "%"
# [FULLWIDTH AMPERSAND]
"\uFF06" => "&"
# [LOW ASTERISK]
"\u204E" => "*"
# [FULLWIDTH ASTERISK]
"\uFF0A" => "*"
# [FULLWIDTH COMMA]
"\uFF0C" => ","
# [FULLWIDTH FULL STOP]
"\uFF0E" => "."
# [FRACTION SLASH]
"\u2044" => "/"
# [FULLWIDTH SOLIDUS]
"\uFF0F" => "/"
# [FULLWIDTH COLON]
"\uFF1A" => ":"
# ⁏ [REVERSED SEMICOLON]
"\u204F" => ";"
# [FULLWIDTH SEMICOLON]
"\uFF1B" => ";"
# [FULLWIDTH QUESTION MARK]
"\uFF1F" => "?"
# ⁇ [DOUBLE QUESTION MARK]
"\u2047" => "??"
# ⁈ [QUESTION EXCLAMATION MARK]
"\u2048" => "?!"
# [FULLWIDTH COMMERCIAL AT]
"\uFF20" => "@"
# [FULLWIDTH REVERSE SOLIDUS]
"\uFF3C" => "\\"
# ‸ [CARET]
"\u2038" => "^"
# [FULLWIDTH CIRCUMFLEX ACCENT]
"\uFF3E" => "^"
# _ [FULLWIDTH LOW LINE]
"\uFF3F" => "_"
# [SWUNG DASH]
"\u2053" => "~"
# [FULLWIDTH TILDE]
"\uFF5E" => "~"
################################################################
# Below is the Perl script used to generate the above mappings #
# from ASCIIFoldingFilter.java: #
################################################################
#
# #!/usr/bin/perl
#
# use warnings;
# use strict;
#
# my @source_chars = ();
# my @source_char_descriptions = ();
# my $target = '';
#
# while (<>) {
# if (/case\s+'(\\u[A-F0-9]+)':\s*\/\/\s*(.*)/i) {
# push @source_chars, $1;
# push @source_char_descriptions, $2;
# next;
# }
# if (/output\[[^\]]+\]\s*=\s*'(\\'|\\\\|.)'/) {
# $target .= $1;
# next;
# }
# if (/break;/) {
# $target = "\\\"" if ($target eq '"');
# for my $source_char_num (0..$#source_chars) {
# print "# $source_char_descriptions[$source_char_num]\n";
# print "\"$source_chars[$source_char_num]\" => \"$target\"\n\n";
# }
# @source_chars = ();
# @source_char_descriptions = ();
# $target = '';
# }
# }

21
conf/extras/protwords.txt Normal file
View File

@ -0,0 +1,21 @@
# The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.
#-----------------------------------------------------------------------
# Use a protected word file to protect against the stemmer reducing two
# unrelated words to the same base word.
# Some non-words that normally won't be encountered,
# just to test that they won't be stemmed.
dontstems
zwhacky

1510
conf/extras/solrconfig.xml Normal file
View File

@ -0,0 +1,1510 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.
-->
<!--
For more details about configurations options that may appear in
this file, see http://wiki.apache.org/solr/SolrConfigXml.
-->
<config>
<!-- In all configuration below, a prefix of "solr." for class names
is an alias that causes solr to search appropriate packages,
including org.apache.solr.(search|update|request|core|analysis)
You may also specify a fully qualified Java classname if you
have your own custom plugins.
-->
<!-- Set this to 'false' if you want solr to continue working after
it has encountered an severe configuration error. In a
production environment, you may want solr to keep working even
if one handler is mis-configured.
You may also set this to false using by setting the system
property:
-Dsolr.abortOnConfigurationError=false
-->
<abortOnConfigurationError>${solr.abortOnConfigurationError:true}</abortOnConfigurationError>
<!-- Controls what version of Lucene various components of Solr
adhere to. Generally, you want to use the latest version to
get all bug fixes and improvements. It is highly recommended
that you fully re-index after changing this setting as it can
affect both how text is indexed and queried.
-->
<luceneMatchVersion>LUCENE_31</luceneMatchVersion>
<!-- lib directives can be used to instruct Solr to load an Jars
identified and use them to resolve any "plugins" specified in
your solrconfig.xml or schema.xml (ie: Analyzers, Request
Handlers, etc...).
All directories and paths are resolved relative to the
instanceDir.
If a "./lib" directory exists in your instanceDir, all files
found in it are included as if you had used the following
syntax...
<lib dir="./lib" />
-->
<!-- A dir option by itself adds any files found in the directory to
the classpath, this is useful for including all jars in a
directory.
-->
<lib dir="../../contrib/extraction/lib" />
<!-- When a regex is specified in addition to a directory, only the
files in that directory which completely match the regex
(anchored on both ends) will be included.
-->
<lib dir="../../dist/" regex="apache-solr-cell-\d.*\.jar" />
<lib dir="../../dist/" regex="apache-solr-clustering-\d.*\.jar" />
<lib dir="../../dist/" regex="apache-solr-dataimporthandler-\d.*\.jar" />
<!-- If a dir option (with or without a regex) is used and nothing
is found that matches, it will be ignored
-->
<lib dir="../../contrib/clustering/lib/" />
<lib dir="/total/crap/dir/ignored" />
<!-- an exact path can be used to specify a specific file. This
will cause a serious error to be logged if it can't be loaded.
-->
<!--
<lib path="../a-jar-that-does-not-exist.jar" />
-->
<!-- Data Directory
Used to specify an alternate directory to hold all index data
other than the default ./data under the Solr home. If
replication is in use, this should match the replication
configuration.
-->
<dataDir>${solr.data.dir:}</dataDir>
<!-- The DirectoryFactory to use for indexes.
solr.StandardDirectoryFactory, the default, is filesystem
based. solr.RAMDirectoryFactory is memory based, not
persistent, and doesn't work with replication.
-->
<directoryFactory name="DirectoryFactory"
class="${solr.directoryFactory:solr.StandardDirectoryFactory}"/>
<!-- Index Defaults
Values here affect all index writers and act as a default
unless overridden.
WARNING: See also the <mainIndex> section below for parameters
that overfor Solr's main Lucene index.
-->
<indexDefaults>
<useCompoundFile>false</useCompoundFile>
<mergeFactor>10</mergeFactor>
<!-- Sets the amount of RAM that may be used by Lucene indexing
for buffering added documents and deletions before they are
flushed to the Directory. -->
<ramBufferSizeMB>32</ramBufferSizeMB>
<!-- If both ramBufferSizeMB and maxBufferedDocs is set, then
Lucene will flush based on whichever limit is hit first.
-->
<!-- <maxBufferedDocs>1000</maxBufferedDocs> -->
<maxFieldLength>10000</maxFieldLength>
<writeLockTimeout>1000</writeLockTimeout>
<commitLockTimeout>10000</commitLockTimeout>
<!-- Expert: Merge Policy
The Merge Policy in Lucene controls how merging is handled by
Lucene. The default in 2.3 is the LogByteSizeMergePolicy,
previous versions used LogDocMergePolicy.
LogByteSizeMergePolicy chooses segments to merge based on
their size. The Lucene 2.2 default, LogDocMergePolicy chose
when to merge based on number of documents
Other implementations of MergePolicy must have a no-argument
constructor
-->
<!--
<mergePolicy class="org.apache.lucene.index.LogByteSizeMergePolicy"/>
-->
<!-- Expert: Merge Scheduler
The Merge Scheduler in Lucene controls how merges are
performed. The ConcurrentMergeScheduler (Lucene 2.3 default)
can perform merges in the background using separate threads.
The SerialMergeScheduler (Lucene 2.2 default) does not.
-->
<!--
<mergeScheduler class="org.apache.lucene.index.ConcurrentMergeScheduler"/>
-->
<!-- LockFactory
This option specifies which Lucene LockFactory implementation
to use.
single = SingleInstanceLockFactory - suggested for a
read-only index or when there is no possibility of
another process trying to modify the index.
native = NativeFSLockFactory - uses OS native file locking.
Do not use when multiple solr webapps in the same
JVM are attempting to share a single index.
simple = SimpleFSLockFactory - uses a plain file for locking
(For backwards compatibility with Solr 1.2, 'simple' is the
default if not specified.)
More details on the nuances of each LockFactory...
http://wiki.apache.org/lucene-java/AvailableLockFactories
-->
<lockType>native</lockType>
<!-- Expert: Controls how often Lucene loads terms into memory
Default is 128 and is likely good for most everyone.
-->
<!-- <termIndexInterval>256</termIndexInterval> -->
</indexDefaults>
<!-- Main Index
Values here override the values in the <indexDefaults> section
for the main on disk index.
-->
<mainIndex>
<useCompoundFile>false</useCompoundFile>
<ramBufferSizeMB>32</ramBufferSizeMB>
<mergeFactor>10</mergeFactor>
<!-- Unlock On Startup
If true, unlock any held write or commit locks on startup.
This defeats the locking mechanism that allows multiple
processes to safely access a lucene index, and should be used
with care.
This is not needed if lock type is 'none' or 'single'
-->
<unlockOnStartup>false</unlockOnStartup>
<!-- If true, IndexReaders will be reopened (often more efficient)
instead of closed and then opened.
-->
<reopenReaders>true</reopenReaders>
<!-- Commit Deletion Policy
Custom deletion policies can specified here. The class must
implement org.apache.lucene.index.IndexDeletionPolicy.
http://lucene.apache.org/java/2_9_1/api/all/org/apache/lucene/index/IndexDeletionPolicy.html
The standard Solr IndexDeletionPolicy implementation supports
deleting index commit points on number of commits, age of
commit point and optimized status.
The latest commit point should always be preserved regardless
of the criteria.
-->
<deletionPolicy class="solr.SolrDeletionPolicy">
<!-- The number of commit points to be kept -->
<str name="maxCommitsToKeep">1</str>
<!-- The number of optimized commit points to be kept -->
<str name="maxOptimizedCommitsToKeep">0</str>
<!--
Delete all commit points once they have reached the given age.
Supports DateMathParser syntax e.g.
-->
<!--
<str name="maxCommitAge">30MINUTES</str>
<str name="maxCommitAge">1DAY</str>
-->
</deletionPolicy>
<!-- Lucene Infostream
To aid in advanced debugging, Lucene provides an "InfoStream"
of detailed information when indexing.
Setting The value to true will instruct the underlying Lucene
IndexWriter to write it's debugging info the specified file
-->
<infoStream file="INFOSTREAM.txt">false</infoStream>
</mainIndex>
<!-- JMX
This example enables JMX if and only if an existing MBeanServer
is found, use this if you want to configure JMX through JVM
parameters. Remove this to disable exposing Solr configuration
and statistics to JMX.
For more details see http://wiki.apache.org/solr/SolrJmx
-->
<jmx />
<!-- If you want to connect to a particular server, specify the
agentId
-->
<!-- <jmx agentId="myAgent" /> -->
<!-- If you want to start a new MBeanServer, specify the serviceUrl -->
<!-- <jmx serviceUrl="service:jmx:rmi:///jndi/rmi://localhost:9999/solr"/>
-->
<!-- The default high-performance update handler -->
<updateHandler class="solr.DirectUpdateHandler2">
<!-- AutoCommit
Perform a <commit/> automatically under certain conditions.
Instead of enabling autoCommit, consider using "commitWithin"
when adding documents.
http://wiki.apache.org/solr/UpdateXmlMessages
maxDocs - Maximum number of documents to add since the last
commit before automaticly triggering a new commit.
maxTime - Maximum amount of time that is allowed to pass
since a document was added before automaticly
triggering a new commit.
-->
<!--
<autoCommit>
<maxDocs>10000</maxDocs>
<maxTime>1000</maxTime>
</autoCommit>
-->
<!-- Update Related Event Listeners
Various IndexWriter realted events can trigger Listeners to
take actions.
postCommit - fired after every commit or optimize command
postOptimize - fired after every optimize command
-->
<!-- The RunExecutableListener executes an external command from a
hook such as postCommit or postOptimize.
exe - the name of the executable to run
dir - dir to use as the current working directory. (default=".")
wait - the calling thread waits until the executable returns.
(default="true")
args - the arguments to pass to the program. (default is none)
env - environment variables to set. (default is none)
-->
<!-- This example shows how RunExecutableListener could be used
with the script based replication...
http://wiki.apache.org/solr/CollectionDistribution
-->
<!--
<listener event="postCommit" class="solr.RunExecutableListener">
<str name="exe">solr/bin/snapshooter</str>
<str name="dir">.</str>
<bool name="wait">true</bool>
<arr name="args"> <str>arg1</str> <str>arg2</str> </arr>
<arr name="env"> <str>MYVAR=val1</str> </arr>
</listener>
-->
</updateHandler>
<!-- IndexReaderFactory
Use the following format to specify a custom IndexReaderFactory,
which allows for alternate IndexReader implementations.
** Experimental Feature **
Please note - Using a custom IndexReaderFactory may prevent
certain other features from working. The API to
IndexReaderFactory may change without warning or may even be
removed from future releases if the problems cannot be
resolved.
** Features that may not work with custom IndexReaderFactory **
The ReplicationHandler assumes a disk-resident index. Using a
custom IndexReader implementation may cause incompatibility
with ReplicationHandler and may cause replication to not work
correctly. See SOLR-1366 for details.
-->
<!--
<indexReaderFactory name="IndexReaderFactory" class="package.class">
<str name="someArg">Some Value</str>
</indexReaderFactory >
-->
<!-- By explicitly declaring the Factory, the termIndexDivisor can
be specified.
-->
<!--
<indexReaderFactory name="IndexReaderFactory"
class="solr.StandardIndexReaderFactory">
<int name="setTermIndexDivisor">12</int>
</indexReaderFactory >
-->
<query>
<!-- Max Boolean Clauses
Maximum number of clauses in each BooleanQuery, an exception
is thrown if exceeded.
** WARNING **
This option actually modifies a global Lucene property that
will affect all SolrCores. If multiple solrconfig.xml files
disagree on this property, the value at any given moment will
be based on the last SolrCore to be initialized.
-->
<maxBooleanClauses>4096</maxBooleanClauses>
<!-- Solr Internal Query Caches
There are two implementations of cache available for Solr,
LRUCache, based on a synchronized LinkedHashMap, and
FastLRUCache, based on a ConcurrentHashMap.
FastLRUCache has faster gets and slower puts in single
threaded operation and thus is generally faster than LRUCache
when the hit ratio of the cache is high (> 75%), and may be
faster under other scenarios on multi-cpu systems.
-->
<!-- Filter Cache
Cache used by SolrIndexSearcher for filters (DocSets),
unordered sets of *all* documents that match a query. When a
new searcher is opened, its caches may be prepopulated or
"autowarmed" using data from caches in the old searcher.
autowarmCount is the number of items to prepopulate. For
LRUCache, the autowarmed items will be the most recently
accessed items.
Parameters:
class - the SolrCache implementation LRUCache or
(LRUCache or FastLRUCache)
size - the maximum number of entries in the cache
initialSize - the initial capacity (number of entries) of
the cache. (see java.util.HashMap)
autowarmCount - the number of entries to prepopulate from
and old cache.
-->
<filterCache class="solr.FastLRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<!-- Query Result Cache
Caches results of searches - ordered lists of document ids
(DocList) based on a query, a sort, and the range of documents requested.
-->
<queryResultCache class="solr.LRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<!-- Document Cache
Caches Lucene Document objects (the stored fields for each
document). Since Lucene internal document ids are transient,
this cache will not be autowarmed.
-->
<documentCache class="solr.LRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<!-- Field Value Cache
Cache used to hold field values that are quickly accessible
by document id. The fieldValueCache is created by default
even if not configured here.
-->
<!--
<fieldValueCache class="solr.FastLRUCache"
size="512"
autowarmCount="128"
showItems="32" />
-->
<!-- Custom Cache
Example of a generic cache. These caches may be accessed by
name through SolrIndexSearcher.getCache(),cacheLookup(), and
cacheInsert(). The purpose is to enable easy caching of
user/application level data. The regenerator argument should
be specified as an implementation of solr.CacheRegenerator
if autowarming is desired.
-->
<!--
<cache name="myUserCache"
class="solr.LRUCache"
size="4096"
initialSize="1024"
autowarmCount="1024"
regenerator="com.mycompany.MyRegenerator"
/>
-->
<!-- Lazy Field Loading
If true, stored fields that are not requested will be loaded
lazily. This can result in a significant speed improvement
if the usual case is to not load all stored fields,
especially if the skipped fields are large compressed text
fields.
-->
<enableLazyFieldLoading>true</enableLazyFieldLoading>
<!-- Use Filter For Sorted Query
A possible optimization that attempts to use a filter to
satisfy a search. If the requested sort does not include
score, then the filterCache will be checked for a filter
matching the query. If found, the filter will be used as the
source of document ids, and then the sort will be applied to
that.
For most situations, this will not be useful unless you
frequently get the same search repeatedly with differnet sort
options, and none of them ever use "score"
-->
<!--
<useFilterForSortedQuery>true</useFilterForSortedQuery>
-->
<!-- Result Window Size
An optimization for use with the queryResultCache. When a search
is requested, a superset of the requested number of document ids
are collected. For example, if a search for a particular query
requests matching documents 10 through 19, and queryWindowSize is 50,
then documents 0 through 49 will be collected and cached. Any further
requests in that range can be satisfied via the cache.
-->
<queryResultWindowSize>20</queryResultWindowSize>
<!-- Maximum number of documents to cache for any entry in the
queryResultCache.
-->
<queryResultMaxDocsCached>200</queryResultMaxDocsCached>
<!-- Query Related Event Listeners
Various IndexSearcher related events can trigger Listeners to
take actions.
newSearcher - fired whenever a new searcher is being prepared
and there is a current searcher handling requests (aka
registered). It can be used to prime certain caches to
prevent long request times for certain requests.
firstSearcher - fired whenever a new searcher is being
prepared but there is no current registered searcher to handle
requests or to gain autowarming data from.
-->
<!-- QuerySenderListener takes an array of NamedList and executes a
local query request for each NamedList in sequence.
-->
<listener event="newSearcher" class="solr.QuerySenderListener">
<arr name="queries">
<!--
<lst><str name="q">solr</str><str name="sort">price asc</str></lst>
<lst><str name="q">rocks</str><str name="sort">weight asc</str></lst>
-->
</arr>
</listener>
<listener event="firstSearcher" class="solr.QuerySenderListener">
<arr name="queries">
<lst>
<str name="q">static firstSearcher warming in solrconfig.xml</str>
</lst>
</arr>
</listener>
<!-- Use Cold Searcher
If a search request comes in and there is no current
registered searcher, then immediately register the still
warming searcher and use it. If "false" then all requests
will block until the first searcher is done warming.
-->
<useColdSearcher>false</useColdSearcher>
<!-- Max Warming Searchers
Maximum number of searchers that may be warming in the
background concurrently. An error is returned if this limit
is exceeded.
Recommend values of 1-2 for read-only slaves, higher for
masters w/o cache warming.
-->
<maxWarmingSearchers>2</maxWarmingSearchers>
</query>
<!-- Request Dispatcher
This section contains instructions for how the SolrDispatchFilter
should behave when processing requests for this SolrCore.
handleSelect affects the behavior of requests such as /select?qt=XXX
handleSelect="true" will cause the SolrDispatchFilter to process
the request and will result in consistent error handling and
formating for all types of requests.
handleSelect="false" will cause the SolrDispatchFilter to
ignore "/select" requests and fallback to using the legacy
SolrServlet and it's Solr 1.1 style error formatting
-->
<requestDispatcher handleSelect="true" >
<!-- Request Parsing
These settings indicate how Solr Requests may be parsed, and
what restrictions may be placed on the ContentStreams from
those requests
enableRemoteStreaming - enables use of the stream.file
and stream.url paramaters for specifying remote streams.
multipartUploadLimitInKB - specifies the max size of
Multipart File Uploads that Solr will alow in a Request.
*** WARNING ***
The settings below authorize Solr to fetch remote files, You
should make sure your system has some authentication before
using enableRemoteStreaming="true"
-->
<requestParsers enableRemoteStreaming="true"
multipartUploadLimitInKB="2048000" />
<!-- HTTP Caching
Set HTTP caching related parameters (for proxy caches and clients).
The options below instruct Solr not to output any HTTP Caching
related headers
-->
<httpCaching never304="true" />
<!-- If you include a <cacheControl> directive, it will be used to
generate a Cache-Control header (as well as an Expires header
if the value contains "max-age=")
By default, no Cache-Control header is generated.
You can use the <cacheControl> option even if you have set
never304="true"
-->
<!--
<httpCaching never304="true" >
<cacheControl>max-age=30, public</cacheControl>
</httpCaching>
-->
<!-- To enable Solr to responde with automaticly generated HTTP
Caching headers, and to response to Cache Validation requests
correctly, set the value of never304="false"
This will cause Solr to generate Last-Modified and ETag
headers based on the properties of the Index.
The following options can also be specified to affect the
values of these headers...
lastModFrom - the default value is "openTime" which means the
Last-Modified value (and validation against If-Modified-Since
requests) will all be relative to when the current Searcher
was opened. You can change it to lastModFrom="dirLastMod" if
you want the value to exactly corrispond to when the physical
index was last modified.
etagSeed="..." is an option you can change to force the ETag
header (and validation against If-None-Match requests) to be
differnet even if the index has not changed (ie: when making
significant changes to your config file)
(lastModifiedFrom and etagSeed are both ignored if you use
the never304="true" option)
-->
<!--
<httpCaching lastModifiedFrom="openTime"
etagSeed="Solr">
<cacheControl>max-age=30, public</cacheControl>
</httpCaching>
-->
</requestDispatcher>
<!-- Request Handlers
http://wiki.apache.org/solr/SolrRequestHandler
incoming queries will be dispatched to the correct handler
based on the path or the qt (query type) param.
Names starting with a '/' are accessed with the a path equal to
the registered name. Names without a leading '/' are accessed
with: http://host/app/[core/]select?qt=name
If a /select request is processed with out a qt param
specified, the requestHandler that declares default="true" will
be used.
If a Request Handler is declared with startup="lazy", then it will
not be initialized until the first request that uses it.
-->
<!-- SearchHandler
http://wiki.apache.org/solr/SearchHandler
For processing Search Queries, the primary Request Handler
provided with Solr is "SearchHandler" It delegates to a sequent
of SearchComponents (see below) and supports distributed
queries across multiple shards
-->
<requestHandler name="search" class="solr.SearchHandler" default="true">
<!-- default values for query parameters can be specified, these
will be overridden by parameters in the request
-->
<lst name="defaults">
<str name="echoParams">explicit</str>
<int name="rows">10</int>
<str name="defType">edismax</str>
<str name="qf">_text^0.5 SiteTree_Title^1 </str>
</lst>
<!-- In addition to defaults, "appends" params can be specified
to identify values which should be appended to the list of
multi-val params from the query (or the existing "defaults").
-->
<!-- In this example, the param "fq=instock:true" would be appended to
any query time fq params the user may specify, as a mechanism for
partitioning the index, independent of any user selected filtering
that may also be desired (perhaps as a result of faceted searching).
NOTE: there is *absolutely* nothing a client can do to prevent these
"appends" values from being used, so don't use this mechanism
unless you are sure you always want it.
-->
<!--
<lst name="appends">
<str name="fq">inStock:true</str>
</lst>
-->
<!-- "invariants" are a way of letting the Solr maintainer lock down
the options available to Solr clients. Any params values
specified here are used regardless of what values may be specified
in either the query, the "defaults", or the "appends" params.
In this example, the facet.field and facet.query params would
be fixed, limiting the facets clients can use. Faceting is
not turned on by default - but if the client does specify
facet=true in the request, these are the only facets they
will be able to see counts for; regardless of what other
facet.field or facet.query params they may specify.
NOTE: there is *absolutely* nothing a client can do to prevent these
"invariants" values from being used, so don't use this mechanism
unless you are sure you always want it.
-->
<!--
<lst name="invariants">
<str name="facet.field">cat</str>
<str name="facet.field">manu_exact</str>
<str name="facet.query">price:[* TO 500]</str>
<str name="facet.query">price:[500 TO *]</str>
</lst>
-->
<!-- If the default list of SearchComponents is not desired, that
list can either be overridden completely, or components can be
prepended or appended to the default list. (see below)
-->
<!--
<arr name="components">
<str>nameOfCustomComponent1</str>
<str>nameOfCustomComponent2</str>
</arr>
-->
</requestHandler>
<!-- A Robust Example
This example SearchHandler declaration shows off usage of the
SearchHandler with many defaults declared
Note that multiple instances of hte same Request Handler
(SearchHandler) can be registered multiple times with different
names (and different init parameters)
-->
<requestHandler name="/browse" class="solr.SearchHandler">
<lst name="defaults">
<str name="echoParams">explicit</str>
<!-- VelocityResponseWriter settings -->
<str name="wt">velocity</str>
<str name="v.template">browse</str>
<str name="v.layout">layout</str>
<str name="title">Solritas</str>
<str name="defType">edismax</str>
<str name="q.alt">*:*</str>
<str name="rows">10</str>
<str name="fl">*,score</str>
<str name="mlt.qf">
text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
</str>
<str name="mlt.fl">text,features,name,sku,id,manu,cat</str>
<int name="mlt.count">3</int>
<str name="qf">
text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
</str>
<str name="facet">on</str>
<str name="facet.field">cat</str>
<str name="facet.field">manu_exact</str>
<str name="facet.query">ipod</str>
<str name="facet.query">GB</str>
<str name="facet.mincount">1</str>
<str name="facet.pivot">cat,inStock</str>
<str name="facet.range">price</str>
<int name="f.price.facet.range.start">0</int>
<int name="f.price.facet.range.end">600</int>
<int name="f.price.facet.range.gap">50</int>
<str name="f.price.facet.range.other">after</str>
<str name="facet.range">manufacturedate_dt</str>
<str name="f.manufacturedate_dt.facet.range.start">NOW/YEAR-10YEARS</str>
<str name="f.manufacturedate_dt.facet.range.end">NOW</str>
<str name="f.manufacturedate_dt.facet.range.gap">+1YEAR</str>
<str name="f.manufacturedate_dt.facet.range.other">before</str>
<str name="f.manufacturedate_dt.facet.range.other">after</str>
<!-- Highlighting defaults -->
<str name="hl">on</str>
<str name="hl.fl">text features name</str>
<str name="f.name.hl.fragsize">0</str>
<str name="f.name.hl.alternateField">name</str>
</lst>
<arr name="last-components">
<str>spellcheck</str>
</arr>
<!--
<str name="url-scheme">httpx</str>
-->
</requestHandler>
<!-- XML Update Request Handler.
http://wiki.apache.org/solr/UpdateXmlMessages
The canonical Request Handler for Modifying the Index through
commands specified using XML.
Note: Since solr1.1 requestHandlers requires a valid content
type header if posted in the body. For example, curl now
requires: -H 'Content-type:text/xml; charset=utf-8'
-->
<requestHandler name="/update"
class="solr.XmlUpdateRequestHandler">
<!-- See below for information on defining
updateRequestProcessorChains that can be used by name
on each Update Request
-->
<!--
<lst name="defaults">
<str name="update.processor">dedupe</str>
</lst>
-->
</requestHandler>
<!-- Binary Update Request Handler
http://wiki.apache.org/solr/javabin
-->
<requestHandler name="/update/javabin"
class="solr.BinaryUpdateRequestHandler" />
<!-- CSV Update Request Handler
http://wiki.apache.org/solr/UpdateCSV
-->
<requestHandler name="/update/csv"
class="solr.CSVRequestHandler"
startup="lazy" />
<!-- JSON Update Request Handler
http://wiki.apache.org/solr/UpdateJSON
-->
<requestHandler name="/update/json"
class="solr.JsonUpdateRequestHandler"
startup="lazy" />
<!-- Solr Cell Update Request Handler
http://wiki.apache.org/solr/ExtractingRequestHandler
-->
<requestHandler name="/update/extract"
startup="lazy"
class="solr.extraction.ExtractingRequestHandler" >
<lst name="defaults">
<!-- All the main content goes into "text"... if you need to return
the extracted text or do highlighting, use a stored field. -->
<str name="fmap.content">text</str>
<str name="lowernames">true</str>
<str name="uprefix">ignored_</str>
<!-- capture link hrefs but ignore div attributes -->
<str name="captureAttr">true</str>
<str name="fmap.a">links</str>
<str name="fmap.div">ignored_</str>
</lst>
</requestHandler>
<!-- Field Analysis Request Handler
RequestHandler that provides much the same functionality as
analysis.jsp. Provides the ability to specify multiple field
types and field names in the same request and outputs
index-time and query-time analysis for each of them.
Request parameters are:
analysis.fieldname - field name whose analyzers are to be used
analysis.fieldtype - field type whose analyzers are to be used
analysis.fieldvalue - text for index-time analysis
q (or analysis.q) - text for query time analysis
analysis.showmatch (true|false) - When set to true and when
query analysis is performed, the produced tokens of the
field value analysis will be marked as "matched" for every
token that is produces by the query analysis
-->
<requestHandler name="/analysis/field"
startup="lazy"
class="solr.FieldAnalysisRequestHandler" />
<!-- Document Analysis Handler
http://wiki.apache.org/solr/AnalysisRequestHandler
An analysis handler that provides a breakdown of the analysis
process of provided docuemnts. This handler expects a (single)
content stream with the following format:
<docs>
<doc>
<field name="id">1</field>
<field name="name">The Name</field>
<field name="text">The Text Value</field>
</doc>
<doc>...</doc>
<doc>...</doc>
...
</docs>
Note: Each document must contain a field which serves as the
unique key. This key is used in the returned response to assoicate
ananalysis breakdown to the analyzed document.
Like the FieldAnalysisRequestHandler, this handler also supports
query analysis by sending either an "analysis.query" or "q"
request paraemter that holds the query text to be analyized. It
also supports the "analysis.showmatch" parameter which when set to
true, all field tokens that match the query tokens will be marked
as a "match".
-->
<requestHandler name="/analysis/document"
class="solr.DocumentAnalysisRequestHandler"
startup="lazy" />
<!-- Admin Handlers
Admin Handlers - This will register all the standard admin
RequestHandlers.
-->
<requestHandler name="/admin/"
class="solr.admin.AdminHandlers" />
<!-- This single handler is equivilent to the following... -->
<!--
<requestHandler name="/admin/luke" class="solr.admin.LukeRequestHandler" />
<requestHandler name="/admin/system" class="solr.admin.SystemInfoHandler" />
<requestHandler name="/admin/plugins" class="solr.admin.PluginInfoHandler" />
<requestHandler name="/admin/threads" class="solr.admin.ThreadDumpHandler" />
<requestHandler name="/admin/properties" class="solr.admin.PropertiesRequestHandler" />
<requestHandler name="/admin/file" class="solr.admin.ShowFileRequestHandler" >
-->
<!-- If you wish to hide files under ${solr.home}/conf, explicitly
register the ShowFileRequestHandler using:
-->
<!--
<requestHandler name="/admin/file"
class="solr.admin.ShowFileRequestHandler" >
<lst name="invariants">
<str name="hidden">synonyms.txt</str>
<str name="hidden">anotherfile.txt</str>
</lst>
</requestHandler>
-->
<!-- ping/healthcheck -->
<requestHandler name="/admin/ping" class="solr.PingRequestHandler">
<lst name="defaults">
<str name="qt">search</str>
<str name="q">solrpingquery</str>
<str name="echoParams">all</str>
</lst>
</requestHandler>
<!-- Echo the request contents back to the client -->
<requestHandler name="/debug/dump" class="solr.DumpRequestHandler" >
<lst name="defaults">
<str name="echoParams">explicit</str>
<str name="echoHandler">true</str>
</lst>
</requestHandler>
<!-- Solr Replication
The SolrReplicationHandler supports replicating indexes from a
"master" used for indexing and "salves" used for queries.
http://wiki.apache.org/solr/SolrReplication
In the example below, remove the <lst name="master"> section if
this is just a slave and remove the <lst name="slave"> section
if this is just a master.
-->
<!--
<requestHandler name="/replication" class="solr.ReplicationHandler" >
<lst name="master">
<str name="replicateAfter">commit</str>
<str name="replicateAfter">startup</str>
<str name="confFiles">schema.xml,stopwords.txt</str>
</lst>
<lst name="slave">
<str name="masterUrl">http://localhost:8983/solr/replication</str>
<str name="pollInterval">00:00:60</str>
</lst>
</requestHandler>
-->
<!-- Search Components
Search components are registered to SolrCore and used by
instances of SearchHandler (which can access them by name)
By default, the following components are avaliable:
<searchComponent name="query" class="solr.QueryComponent" />
<searchComponent name="facet" class="solr.FacetComponent" />
<searchComponent name="mlt" class="solr.MoreLikeThisComponent" />
<searchComponent name="highlight" class="solr.HighlightComponent" />
<searchComponent name="stats" class="solr.StatsComponent" />
<searchComponent name="debug" class="solr.DebugComponent" />
Default configuration in a requestHandler would look like:
<arr name="components">
<str>query</str>
<str>facet</str>
<str>mlt</str>
<str>highlight</str>
<str>stats</str>
<str>debug</str>
</arr>
If you register a searchComponent to one of the standard names,
that will be used instead of the default.
To insert components before or after the 'standard' components, use:
<arr name="first-components">
<str>myFirstComponentName</str>
</arr>
<arr name="last-components">
<str>myLastComponentName</str>
</arr>
NOTE: The component registered with the name "debug" will
always be executed after the "last-components"
-->
<!-- Spell Check
The spell check component can return a list of alternative spelling
suggestions.
http://wiki.apache.org/solr/SpellCheckComponent
-->
<searchComponent name="spellcheck" class="solr.SpellCheckComponent">
<str name="queryAnalyzerFieldType">textSpell</str>
<!-- Multiple "Spell Checkers" can be declared and used by this
component
-->
<!-- a spellchecker built from a field of hte main index, and
written to disk
-->
<lst name="spellchecker">
<str name="name">default</str>
<str name="field">name</str>
<str name="spellcheckIndexDir">spellchecker</str>
</lst>
<!-- a spellchecker that uses a different distance measure -->
<!--
<lst name="spellchecker">
<str name="name">jarowinkler</str>
<str name="field">spell</str>
<str name="distanceMeasure">
org.apache.lucene.search.spell.JaroWinklerDistance
</str>
<str name="spellcheckIndexDir">spellcheckerJaro</str>
</lst>
-->
<!-- a spellchecker that use an alternate comparator
comparatorClass be one of:
1. score (default)
2. freq (Frequency first, then score)
3. A fully qualified class name
-->
<!--
<lst name="spellchecker">
<str name="name">freq</str>
<str name="field">lowerfilt</str>
<str name="spellcheckIndexDir">spellcheckerFreq</str>
<str name="comparatorClass">freq</str>
<str name="buildOnCommit">true</str>
-->
<!-- A spellchecker that reads the list of words from a file -->
<!--
<lst name="spellchecker">
<str name="classname">solr.FileBasedSpellChecker</str>
<str name="name">file</str>
<str name="sourceLocation">spellings.txt</str>
<str name="characterEncoding">UTF-8</str>
<str name="spellcheckIndexDir">spellcheckerFile</str>
</lst>
-->
</searchComponent>
<!-- A request handler for demonstrating the spellcheck component.
NOTE: This is purely as an example. The whole purpose of the
SpellCheckComponent is to hook it into the request handler that
handles your normal user queries so that a separate request is
not needed to get suggestions.
IN OTHER WORDS, THERE IS REALLY GOOD CHANCE THE SETUP BELOW IS
NOT WHAT YOU WANT FOR YOUR PRODUCTION SYSTEM!
See http://wiki.apache.org/solr/SpellCheckComponent for details
on the request parameters.
-->
<requestHandler name="/spell" class="solr.SearchHandler" startup="lazy">
<lst name="defaults">
<str name="spellcheck.onlyMorePopular">false</str>
<str name="spellcheck.extendedResults">false</str>
<str name="spellcheck.count">1</str>
</lst>
<arr name="last-components">
<str>spellcheck</str>
</arr>
</requestHandler>
<!-- Term Vector Component
http://wiki.apache.org/solr/TermVectorComponent
-->
<searchComponent name="tvComponent" class="solr.TermVectorComponent"/>
<!-- A request handler for demonstrating the term vector component
This is purely as an example.
In reality you will likely want to add the component to your
already specified request handlers.
-->
<requestHandler name="tvrh" class="solr.SearchHandler" startup="lazy">
<lst name="defaults">
<bool name="tv">true</bool>
</lst>
<arr name="last-components">
<str>tvComponent</str>
</arr>
</requestHandler>
<!-- Clustering Component
http://wiki.apache.org/solr/ClusteringComponent
This relies on third party jars which are notincluded in the
release. To use this component (and the "/clustering" handler)
Those jars will need to be downloaded, and you'll need to set
the solr.cluster.enabled system property when running solr...
java -Dsolr.clustering.enabled=true -jar start.jar
-->
<searchComponent name="clustering"
enable="${solr.clustering.enabled:false}"
class="solr.clustering.ClusteringComponent" >
<!-- Declare an engine -->
<lst name="engine">
<!-- The name, only one can be named "default" -->
<str name="name">default</str>
<!-- Class name of Carrot2 clustering algorithm.
Currently available algorithms are:
* org.carrot2.clustering.lingo.LingoClusteringAlgorithm
* org.carrot2.clustering.stc.STCClusteringAlgorithm
See http://project.carrot2.org/algorithms.html for the
algorithm's characteristics.
-->
<str name="carrot.algorithm">org.carrot2.clustering.lingo.LingoClusteringAlgorithm</str>
<!-- Overriding values for Carrot2 default algorithm attributes.
For a description of all available attributes, see:
http://download.carrot2.org/stable/manual/#chapter.components.
Use attribute key as name attribute of str elements
below. These can be further overridden for individual
requests by specifying attribute key as request parameter
name and attribute value as parameter value.
-->
<str name="LingoClusteringAlgorithm.desiredClusterCountBase">20</str>
<!-- The language to assume for the documents.
For a list of allowed values, see:
http://download.carrot2.org/stable/manual/#section.attribute.lingo.MultilingualClustering.defaultLanguage
-->
<str name="MultilingualClustering.defaultLanguage">ENGLISH</str>
</lst>
<lst name="engine">
<str name="name">stc</str>
<str name="carrot.algorithm">org.carrot2.clustering.stc.STCClusteringAlgorithm</str>
</lst>
</searchComponent>
<!-- A request handler for demonstrating the clustering component
This is purely as an example.
In reality you will likely want to add the component to your
already specified request handlers.
-->
<requestHandler name="/clustering"
startup="lazy"
enable="${solr.clustering.enabled:false}"
class="solr.SearchHandler">
<lst name="defaults">
<bool name="clustering">true</bool>
<str name="clustering.engine">default</str>
<bool name="clustering.results">true</bool>
<!-- The title field -->
<str name="carrot.title">name</str>
<str name="carrot.url">id</str>
<!-- The field to cluster on -->
<str name="carrot.snippet">features</str>
<!-- produce summaries -->
<bool name="carrot.produceSummary">true</bool>
<!-- the maximum number of labels per cluster -->
<!--<int name="carrot.numDescriptions">5</int>-->
<!-- produce sub clusters -->
<bool name="carrot.outputSubClusters">false</bool>
<str name="defType">edismax</str>
<str name="qf">
text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
</str>
<str name="q.alt">*:*</str>
<str name="rows">10</str>
<str name="fl">*,score</str>
</lst>
<arr name="last-components">
<str>clustering</str>
</arr>
</requestHandler>
<!-- Terms Component
http://wiki.apache.org/solr/TermsComponent
A component to return terms and document frequency of those
terms
-->
<searchComponent name="terms" class="solr.TermsComponent"/>
<!-- A request handler for demonstrating the terms component -->
<requestHandler name="/terms" class="solr.SearchHandler" startup="lazy">
<lst name="defaults">
<bool name="terms">true</bool>
</lst>
<arr name="components">
<str>terms</str>
</arr>
</requestHandler>
<!-- Query Elevation Component
http://wiki.apache.org/solr/QueryElevationComponent
a search component that enables you to configure the top
results for a given query regardless of the normal lucene
scoring.
-->
<searchComponent name="elevator" class="solr.QueryElevationComponent" >
<!-- pick a fieldType to analyze queries -->
<str name="queryFieldType">string</str>
<str name="config-file">elevate.xml</str>
</searchComponent>
<!-- A request handler for demonstrating the elevator component -->
<requestHandler name="/elevate" class="solr.SearchHandler" startup="lazy">
<lst name="defaults">
<str name="echoParams">explicit</str>
</lst>
<arr name="last-components">
<str>elevator</str>
</arr>
</requestHandler>
<!-- Highlighting Component
http://wiki.apache.org/solr/HighlightingParameters
-->
<searchComponent class="solr.HighlightComponent" name="highlight">
<highlighting>
<!-- Configure the standard fragmenter -->
<!-- This could most likely be commented out in the "default" case -->
<fragmenter name="gap"
default="true"
class="solr.highlight.GapFragmenter">
<lst name="defaults">
<int name="hl.fragsize">100</int>
</lst>
</fragmenter>
<!-- A regular-expression-based fragmenter
(for sentence extraction)
-->
<fragmenter name="regex"
class="solr.highlight.RegexFragmenter">
<lst name="defaults">
<!-- slightly smaller fragsizes work better because of slop -->
<int name="hl.fragsize">70</int>
<!-- allow 50% slop on fragment sizes -->
<float name="hl.regex.slop">0.5</float>
<!-- a basic sentence pattern -->
<str name="hl.regex.pattern">[-\w ,/\n\&quot;&apos;]{20,200}</str>
</lst>
</fragmenter>
<!-- Configure the standard formatter -->
<formatter name="html"
default="true"
class="solr.highlight.HtmlFormatter">
<lst name="defaults">
<str name="hl.simple.pre"><![CDATA[<em>]]></str>
<str name="hl.simple.post"><![CDATA[</em>]]></str>
</lst>
</formatter>
<!-- Configure the standard encoder -->
<encoder name="html"
default="true"
class="solr.highlight.HtmlEncoder" />
<!-- Configure the standard fragListBuilder -->
<fragListBuilder name="simple"
default="true"
class="solr.highlight.SimpleFragListBuilder"/>
<!-- Configure the single fragListBuilder -->
<fragListBuilder name="single"
class="solr.highlight.SingleFragListBuilder"/>
<!-- default tag FragmentsBuilder -->
<fragmentsBuilder name="default"
default="true"
class="solr.highlight.ScoreOrderFragmentsBuilder">
<!--
<lst name="defaults">
<str name="hl.multiValuedSeparatorChar">/</str>
</lst>
-->
</fragmentsBuilder>
<!-- multi-colored tag FragmentsBuilder -->
<fragmentsBuilder name="colored"
class="solr.highlight.ScoreOrderFragmentsBuilder">
<lst name="defaults">
<str name="hl.tag.pre"><![CDATA[
<b style="background:yellow">,<b style="background:lawgreen">,
<b style="background:aquamarine">,<b style="background:magenta">,
<b style="background:palegreen">,<b style="background:coral">,
<b style="background:wheat">,<b style="background:khaki">,
<b style="background:lime">,<b style="background:deepskyblue">]]></str>
<str name="hl.tag.post"><![CDATA[</b>]]></str>
</lst>
</fragmentsBuilder>
</highlighting>
</searchComponent>
<!-- Update Processors
Chains of Update Processor Factories for dealing with Update
Requests can be declared, and then used by name in Update
Request Processors
http://wiki.apache.org/solr/UpdateRequestProcessor
-->
<!-- Deduplication
An example dedup update processor that creates the "id" field
on the fly based on the hash code of some other fields. This
example has overwriteDupes set to false since we are using the
id field as the signatureField and Solr will maintain
uniqueness based on that anyway.
-->
<!--
<updateRequestProcessorChain name="dedupe">
<processor class="solr.processor.SignatureUpdateProcessorFactory">
<bool name="enabled">true</bool>
<str name="signatureField">id</str>
<bool name="overwriteDupes">false</bool>
<str name="fields">name,features,cat</str>
<str name="signatureClass">solr.processor.Lookup3Signature</str>
</processor>
<processor class="solr.LogUpdateProcessorFactory" />
<processor class="solr.RunUpdateProcessorFactory" />
</updateRequestProcessorChain>
-->
<!-- Response Writers
http://wiki.apache.org/solr/QueryResponseWriter
Request responses will be written using the writer specified by
the 'wt' request parameter matching the name of a registered
writer.
The "default" writer is the default and will be used if 'wt' is
not specified in the request.
-->
<!-- The following response writers are implicitly configured unless
overridden...
-->
<!--
<queryResponseWriter name="xml"
default="true"
class="solr.XMLResponseWriter" />
<queryResponseWriter name="json" class="solr.JSONResponseWriter"/>
<queryResponseWriter name="python" class="solr.PythonResponseWriter"/>
<queryResponseWriter name="ruby" class="solr.RubyResponseWriter"/>
<queryResponseWriter name="php" class="solr.PHPResponseWriter"/>
<queryResponseWriter name="phps" class="solr.PHPSerializedResponseWriter"/>
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter"/>
<queryResponseWriter name="csv" class="solr.CSVResponseWriter"/>
-->
<!--
Custom response writers can be declared as needed...
-->
<!--
<queryResponseWriter name="custom" class="com.example.MyResponseWriter"/>
-->
<!-- XSLT response writer transforms the XML output by any xslt file found
in Solr's conf/xslt directory. Changes to xslt files are checked for
every xsltCacheLifetimeSeconds.
-->
<queryResponseWriter name="xslt" class="solr.XSLTResponseWriter">
<int name="xsltCacheLifetimeSeconds">5</int>
</queryResponseWriter>
<!-- Query Parsers
http://wiki.apache.org/solr/SolrQuerySyntax
Multiple QParserPlugins can be registered by name, and then
used in either the "defType" param for the QueryComponent (used
by SearchHandler) or in LocalParams
-->
<!-- example of registering a query parser -->
<!--
<queryParser name="myparser" class="com.mycompany.MyQParserPlugin"/>
-->
<!-- Function Parsers
http://wiki.apache.org/solr/FunctionQuery
Multiple ValueSourceParsers can be registered by name, and then
used as function names when using the "func" QParser.
-->
<!-- example of registering a custom function parser -->
<!--
<valueSourceParser name="myfunc"
class="com.mycompany.MyValueSourceParser" />
-->
<!-- Legacy config for the admin interface -->
<admin>
<defaultQuery>*:*</defaultQuery>
<!-- configure a healthcheck file for servers behind a
loadbalancer
-->
<!--
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>

58
conf/extras/stopwords.txt Normal file
View File

@ -0,0 +1,58 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.
#-----------------------------------------------------------------------
# a couple of test stopwords to test that the words are really being
# configured from this file:
stopworda
stopwordb
#Standard english stop words taken from Lucene's StopAnalyzer
a
an
and
are
as
at
be
but
by
for
if
in
into
is
it
no
not
of
on
or
s
such
t
that
the
their
then
there
these
they
this
to
was
will
with

29
conf/extras/synonyms.txt Normal file
View File

@ -0,0 +1,29 @@
# The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.
#-----------------------------------------------------------------------
#some test synonym mappings unlikely to appear in real input text
aaafoo => aaabar
bbbfoo => bbbfoo bbbbar
cccfoo => cccbar cccbaz
fooaaa,baraaa,bazaaa
# Some synonym groups specific to this example
GB,gib,gigabyte,gigabytes
MB,mib,megabyte,megabytes
Television, Televisions, TV, TVs
#notice we use "gib" instead of "GiB" so any WordDelimiterFilter coming
#after us won't split it into two words.
# Synonym mappings can be used for spelling correction too
pixima => pixma

66
conf/templates/schema.ss Normal file
View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.
-->
<!--
This is the Solr schema file. This file should be named "schema.xml" and
should be in the conf directory under the solr home
(i.e. ./solr/conf/schema.xml by default)
or located where the classloader for the Solr webapp can find it.
This example schema is the recommended starting point for users.
It should be kept correct and concise, usable out-of-the-box.
For more information, on how to customize this file, please see
http://wiki.apache.org/solr/SchemaXml
PERFORMANCE NOTE: this schema includes many optional features and should not
be used for benchmarking. To improve performance one could
- set stored="false" for all fields possible (esp large fields) when you
only need to search on the field but don't need to return the original
value.
- set indexed="false" if you don't need to search on the field, but only
return the field as a result of searching on other indexed fields.
- remove all unneeded copyField statements
- for best index size and searching performance, set "index" to false
for all general text fields, use copyField to copy them to the
catchall "text" field, and use that for searching.
- For maximum indexing performance, use the StreamingUpdateSolrServer
java client.
- Remember to run the JVM in server mode, and use a higher logging level
that avoids logging every request
-->
<schema name="$IndexName" version="1.3">
<types>
$Types
</types>
<fields>
$FieldDefinitions
</fields>
$CopyFieldDefinitions
<uniqueKey>_documentid</uniqueKey>
<defaultSearchField>_text</defaultSearchField>
<solrQueryParser defaultOperator="OR"/>
</schema>

349
conf/templates/types.ss Normal file
View File

@ -0,0 +1,349 @@
<!-- The StrField type is not analyzed, but indexed/stored verbatim. -->
<fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
<!-- boolean type: "true" or "false" -->
<fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
<!--Binary data type. The data should be sent/retrieved in as Base64 encoded Strings -->
<fieldtype name="binary" class="solr.BinaryField"/>
<!-- The optional sortMissingLast and sortMissingFirst attributes are
currently supported on types that are sorted internally as strings.
This includes "string","boolean","sint","slong","sfloat","sdouble","pdate"
- If sortMissingLast="true", then a sort on this field will cause documents
without the field to come after documents with the field,
regardless of the requested sort order (asc or desc).
- If sortMissingFirst="true", then a sort on this field will cause documents
without the field to come before documents with the field,
regardless of the requested sort order.
- If sortMissingLast="false" and sortMissingFirst="false" (the default),
then default lucene sorting will be used which places docs without the
field first in an ascending sort and last in a descending sort.
-->
<!--
Default numeric field types. For faster range queries, consider the tint/tfloat/tlong/tdouble types.
-->
<fieldType name="int" class="solr.TrieIntField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="float" class="solr.TrieFloatField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
<!--
Numeric field types that index each value at various levels of precision
to accelerate range queries when the number of values between the range
endpoints is large. See the javadoc for NumericRangeQuery for internal
implementation details.
Smaller precisionStep values (specified in bits) will lead to more tokens
indexed per value, slightly larger index size, and faster range queries.
A precisionStep of 0 disables indexing at different precision levels.
-->
<fieldType name="tint" class="solr.TrieIntField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
<!-- The format for this date field is of the form 1995-12-31T23:59:59Z, and
is a more restricted form of the canonical representation of dateTime
http://www.w3.org/TR/xmlschema-2/#dateTime
The trailing "Z" designates UTC time and is mandatory.
Optional fractional seconds are allowed: 1995-12-31T23:59:59.999Z
All other components are mandatory.
Expressions can also be used to denote calculations that should be
performed relative to "NOW" to determine the value, ie...
NOW/HOUR
... Round to the start of the current hour
NOW-1DAY
... Exactly 1 day prior to now
NOW/DAY+6MONTHS+3DAYS
... 6 months and 3 days in the future from the start of
the current day
Consult the DateField javadocs for more information.
Note: For faster range queries, consider the tdate type
-->
<fieldType name="date" class="solr.TrieDateField" omitNorms="true" precisionStep="0" positionIncrementGap="0"/>
<!-- A Trie based date field for faster date range queries and date faceting. -->
<fieldType name="tdate" class="solr.TrieDateField" omitNorms="true" precisionStep="6" positionIncrementGap="0"/>
<!-- The "RandomSortField" is not used to store or search any
data. You can declare fields of this type it in your schema
to generate pseudo-random orderings of your docs for sorting
purposes. The ordering is generated based on the field name
and the version of the index, As long as the index version
remains unchanged, and the same field name is reused,
the ordering of the docs will be consistent.
If you want different psuedo-random orderings of documents,
for the same version of the index, use a dynamicField and
change the name
-->
<fieldType name="random" class="solr.RandomSortField" indexed="true" />
<!-- solr.TextField allows the specification of custom text analyzers
specified as a tokenizer and a list of token filters. Different
analyzers may be specified for indexing and querying.
The optional positionIncrementGap puts space between multiple fields of
this type on the same document, with the purpose of preventing false phrase
matching across fields.
For more info on customizing your analyzer chain, please see
http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters
-->
<!-- One can also specify an existing Analyzer class that has a
default constructor via the class attribute on the analyzer element
<fieldType name="text_greek" class="solr.TextField">
<analyzer class="org.apache.lucene.analysis.el.GreekAnalyzer"/>
</fieldType>
-->
<!-- A text field that only splits on whitespace for exact matching of words -->
<fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
</analyzer>
</fieldType>
<!-- A text field that uses WordDelimiterFilter to enable splitting and matching of
words on case-change, alpha numeric boundaries, and non-alphanumeric chars,
so that a query of "wifi" or "wi fi" could match a document containing "Wi-Fi".
Synonyms and stopwords are customized by external files, and stemming is enabled.
The attribute autoGeneratePhraseQueries="true" (the default) causes words that get split to
form phrase queries. For example, WordDelimiterFilter splitting text:pdp-11 will cause the parser
to generate text:"pdp 11" rather than (text:PDP OR text:11).
NOTE: autoGeneratePhraseQueries="true" tends to not work well for non whitespace delimited languages.
-->
<fieldType name="text" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="true">
<analyzer type="index">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<!-- in this example, we will only use synonyms at query time
<filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
-->
<!-- Case insensitive stop word removal.
add enablePositionIncrements=true in both the index and query
analyzers to leave a 'gap' for more accurate phrase queries.
-->
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="stopwords.txt"
enablePositionIncrements="true"
/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="stopwords.txt"
enablePositionIncrements="true"
/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
</fieldType>
<!-- A copy of text that has the HTMLStripCharFilterFactory as the first index analyzer, so that html can be provided -->
<fieldType name="htmltext" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="true">
<analyzer type="index">
<charFilter class="solr.HTMLStripCharFilterFactory"/>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
</fieldType>
<!-- Less flexible matching, but less false matches. Probably not ideal for product names,
but may be good for SKUs. Can insert dashes in the wrong place and still match. -->
<fieldType name="textTight" class="solr.TextField" positionIncrementGap="100" >
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.EnglishMinimalStemFilterFactory"/>
<!-- this filter can remove any duplicate tokens that appear at the same position - sometimes
possible with WordDelimiterFilter in conjuncton with stemming. -->
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
</analyzer>
</fieldType>
<!-- A general unstemmed text field - good if one does not know the language of the field -->
<fieldType name="textgen" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="0"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="stopwords.txt"
enablePositionIncrements="true"
/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="0"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<!-- A general unstemmed text field that indexes tokens normally and also
reversed (via ReversedWildcardFilterFactory), to enable more efficient
leading wildcard queries. -->
<fieldType name="text_rev" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="0"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.ReversedWildcardFilterFactory" withOriginal="true"
maxPosAsterisk="3" maxPosQuestion="2" maxFractionAsterisk="0.33"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="stopwords.txt"
enablePositionIncrements="true"
/>
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="0"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<!-- charFilter + WhitespaceTokenizer -->
<!--
<fieldType name="textCharNorm" class="solr.TextField" positionIncrementGap="100" >
<analyzer>
<charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
</analyzer>
</fieldType>
-->
<!-- This is an example of using the KeywordTokenizer along
With various TokenFilterFactories to produce a sortable field
that does not include some properties of the source text
-->
<fieldType name="alphaOnlySort" class="solr.TextField" sortMissingLast="true" omitNorms="true">
<analyzer>
<!-- KeywordTokenizer does no actual tokenizing, so the entire
input string is preserved as a single token
-->
<tokenizer class="solr.KeywordTokenizerFactory"/>
<!-- The LowerCase TokenFilter does what you expect, which can be
when you want your sorting to be case insensitive
-->
<filter class="solr.LowerCaseFilterFactory" />
<!-- The TrimFilter removes any leading or trailing whitespace -->
<filter class="solr.TrimFilterFactory" />
<!-- The PatternReplaceFilter gives you the flexibility to use
Java Regular expression to replace any sequence of characters
matching a pattern with an arbitrary replacement string,
which may include back references to portions of the original
string matched by the pattern.
See the Java Regular Expression documentation for more
information on pattern and replacement string syntax.
http://java.sun.com/j2se/1.5.0/docs/api/java/util/regex/package-summary.html
-->
<filter class="solr.PatternReplaceFilterFactory"
pattern="([^a-z])" replacement="" replace="all"
/>
</analyzer>
</fieldType>
<fieldtype name="phonetic" stored="false" indexed="true" class="solr.TextField" >
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.DoubleMetaphoneFilterFactory" inject="false"/>
</analyzer>
</fieldtype>
<fieldtype name="payloads" stored="false" indexed="true" class="solr.TextField" >
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<!--
The DelimitedPayloadTokenFilter can put payloads on tokens... for example,
a token of "foo|1.4" would be indexed as "foo" with a payload of 1.4f
Attributes of the DelimitedPayloadTokenFilterFactory :
"delimiter" - a one character delimiter. Default is | (pipe)
"encoder" - how to encode the following value into a playload
float -> org.apache.lucene.analysis.payloads.FloatEncoder,
integer -> o.a.l.a.p.IntegerEncoder
identity -> o.a.l.a.p.IdentityEncoder
Fully Qualified class name implementing PayloadEncoder, Encoder must have a no arg constructor.
-->
<filter class="solr.DelimitedPayloadTokenFilterFactory" encoder="float"/>
</analyzer>
</fieldtype>
<!-- lowercases the entire field value, keeping it as a single token. -->
<fieldType name="lowercase" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.KeywordTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory" />
</analyzer>
</fieldType>
<fieldType name="text_path" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.PathHierarchyTokenizerFactory"/>
</analyzer>
</fieldType>
<!-- since fields of this type are by default not stored or indexed,
any data added to them will be ignored outright. -->
<fieldtype name="ignored" stored="false" indexed="false" multiValued="true" class="solr.StrField" />
<!-- This point type indexes the coordinates as separate fields (subFields)
If subFieldType is defined, it references a type, and a dynamic field
definition is created matching *___<typename>. Alternately, if
subFieldSuffix is defined, that is used to create the subFields.
Example: if subFieldType="double", then the coordinates would be
indexed in fields myloc_0___double,myloc_1___double.
Example: if subFieldSuffix="_d" then the coordinates would be indexed
in fields myloc_0_d,myloc_1_d
The subFields are an implementation detail of the fieldType, and end
users normally should not need to know about them.
-->
<fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
<!-- A specialized field for geospatial search. If indexed, this fieldType must not be multivalued. -->
<fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
<!--
A Geohash is a compact representation of a latitude longitude pair in a single field.
See http://wiki.apache.org/solr/SpatialSearch
-->
<fieldtype name="geohash" class="solr.GeoHashField"/>

95
docs/README.md Normal file
View File

@ -0,0 +1,95 @@
# WARNING: Heavily experimental API. Likely to change without notice.
# FullTextSearch module
## Maintainer Contact
* Hamish Friedlander <hamish (at) silverstripe (dot) com>
## Requirements
* SilverStripe 2.4. Untested in 3 alpha, but probably won't work.
## Introduction
This is a module aimed at adding support for standalone fulltext search engines to SilverStripe.
It contains several layers.
* A fulltext API, ignoring the actual provision of fulltext searching
* A connector API, providing common code to allow connecting a fulltext searching engine to the fulltext API, and
* Some connectors for common fulltext searching engines.
## Reasoning
There are several fulltext search engines that work in a similar manner. They build indexes of denormalized data that
is then searched through using some custom query syntax.
Traditionally, fulltext search connectors for SilverStripe have attempted to hide this design, instead presenting
fulltext searching as an extension of the object model. However the disconnect between the fulltext search engine's
design and the object model meant that searching was inefficient. The abstraction would also often break and it was
hard to then figure out what was going on.
Instead this module provides the ability to define those indexes and queries in PHP. The indexes are defined as a mapping
between the object model and the index model. This module then interogates model metadata to build the specific index
definition. It also hooks into Sapphire in order to update the indexes when the models change.
Connectors then convert those index and query definitions into fulltext engine specific code.
The intent of this module is not to make changing fulltext search engines seamless. Where possible this module provides
common interfaces to fulltext engine functionality, abstracting out common behaviour. However, each connector also
offers it's own extensions, and there is some behaviour (such as getting the fulltext search engines installed, configured
and running) that each connector deals with itself, in a way best suited to the specific fulltext search engines design.
## Basic usage
Basic usage is a four step process
1) Define an index (Note the specific connector index instance - that's what defines which engine gets used)
```php
mysite/code/MyIndex.php:
<?php
class MyIndex extends SolrIndex {
function init() {
$this->addClass('Page');
$this->addFulltextField('DocumentContents');
}
}
```
2) Add something to the index (adding existing objects to the index is connector specific)
```php
$page = new Page(array('Contents' => 'Help me. My house is on fire. This is less than optimal.'));
$page->write();
```
3) Build a query
```php
$query = new SearchQuery();
$query->search('My house is on fire');
```
4) Apply that query to an index
```php
singleton('MyIndex')->search($query);
```
Note that for most connectors, changes won't be searchable until _after_ the request that triggered the change.
## Connectors
### Solr
See Solr.md
### Sphinx
Not written yet

58
docs/Solr.md Normal file
View File

@ -0,0 +1,58 @@
# Solr connector for SilverStripe fulltextsearch
This module provides a fulltextsearch module connector to Solr.
It works with Solr in multi-core mode. It needs to be able to update Solr configuration files, and has modes for
doing this by direct file access (when Solr shares a server with SilverStripe) and by WebDAV (when it's on a different server).
Since Solr is Java based, this module requires a Java runtime to be present on the server Solr is running on (not nessecarily
the same server the SilverStripe server is on).
## Getting started quickly (dev mode)
Configure Solr in file mode
```php
mysite/_config.php:
<?php
Solr::configure_server(isset($solr_config) ? $solr_config : array(
'host' => 'localhost',
'indexstore' => array(
'mode' => 'file',
'path' => BASE_PATH . '/fulltextsearch/thirdparty/solr/server/solr'
)
));
```
Create an index
```php
mysite/code/MyIndex.php:
<?php
class MyIndex extends SolrIndex {
function init() {
$this->addClass('Page');
$this->addAllFulltextFields();
}
}
```
Open a terminal, change to thirdparty/solr/server and start Solr by running `java -jar start.jar`
In another terminal run the configure task `sake dev/tasks/Solr_configure`
Tne run the configure task `sake dev/tasks/Solr_reindex`
You can now visit http://localhost:8983/solr/MyIndex/admin/ to search the contents of the now created Solr index
-----
These instructions will get you running quickly, but the Solr indexes will be stored in your project. You can also
copy the thirdparty/solr directory somewhere else. The instructions are above still apply then - just set the path value
in mysite/_config.php to point to this other location, and of course run `java -jar start.jar` from the new directory
not the thirdparty one.

173
tests/SearchUpdaterTest.php Normal file
View File

@ -0,0 +1,173 @@
<?php
class SearchUpdaterTest_Container extends DataObject {
static $db = array(
'Field1' => 'Varchar',
'Field2' => 'Varchar'
);
static $has_one = array(
'HasOneObject' => 'SearchUpdaterTest_HasOne'
);
static $has_many = array(
'HasManyObjects' => 'SearchUpdaterTest_HasMany'
);
}
class SearchUpdaterTest_HasOne extends DataObject {
static $db = array(
'Field1' => 'Varchar',
'Field2' => 'Varchar'
);
static $has_many = array(
'HasManyContainers' => 'SearchUpdaterTest_Container'
);
}
class SearchUpdaterTest_HasMany extends DataObject {
static $db = array(
'Field1' => 'Varchar',
'Field2' => 'Varchar'
);
static $has_one = array(
'HasManyContainer' => 'SearchUpdaterTest_Container'
);
}
class SearchUpdaterTest_Index extends SearchIndex_Recording {
function init() {
$this->addClass('SearchUpdaterTest_Container');
$this->addFilterField('Field1');
$this->addFilterField('HasOneObject.Field1');
$this->addFilterField('HasManyObjects.Field1');
}
}
class SearchUpdaterTest extends SapphireTest {
private static $index = null;
function setUp() {
parent::setUp();
if (self::$index === null) self::$index = singleton(get_class($this).'_Index');
else self::$index->reset();
FullTextSearch::force_index_list(self::$index);
SearchUpdater::clear_dirty_indexes();
}
function testBasic() {
$item = new SearchUpdaterTest_Container();
$item->write();
// TODO: Make sure changing field1 updates item.
// TODO: Get updating just field2 to not update item (maybe not possible - variants complicate)
}
function testHasOneHook() {
$hasOne = new SearchUpdaterTest_HasOne();
$hasOne->write();
$alternateHasOne = new SearchUpdaterTest_HasOne();
$alternateHasOne->write();
$container1 = new SearchUpdaterTest_Container();
$container1->HasOneObjectID = $hasOne->ID;
$container1->write();
$container2 = new SearchUpdaterTest_Container();
$container2->HasOneObjectID = $hasOne->ID;
$container2->write();
$container3 = new SearchUpdaterTest_Container();
$container3->HasOneObjectID = $alternateHasOne->ID;
$container3->write();
// Check the default "writing a document updates the document"
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID')), array(
array('ID' => $container1->ID),
array('ID' => $container2->ID),
array('ID' => $container3->ID)
));
// Check writing a has_one tracks back to the origin documents
self::$index->reset();
$hasOne->Field1 = "Updated";
$hasOne->write();
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID')), array(
array('ID' => $container1->ID),
array('ID' => $container2->ID)
));
// Check updating an unrelated field doesn't track back
self::$index->reset();
$hasOne->Field2 = "Updated";
$hasOne->write();
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID')), array());
// Check writing a has_one tracks back to the origin documents
self::$index->reset();
$alternateHasOne->Field1= "Updated";
$alternateHasOne->write();
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID')), array(
array('ID' => $container3->ID)
));
}
function testHasManyHook() {
$container1 = new SearchUpdaterTest_Container();
$container1->write();
$container2 = new SearchUpdaterTest_Container();
$container2->write();
//self::$index->reset();
//SearchUpdater::clear_dirty_indexes();
$hasMany1 = new SearchUpdaterTest_HasMany();
$hasMany1->HasManyContainerID = $container1->ID;
$hasMany1->write();
$hasMany2 = new SearchUpdaterTest_HasMany();
$hasMany2->HasManyContainerID = $container1->ID;
$hasMany2->write();
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID')), array(
array('ID' => $container1->ID),
array('ID' => $container2->ID)
));
self::$index->reset();
$hasMany1->Field1 = 'Updated';
$hasMany1->write();
$hasMany2->Field1 = 'Updated';
$hasMany2->write();
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID')), array(
array('ID' => $container1->ID)
));
}
}

View File

@ -0,0 +1,71 @@
<?php
class SearchVariantSiteTreeSubsitesPolyhomeTest_Item extends SiteTree {
// TODO: Currently theres a failure if you addClass a non-table class
static $db = array(
'TestText' => 'Varchar'
);
}
class SearchVariantSiteTreeSubsitesPolyhomeTest_Index extends SearchIndex_Recording {
function init() {
$this->addClass('SearchVariantSiteTreeSubsitesPolyhomeTest_Item');
$this->addFilterField('TestText');
}
}
class SearchVariantSiteTreeSubsitesPolyhomeTest extends SapphireTest {
private static $index = null;
private static $subsite_a = null;
private static $subsite_b = null;
function setUp() {
parent::setUp();
if (self::$index === null) self::$index = singleton('SearchVariantSiteTreeSubsitesPolyhomeTest_Index');
if (self::$subsite_a === null) {
self::$subsite_a = new Subsite(); self::$subsite_a->write();
self::$subsite_b = new Subsite(); self::$subsite_b->write();
}
FullTextSearch::force_index_list(self::$index);
SearchUpdater::clear_dirty_indexes();
}
function testSavingDirect() {
// Initial add
$item = new SearchVariantSiteTreeSubsitesPolyhomeTest_Item();
$item->write();
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID', '_subsite')), array(
array('ID' => $item->ID, '_subsite' => 0)
));
// Check that adding to subsites works
self::$index->reset();
$item->setField('AddToSubsite[0]', 1);
$item->setField('AddToSubsite['.(self::$subsite_a->ID).']', 1);
$item->write();
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID', '_subsite')), array(
array('ID' => $item->ID, '_subsite' => 0),
array('ID' => $item->ID, '_subsite' => self::$subsite_a->ID)
));
$this->assertEquals(self::$index->deleted, array(
array('base' => 'SiteTree', 'id' => $item->ID, 'state' => array(
'SearchVariantVersioned' => 'Stage', 'SearchVariantSiteTreeSubsitesPolyhome' => self::$subsite_b->ID
))
));
}
}

View File

@ -0,0 +1,64 @@
<?php
class SearchVariantVersionedTest_Item extends SiteTree {
// TODO: Currently theres a failure if you addClass a non-table class
static $db = array(
'TestText' => 'Varchar'
);
}
class SearchVariantVersionedTest_Index extends SearchIndex_Recording {
function init() {
$this->addClass('SearchVariantVersionedTest_Item');
$this->addFilterField('TestText');
}
}
class SearchVariantVersionedTest extends SapphireTest {
private static $index = null;
function setUp() {
parent::setUp();
if (self::$index === null) self::$index = singleton('SearchVariantVersionedTest_Index');
FullTextSearch::force_index_list(self::$index);
SearchUpdater::clear_dirty_indexes();
}
function testPublishing() {
// Check that write updates Stage
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
$item->write();
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID', '_versionedstage')), array(
array('ID' => $item->ID, '_versionedstage' => 'Stage')
));
// Check that publish updates Live
self::$index->reset();
$item->publish("Stage", "Live");
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID', '_versionedstage')), array(
array('ID' => $item->ID, '_versionedstage' => 'Live')
));
// Just update a SiteTree field, and check it updates Stage
self::$index->reset();
$item->Title = "Pow!";
$item->write();
SearchUpdater::flush_dirty_indexes();
$this->assertEquals(self::$index->getAdded(array('ID', '_versionedstage')), array(
array('ID' => $item->ID, '_versionedstage' => 'Stage')
));
}
}

0
thirdparty/_manifest_exclude vendored Normal file
View File

View File

@ -0,0 +1,8 @@
---
format: 1
handler:
piston:remote-revision: 60
piston:uuid: e73431f4-bb50-11dd-afb1-a7db9ebf18e3
lock: false
repository_class: Piston::Svn::Repository
repository_url: http://solr-php-client.googlecode.com/svn/trunk/

View File

@ -0,0 +1,367 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: Document.php 54 2011-02-04 16:29:18Z donovan.jimenez $
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Holds Key / Value pairs that represent a Solr Document along with any associated boost
* values. Field values can be accessed by direct dereferencing such as:
* <code>
* ...
* $document->title = 'Something';
* echo $document->title;
* ...
* </code>
*
* Additionally, the field values can be iterated with foreach
*
* <code>
* foreach ($document as $fieldName => $fieldValue)
* {
* ...
* }
* </code>
*/
class Apache_Solr_Document implements IteratorAggregate
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision: 54 $';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id: Document.php 54 2011-02-04 16:29:18Z donovan.jimenez $';
/**
* Document boost value
*
* @var float
*/
protected $_documentBoost = false;
/**
* Document field values, indexed by name
*
* @var array
*/
protected $_fields = array();
/**
* Document field boost values, indexed by name
*
* @var array array of floats
*/
protected $_fieldBoosts = array();
/**
* Clear all boosts and fields from this document
*/
public function clear()
{
$this->_documentBoost = false;
$this->_fields = array();
$this->_fieldBoosts = array();
}
/**
* Get current document boost
*
* @return mixed will be false for default, or else a float
*/
public function getBoost()
{
return $this->_documentBoost;
}
/**
* Set document boost factor
*
* @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false
*/
public function setBoost($boost)
{
$boost = (float) $boost;
if ($boost > 0.0)
{
$this->_documentBoost = $boost;
}
else
{
$this->_documentBoost = false;
}
}
/**
* Add a value to a multi-valued field
*
* NOTE: the solr XML format allows you to specify boosts
* PER value even though the underlying Lucene implementation
* only allows a boost per field. To remedy this, the final
* field boost value will be the product of all specified boosts
* on field values - this is similar to SolrJ's functionality.
*
* <code>
* $doc = new Apache_Solr_Document();
*
* $doc->addField('foo', 'bar', 2.0);
* $doc->addField('foo', 'baz', 3.0);
*
* // resultant field boost will be 6!
* echo $doc->getFieldBoost('foo');
* </code>
*
* @param string $key
* @param mixed $value
* @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false
*/
public function addField($key, $value, $boost = false)
{
if (!isset($this->_fields[$key]))
{
// create holding array if this is the first value
$this->_fields[$key] = array();
}
else if (!is_array($this->_fields[$key]))
{
// move existing value into array if it is not already an array
$this->_fields[$key] = array($this->_fields[$key]);
}
if ($this->getFieldBoost($key) === false)
{
// boost not already set, set it now
$this->setFieldBoost($key, $boost);
}
else if ((float) $boost > 0.0)
{
// multiply passed boost with current field boost - similar to SolrJ implementation
$this->_fieldBoosts[$key] *= (float) $boost;
}
// add value to array
$this->_fields[$key][] = $value;
}
/**
* Handle the array manipulation for a multi-valued field
*
* @param string $key
* @param string $value
* @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false
*
* @deprecated Use addField(...) instead
*/
public function setMultiValue($key, $value, $boost = false)
{
$this->addField($key, $value, $boost);
}
/**
* Get field information
*
* @param string $key
* @return mixed associative array of info if field exists, false otherwise
*/
public function getField($key)
{
if (isset($this->_fields[$key]))
{
return array(
'name' => $key,
'value' => $this->_fields[$key],
'boost' => $this->getFieldBoost($key)
);
}
return false;
}
/**
* Set a field value. Multi-valued fields should be set as arrays
* or instead use the addField(...) function which will automatically
* make sure the field is an array.
*
* @param string $key
* @param mixed $value
* @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false
*/
public function setField($key, $value, $boost = false)
{
$this->_fields[$key] = $value;
$this->setFieldBoost($key, $boost);
}
/**
* Get the currently set field boost for a document field
*
* @param string $key
* @return float currently set field boost, false if one is not set
*/
public function getFieldBoost($key)
{
return isset($this->_fieldBoosts[$key]) ? $this->_fieldBoosts[$key] : false;
}
/**
* Set the field boost for a document field
*
* @param string $key field name for the boost
* @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false
*/
public function setFieldBoost($key, $boost)
{
$boost = (float) $boost;
if ($boost > 0.0)
{
$this->_fieldBoosts[$key] = $boost;
}
else
{
$this->_fieldBoosts[$key] = false;
}
}
/**
* Return current field boosts, indexed by field name
*
* @return array
*/
public function getFieldBoosts()
{
return $this->_fieldBoosts;
}
/**
* Get the names of all fields in this document
*
* @return array
*/
public function getFieldNames()
{
return array_keys($this->_fields);
}
/**
* Get the values of all fields in this document
*
* @return array
*/
public function getFieldValues()
{
return array_values($this->_fields);
}
/**
* IteratorAggregate implementation function. Allows usage:
*
* <code>
* foreach ($document as $key => $value)
* {
* ...
* }
* </code>
*/
public function getIterator()
{
$arrayObject = new ArrayObject($this->_fields);
return $arrayObject->getIterator();
}
/**
* Magic get for field values
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
if (isset($this->_fields[$key]))
{
return $this->_fields[$key];
}
return null;
}
/**
* Magic set for field values. Multi-valued fields should be set as arrays
* or instead use the addField(...) function which will automatically
* make sure the field is an array.
*
* @param string $key
* @param mixed $value
*/
public function __set($key, $value)
{
$this->setField($key, $value);
}
/**
* Magic isset for fields values. Do not call directly. Allows usage:
*
* <code>
* isset($document->some_field);
* </code>
*
* @param string $key
* @return boolean
*/
public function __isset($key)
{
return isset($this->_fields[$key]);
}
/**
* Magic unset for field values. Do not call directly. Allows usage:
*
* <code>
* unset($document->some_field);
* </code>
*
* @param string $key
*/
public function __unset($key)
{
unset($this->_fields[$key]);
unset($this->_fieldBoosts[$key]);
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: Exception.php 54 2011-02-04 16:29:18Z donovan.jimenez $
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
class Apache_Solr_Exception extends Exception
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision: 54 $';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id: Exception.php 54 2011-02-04 16:29:18Z donovan.jimenez $';
}

View File

@ -0,0 +1,89 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: $
*
* @package Apache
* @subpackage Solr
* @author Timo Schmidt <timo.schmidt@aoemedia.de>, Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Convenience class that implements the transport implementation. Can be extended by
* real implementations to do some of the common book keeping
*/
abstract class Apache_Solr_HttpTransport_Abstract implements Apache_Solr_HttpTransport_Interface
{
/**
* Our default timeout value for requests that don't specify a timeout
*
* @var float
*/
private $_defaultTimeout = false;
/**
* Get the current default timeout setting (initially the default_socket_timeout ini setting)
* in seconds
*
* @return float
*/
public function getDefaultTimeout()
{
// lazy load the default timeout from the ini settings
if ($this->_defaultTimeout === false)
{
$this->_defaultTimeout = (int) ini_get('default_socket_timeout');
// double check we didn't get 0 for a timeout
if ($this->_defaultTimeout <= 0)
{
$this->_defaultTimeout = 60;
}
}
return $this->_defaultTimeout;
}
/**
* Set the current default timeout for all HTTP requests
*
* @param float $timeout
*/
public function setDefaultTimeout($timeout)
{
$timeout = (float) $timeout;
if ($timeout >= 0)
{
$this->_defaultTimeout = $timeout;
}
}
}

View File

@ -0,0 +1,198 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: $
*
* @package Apache
* @subpackage Solr
* @author Timo Schmidt <timo.schmidt@aoemedia.de>, Donovan Jimenez <djimenez@conduit-it.com>
*/
// Require Apache_Solr_HttpTransport_Abstract
require_once(dirname(__FILE__) . '/Abstract.php');
/**
* A Curl based HTTP transport. Uses a single curl session for all requests.
*/
class Apache_Solr_HttpTransport_Curl extends Apache_Solr_HttpTransport_Abstract
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision:$';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id:$';
/**
* Curl Session Handle
*
* @var resource
*/
private $_curl;
/**
* Initializes a curl session
*/
public function __construct()
{
// initialize a CURL session
$this->_curl = curl_init();
// set common options that will not be changed during the session
curl_setopt_array($this->_curl, array(
// return the response body from curl_exec
CURLOPT_RETURNTRANSFER => true,
// get the output as binary data
CURLOPT_BINARYTRANSFER => true,
// we do not need the headers in the output, we get everything we need from curl_getinfo
CURLOPT_HEADER => false
));
}
/**
* Closes a curl session
*/
function __destruct()
{
// close our curl session
curl_close($this->_curl);
}
public function performGetRequest($url, $timeout = false)
{
// check the timeout value
if ($timeout === false || $timeout <= 0.0)
{
// use the default timeout
$timeout = $this->getDefaultTimeout();
}
// set curl GET options
curl_setopt_array($this->_curl, array(
// make sure we're returning the body
CURLOPT_NOBODY => false,
// make sure we're GET
CURLOPT_HTTPGET => true,
// set the URL
CURLOPT_URL => $url,
// set the timeout
CURLOPT_TIMEOUT => $timeout
));
// make the request
$responseBody = curl_exec($this->_curl);
// get info from the transfer
$statusCode = curl_getinfo($this->_curl, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($this->_curl, CURLINFO_CONTENT_TYPE);
return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody);
}
public function performHeadRequest($url, $timeout = false)
{
// check the timeout value
if ($timeout === false || $timeout <= 0.0)
{
// use the default timeout
$timeout = $this->getDefaultTimeout();
}
// set curl HEAD options
curl_setopt_array($this->_curl, array(
// this both sets the method to HEAD and says not to return a body
CURLOPT_NOBODY => true,
// set the URL
CURLOPT_URL => $url,
// set the timeout
CURLOPT_TIMEOUT => $timeout
));
// make the request
$responseBody = curl_exec($this->_curl);
// get info from the transfer
$statusCode = curl_getinfo($this->_curl, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($this->_curl, CURLINFO_CONTENT_TYPE);
return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody);
}
public function performPostRequest($url, $postData, $contentType, $timeout = false)
{
// check the timeout value
if ($timeout === false || $timeout <= 0.0)
{
// use the default timeout
$timeout = $this->getDefaultTimeout();
}
// set curl POST options
curl_setopt_array($this->_curl, array(
// make sure we're returning the body
CURLOPT_NOBODY => false,
// make sure we're POST
CURLOPT_POST => true,
// set the URL
CURLOPT_URL => $url,
// set the post data
CURLOPT_POSTFIELDS => $postData,
// set the content type
CURLOPT_HTTPHEADER => array("Content-Type: {$contentType}"),
// set the timeout
CURLOPT_TIMEOUT => $timeout
));
// make the request
$responseBody = curl_exec($this->_curl);
// get info from the transfer
$statusCode = curl_getinfo($this->_curl, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($this->_curl, CURLINFO_CONTENT_TYPE);
return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody);
}
}

View File

@ -0,0 +1,196 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: $
*
* @package Apache
* @subpackage Solr
* @author Timo Schmidt <timo.schmidt@aoemedia.de>, Donovan Jimenez <djimenez@conduit-it.com>
*/
// Require Apache_Solr_HttpTransport_Abstract
require_once(dirname(__FILE__) . '/Abstract.php');
/**
* An alternative Curl HTTP transport that opens and closes a curl session for
* every request. This isn't the recommended way to use curl, but some version of
* PHP have memory issues.
*/
class Apache_Solr_HttpTransport_CurlNoReuse extends Apache_Solr_HttpTransport_Abstract
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision:$';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id:$';
public function performGetRequest($url, $timeout = false)
{
// check the timeout value
if ($timeout === false || $timeout <= 0.0)
{
// use the default timeout
$timeout = $this->getDefaultTimeout();
}
$curl = curl_init();
// set curl GET options
curl_setopt_array($curl, array(
// return the response body from curl_exec
CURLOPT_RETURNTRANSFER => true,
// get the output as binary data
CURLOPT_BINARYTRANSFER => true,
// we do not need the headers in the output, we get everything we need from curl_getinfo
CURLOPT_HEADER => false,
// set the URL
CURLOPT_URL => $url,
// set the timeout
CURLOPT_TIMEOUT => $timeout
));
// make the request
$responseBody = curl_exec($curl);
// get info from the transfer
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
// close our curl session - we're done with it
curl_close($curl);
return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody);
}
public function performHeadRequest($url, $timeout = false)
{
// check the timeout value
if ($timeout === false || $timeout <= 0.0)
{
// use the default timeout
$timeout = $this->getDefaultTimeout();
}
$curl = curl_init();
// set curl HEAD options
curl_setopt_array($curl, array(
// return the response body from curl_exec
CURLOPT_RETURNTRANSFER => true,
// get the output as binary data
CURLOPT_BINARYTRANSFER => true,
// we do not need the headers in the output, we get everything we need from curl_getinfo
CURLOPT_HEADER => false,
// this both sets the method to HEAD and says not to return a body
CURLOPT_NOBODY => true,
// set the URL
CURLOPT_URL => $url,
// set the timeout
CURLOPT_TIMEOUT => $timeout
));
// make the request
$responseBody = curl_exec($curl);
// get info from the transfer
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
// close our curl session - we're done with it
curl_close($curl);
return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody);
}
public function performPostRequest($url, $postData, $contentType, $timeout = false)
{
// check the timeout value
if ($timeout === false || $timeout <= 0.0)
{
// use the default timeout
$timeout = $this->getDefaultTimeout();
}
$curl = curl_init();
// set curl POST options
curl_setopt_array($curl, array(
// return the response body from curl_exec
CURLOPT_RETURNTRANSFER => true,
// get the output as binary data
CURLOPT_BINARYTRANSFER => true,
// we do not need the headers in the output, we get everything we need from curl_getinfo
CURLOPT_HEADER => false,
// make sure we're POST
CURLOPT_POST => true,
// set the URL
CURLOPT_URL => $url,
// set the post data
CURLOPT_POSTFIELDS => $postData,
// set the content type
CURLOPT_HTTPHEADER => array("Content-Type: {$contentType}"),
// set the timeout
CURLOPT_TIMEOUT => $timeout
));
// make the request
$responseBody = curl_exec($curl);
// get info from the transfer
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
// close our curl session - we're done with it
curl_close($curl);
return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody);
}
}

View File

@ -0,0 +1,216 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: $
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
// Require Apache_Solr_HttpTransport_Abstract
require_once(dirname(__FILE__) . '/Abstract.php');
/**
* HTTP Transport implemenation that uses the builtin http URL wrappers and file_get_contents
*/
class Apache_Solr_HttpTransport_FileGetContents extends Apache_Solr_HttpTransport_Abstract
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision: $';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id: $';
/**
* Reusable stream context resources for GET and POST operations
*
* @var resource
*/
private $_getContext, $_headContext, $_postContext;
/**
* Initializes our reuseable get and post stream contexts
*/
public function __construct()
{
$this->_getContext = stream_context_create();
$this->_headContext = stream_context_create();
$this->_postContext = stream_context_create();
}
public function performGetRequest($url, $timeout = false)
{
// set the timeout if specified
if ($timeout !== FALSE && $timeout > 0.0)
{
// timeouts with file_get_contents seem to need
// to be halved to work as expected
$timeout = (float) $timeout / 2;
stream_context_set_option($this->_getContext, 'http', 'timeout', $timeout);
}
else
{
// use the default timeout pulled from default_socket_timeout otherwise
stream_context_set_option($this->_getContext, 'http', 'timeout', $this->getDefaultTimeout());
}
// $http_response_headers will be updated by the call to file_get_contents later
// see http://us.php.net/manual/en/wrappers.http.php for documentation
// Unfortunately, it will still create a notice in analyzers if we don't set it here
$http_response_header = null;
$responseBody = @file_get_contents($url, false, $this->_getContext);
return $this->_getResponseFromParts($responseBody, $http_response_header);
}
public function performHeadRequest($url, $timeout = false)
{
stream_context_set_option($this->_headContext, array(
'http' => array(
// set HTTP method
'method' => 'HEAD',
// default timeout
'timeout' => $this->getDefaultTimeout()
)
)
);
// set the timeout if specified
if ($timeout !== FALSE && $timeout > 0.0)
{
// timeouts with file_get_contents seem to need
// to be halved to work as expected
$timeout = (float) $timeout / 2;
stream_context_set_option($this->_headContext, 'http', 'timeout', $timeout);
}
// $http_response_headers will be updated by the call to file_get_contents later
// see http://us.php.net/manual/en/wrappers.http.php for documentation
// Unfortunately, it will still create a notice in analyzers if we don't set it here
$http_response_header = null;
$responseBody = @file_get_contents($url, false, $this->_headContext);
return $this->_getResponseFromParts($responseBody, $http_response_header);
}
public function performPostRequest($url, $rawPost, $contentType, $timeout = false)
{
stream_context_set_option($this->_postContext, array(
'http' => array(
// set HTTP method
'method' => 'POST',
// Add our posted content type
'header' => "Content-Type: $contentType",
// the posted content
'content' => $rawPost,
// default timeout
'timeout' => $this->getDefaultTimeout()
)
)
);
// set the timeout if specified
if ($timeout !== FALSE && $timeout > 0.0)
{
// timeouts with file_get_contents seem to need
// to be halved to work as expected
$timeout = (float) $timeout / 2;
stream_context_set_option($this->_postContext, 'http', 'timeout', $timeout);
}
// $http_response_header will be updated by the call to file_get_contents later
// see http://us.php.net/manual/en/wrappers.http.php for documentation
// Unfortunately, it will still create a notice in analyzers if we don't set it here
$http_response_header = null;
$responseBody = @file_get_contents($url, false, $this->_postContext);
// reset content of post context to reclaim memory
stream_context_set_option($this->_postContext, 'http', 'content', '');
return $this->_getResponseFromParts($responseBody, $http_response_header);
}
private function _getResponseFromParts($rawResponse, $httpHeaders)
{
//Assume 0, false as defaults
$status = 0;
$contentType = false;
//iterate through headers for real status, type, and encoding
if (is_array($httpHeaders) && count($httpHeaders) > 0)
{
//look at the first headers for the HTTP status code
//and message (errors are usually returned this way)
//
//HTTP 100 Continue response can also be returned before
//the REAL status header, so we need look until we find
//the last header starting with HTTP
//
//the spec: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.1
//
//Thanks to Daniel Andersson for pointing out this oversight
while (isset($httpHeaders[0]) && substr($httpHeaders[0], 0, 4) == 'HTTP')
{
// we can do a intval on status line without the "HTTP/1.X " to get the code
$status = intval(substr($httpHeaders[0], 9));
// remove this from the headers so we can check for more
array_shift($httpHeaders);
}
//Look for the Content-Type response header and determine type
//and encoding from it (if possible - such as 'Content-Type: text/plain; charset=UTF-8')
foreach ($httpHeaders as $header)
{
// look for the header that starts appropriately
if (strncasecmp($header, 'Content-Type:', 13) == 0)
{
$contentType = substr($header, 13);
break;
}
}
}
return new Apache_Solr_HttpTransport_Response($status, $contentType, $rawResponse);
}
}

View File

@ -0,0 +1,94 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: $
*
* @package Apache
* @subpackage Solr
* @author Timo Schmidt <timo.schmidt@aoemedia.de>, Donovan Jimenez <djimenez@conduit-it.com>
*/
// require Apache_Solr_HttpTransport_Response
require_once(dirname(__FILE__) . '/Response.php');
/**
* Interface that all Transport (HTTP Requester) implementations must implement. These
* Implementations can then be plugged into the Service instance in order to user their
* the desired method for making HTTP requests
*/
interface Apache_Solr_HttpTransport_Interface
{
/**
* Get the current default timeout for all HTTP requests
*
* @return float
*/
public function getDefaultTimeout();
/**
* Set the current default timeout for all HTTP requests
*
* @param float $timeout
*/
public function setDefaultTimeout($timeout);
/**
* Perform a GET HTTP operation with an optional timeout and return the response
* contents, use getLastResponseHeaders to retrieve HTTP headers
*
* @param string $url
* @param float $timeout
* @return Apache_Solr_HttpTransport_Response HTTP response
*/
public function performGetRequest($url, $timeout = false);
/**
* Perform a HEAD HTTP operation with an optional timeout and return the response
* headers - NOTE: head requests have no response body
*
* @param string $url
* @param float $timeout
* @return Apache_Solr_HttpTransport_Response HTTP response
*/
public function performHeadRequest($url, $timeout = false);
/**
* Perform a POST HTTP operation with an optional timeout and return the response
* contents, use getLastResponseHeaders to retrieve HTTP headers
*
* @param string $url
* @param string $rawPost
* @param string $contentType
* @param float $timeout
* @return Apache_Solr_HttpTransport_Response HTTP response
*/
public function performPostRequest($url, $rawPost, $contentType, $timeout = false);
}

View File

@ -0,0 +1,255 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: $
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Represents the required pieces of an HTTP response provided by HTTP transport
* implementations and then consumed by the Apache_Solr_Response class which provides
* decoding
*/
class Apache_Solr_HttpTransport_Response
{
/**
* Status Messages indexed by Status Code
* Obtained from: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
*
* @var array
*/
static private $_defaultStatusMessages = array(
// Specific to PHP Solr Client
0 => "Communication Error",
// Informational 1XX
100 => "Continue",
101 => "Switching Protocols",
// Successful 2XX
200 => "OK",
201 => "Created",
202 => "Accepted",
203 => "Non-Authoritative Information",
204 => "No Content",
205 => "Reset Content",
206 => "Partial Content",
// Redirection 3XX
300 => "Multiple Choices",
301 => "Moved Permanently",
302 => "Found",
303 => "See Other",
304 => "Not Modified",
305 => "Use Proxy",
307 => "Temporary Redirect",
// Client Error 4XX
400 => "Bad Request",
401 => "Unauthorized",
402 => "Payment Required",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
406 => "Not Acceptable",
407 => "Proxy Authentication Required",
408 => "Request Timeout",
409 => "Conflict",
410 => "Gone",
411 => "Length Required",
412 => "Precondition Failed",
413 => "Request Entity Too Large",
414 => "Request-URI Too Long",
415 => "Unsupported Media Type",
416 => "Request Range Not Satisfiable",
417 => "Expectation Failed",
// Server Error 5XX
500 => "Internal Server Error",
501 => "Not Implemented",
502 => "Bad Gateway",
503 => "Service Unavailable",
504 => "Gateway Timeout",
505 => "HTTP Version Not Supported"
);
/**
* Get the HTTP status message based on status code
*
* @return string
*/
public static function getDefaultStatusMessage($statusCode)
{
$statusCode = (int) $statusCode;
if (isset(self::$_defaultStatusMessages[$statusCode]))
{
return self::$_defaultStatusMessages[$statusCode];
}
return "Unknown Status";
}
/**
* The response's HTTP status code
*
* @var integer
*/
private $_statusCode;
/**
* The response's HTTP status message
*
* @var string
*/
private $_statusMessage;
/**
* The response's mime type
*
* @var string
*/
private $_mimeType;
/**
* The response's character encoding
*
* @var string
*/
private $_encoding;
/**
* The response's data
*
* @var string
*/
private $_responseBody;
/**
* Construct a HTTP transport response
*
* @param integer $statusCode The HTTP status code
* @param string $contentType The VALUE of the Content-Type HTTP header
* @param string $responseBody The body of the HTTP response
*/
public function __construct($statusCode, $contentType, $responseBody)
{
// set the status code, make sure its an integer
$this->_statusCode = (int) $statusCode;
// lookup up status message based on code
$this->_statusMessage = self::getDefaultStatusMessage($this->_statusCode);
// set the response body, it should always be a string
$this->_responseBody = (string) $responseBody;
// parse the content type header value for mimetype and encoding
// first set default values that will remain if we can't find
// what we're looking for in the content type
$this->_mimeType = "text/plain";
$this->_encoding = "UTF-8";
if ($contentType)
{
// now break apart the header to see if there's character encoding
$contentTypeParts = explode(';', $contentType, 2);
if (isset($contentTypeParts[0]))
{
$this->_mimeType = trim($contentTypeParts[0]);
}
if (isset($contentTypeParts[1]))
{
// we have a second part, split it further
$contentTypeParts = explode('=', $contentTypeParts[1]);
if (isset($contentTypeParts[1]))
{
$this->_encoding = trim($contentTypeParts[1]);
}
}
}
}
/**
* Get the status code of the response
*
* @return integer
*/
public function getStatusCode()
{
return $this->_statusCode;
}
/**
* Get the status message of the response
*
* @return string
*/
public function getStatusMessage()
{
return $this->_statusMessage;
}
/**
* Get the mimetype of the response body
*
* @return string
*/
public function getMimeType()
{
return $this->_mimeType;
}
/**
* Get the charset encoding of the response body.
*
* @return string
*/
public function getEncoding()
{
return $this->_encoding;
}
/**
* Get the raw response body
*
* @return string
*/
public function getBody()
{
return $this->_responseBody;
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: HttpTransportException.php 54 2011-02-04 16:29:18Z donovan.jimenez $
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
class Apache_Solr_HttpTransportException extends Apache_Solr_Exception
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision: 54 $';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id: HttpTransportException.php 54 2011-02-04 16:29:18Z donovan.jimenez $';
/**
* Response for which exception was generated
*
* @var Apache_Solr_Response
*/
private $_response;
/**
* HttpTransportException Constructor
*
* @param Apache_Solr_Response $response
*/
public function __construct(Apache_Solr_Response $response)
{
parent::__construct("'{$response->getHttpStatus()}' Status: {$response->getHttpStatusMessage()}", $response->getHttpStatus());
$this->_response = $response;
}
/**
* Get the response for which this exception was generated
*
* @return Apache_Solr_Response
*/
public function getResponse()
{
return $this->_response;
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: InvalidArgumentException.php 54 2011-02-04 16:29:18Z donovan.jimenez $
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
class Apache_Solr_InvalidArgumentException extends Apache_Solr_Exception
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision: 54 $';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id: InvalidArgumentException.php 54 2011-02-04 16:29:18Z donovan.jimenez $';
}

View File

@ -0,0 +1,50 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: NoServiceAvailableException.php 54 2011-02-04 16:29:18Z donovan.jimenez $
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
class Apache_Solr_NoServiceAvailableException extends Apache_Solr_Exception
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision: 54 $';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id: NoServiceAvailableException.php 54 2011-02-04 16:29:18Z donovan.jimenez $';
}

View File

@ -0,0 +1,50 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: ParserException.php 54 2011-02-04 16:29:18Z donovan.jimenez $
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
class Apache_Solr_ParserException extends Apache_Solr_Exception
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision: 54 $';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id: ParserException.php 54 2011-02-04 16:29:18Z donovan.jimenez $';
}

View File

@ -0,0 +1,247 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: Response.php 54 2011-02-04 16:29:18Z donovan.jimenez $
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
require_once(dirname(__FILE__) . '/ParserException.php');
/**
* Represents a Solr response. Parses the raw response into a set of stdClass objects
* and associative arrays for easy access.
*
* Currently requires json_decode which is bundled with PHP >= 5.2.0, Alternatively can be
* installed with PECL. Zend Framework also includes a purely PHP solution.
*/
class Apache_Solr_Response
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision: 54 $';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id: Response.php 54 2011-02-04 16:29:18Z donovan.jimenez $';
/**
* Holds the raw response used in construction
*
* @var Apache_Solr_HttpTransport_Response HTTP response
*/
protected $_response;
/**
* Whether the raw response has been parsed
*
* @var boolean
*/
protected $_isParsed = false;
/**
* Parsed representation of the data
*
* @var mixed
*/
protected $_parsedData;
/**
* Data parsing flags. Determines what extra processing should be done
* after the data is initially converted to a data structure.
*
* @var boolean
*/
protected $_createDocuments = true,
$_collapseSingleValueArrays = true;
/**
* Constructor. Takes the raw HTTP response body and the exploded HTTP headers
*
* @return Apache_Solr_HttpTransport_Response HTTP response
* @param boolean $createDocuments Whether to convert the documents json_decoded as stdClass instances to Apache_Solr_Document instances
* @param boolean $collapseSingleValueArrays Whether to make multivalued fields appear as single values
*/
public function __construct(Apache_Solr_HttpTransport_Response $response, $createDocuments = true, $collapseSingleValueArrays = true)
{
$this->_response = $response;
$this->_createDocuments = (bool) $createDocuments;
$this->_collapseSingleValueArrays = (bool) $collapseSingleValueArrays;
}
/**
* Get the HTTP status code
*
* @return integer
*/
public function getHttpStatus()
{
return $this->_response->getStatusCode();
}
/**
* Get the HTTP status message of the response
*
* @return string
*/
public function getHttpStatusMessage()
{
return $this->_response->getStatusMessage();
}
/**
* Get content type of this Solr response
*
* @return string
*/
public function getType()
{
return $this->_response->getMimeType();
}
/**
* Get character encoding of this response. Should usually be utf-8, but just in case
*
* @return string
*/
public function getEncoding()
{
return $this->_response->getEncoding();
}
/**
* Get the raw response as it was given to this object
*
* @return string
*/
public function getRawResponse()
{
return $this->_response->getBody();
}
/**
* Magic get to expose the parsed data and to lazily load it
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
if (!$this->_isParsed)
{
$this->_parseData();
$this->_isParsed = true;
}
if (isset($this->_parsedData->$key))
{
return $this->_parsedData->$key;
}
return null;
}
/**
* Magic function for isset function on parsed data
*
* @param string $key
* @return boolean
*/
public function __isset($key)
{
if (!$this->_isParsed)
{
$this->_parseData();
$this->_isParsed = true;
}
return isset($this->_parsedData->$key);
}
/**
* Parse the raw response into the parsed_data array for access
*
* @throws Apache_Solr_ParserException If the data could not be parsed
*/
protected function _parseData()
{
//An alternative would be to use Zend_Json::decode(...)
$data = json_decode($this->_response->getBody());
// check that we receive a valid JSON response - we should never receive a null
if ($data === null)
{
throw new Apache_Solr_ParserException('Solr response does not appear to be valid JSON, please examine the raw response with getRawResponse() method');
}
//if we're configured to collapse single valued arrays or to convert them to Apache_Solr_Document objects
//and we have response documents, then try to collapse the values and / or convert them now
if (($this->_createDocuments || $this->_collapseSingleValueArrays) && isset($data->response) && is_array($data->response->docs))
{
$documents = array();
foreach ($data->response->docs as $originalDocument)
{
if ($this->_createDocuments)
{
$document = new Apache_Solr_Document();
}
else
{
$document = $originalDocument;
}
foreach ($originalDocument as $key => $value)
{
//If a result is an array with only a single
//value then its nice to be able to access
//it as if it were always a single value
if ($this->_collapseSingleValueArrays && is_array($value) && count($value) <= 1)
{
$value = array_shift($value);
}
$document->$key = $value;
}
$documents[] = $document;
}
$data->response->docs = $documents;
}
$this->_parsedData = $data;
}
}

View File

@ -0,0 +1,1181 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: Service.php 59 2011-02-08 20:38:59Z donovan.jimenez $
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
// See Issue #1 (http://code.google.com/p/solr-php-client/issues/detail?id=1)
// Doesn't follow typical include path conventions, but is more convenient for users
require_once(dirname(__FILE__) . '/Exception.php');
require_once(dirname(__FILE__) . '/HttpTransportException.php');
require_once(dirname(__FILE__) . '/InvalidArgumentException.php');
require_once(dirname(__FILE__) . '/Document.php');
require_once(dirname(__FILE__) . '/Response.php');
require_once(dirname(__FILE__) . '/HttpTransport/Interface.php');
/**
* Starting point for the Solr API. Represents a Solr server resource and has
* methods for pinging, adding, deleting, committing, optimizing and searching.
*
* Example Usage:
* <code>
* ...
* $solr = new Apache_Solr_Service(); //or explicitly new Apache_Solr_Service('localhost', 8180, '/solr')
*
* if ($solr->ping())
* {
* $solr->deleteByQuery('*:*'); //deletes ALL documents - be careful :)
*
* $document = new Apache_Solr_Document();
* $document->id = uniqid(); //or something else suitably unique
*
* $document->title = 'Some Title';
* $document->content = 'Some content for this wonderful document. Blah blah blah.';
*
* $solr->addDocument($document); //if you're going to be adding documents in bulk using addDocuments
* //with an array of documents is faster
*
* $solr->commit(); //commit to see the deletes and the document
* $solr->optimize(); //merges multiple segments into one
*
* //and the one we all care about, search!
* //any other common or custom parameters to the request handler can go in the
* //optional 4th array argument.
* $solr->search('content:blah', 0, 10, array('sort' => 'timestamp desc'));
* }
* ...
* </code>
*
* @todo Investigate using other HTTP clients other than file_get_contents built-in handler. Could provide performance
* improvements when dealing with multiple requests by using HTTP's keep alive functionality
*/
class Apache_Solr_Service
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision: 59 $';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id: Service.php 59 2011-02-08 20:38:59Z donovan.jimenez $';
/**
* Response writer we'll request - JSON. See http://code.google.com/p/solr-php-client/issues/detail?id=6#c1 for reasoning
*/
const SOLR_WRITER = 'json';
/**
* NamedList Treatment constants
*/
const NAMED_LIST_FLAT = 'flat';
const NAMED_LIST_MAP = 'map';
/**
* Search HTTP Methods
*/
const METHOD_GET = 'GET';
const METHOD_POST = 'POST';
/**
* Servlet mappings
*/
const PING_SERVLET = 'admin/ping';
const UPDATE_SERVLET = 'update';
const SEARCH_SERVLET = 'select';
const THREADS_SERVLET = 'admin/threads';
const EXTRACT_SERVLET = 'update/extract';
/**
* Server identification strings
*
* @var string
*/
protected $_host, $_port, $_path;
/**
* Whether {@link Apache_Solr_Response} objects should create {@link Apache_Solr_Document}s in
* the returned parsed data
*
* @var boolean
*/
protected $_createDocuments = true;
/**
* Whether {@link Apache_Solr_Response} objects should have multivalue fields with only a single value
* collapsed to appear as a single value would.
*
* @var boolean
*/
protected $_collapseSingleValueArrays = true;
/**
* How NamedLists should be formatted in the output. This specifically effects facet counts. Valid values
* are {@link Apache_Solr_Service::NAMED_LIST_MAP} (default) or {@link Apache_Solr_Service::NAMED_LIST_FLAT}.
*
* @var string
*/
protected $_namedListTreatment = self::NAMED_LIST_MAP;
/**
* Query delimiters. Someone might want to be able to change
* these (to use &amp; instead of & for example), so I've provided them.
*
* @var string
*/
protected $_queryDelimiter = '?', $_queryStringDelimiter = '&', $_queryBracketsEscaped = true;
/**
* Constructed servlet full path URLs
*
* @var string
*/
protected $_pingUrl, $_updateUrl, $_searchUrl, $_threadsUrl;
/**
* Keep track of whether our URLs have been constructed
*
* @var boolean
*/
protected $_urlsInited = false;
/**
* HTTP Transport implementation (pluggable)
*
* @var Apache_Solr_HttpTransport_Interface
*/
protected $_httpTransport = false;
/**
* Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
*
* NOTE: inside a phrase fewer characters need escaped, use {@link Apache_Solr_Service::escapePhrase()} instead
*
* @param string $value
* @return string
*/
static public function escape($value)
{
//list taken from http://lucene.apache.org/java/docs/queryparsersyntax.html#Escaping%20Special%20Characters
$pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
$replace = '\\\$1';
return preg_replace($pattern, $replace, $value);
}
/**
* Escape a value meant to be contained in a phrase for special query characters
*
* @param string $value
* @return string
*/
static public function escapePhrase($value)
{
$pattern = '/("|\\\)/';
$replace = '\\\$1';
return preg_replace($pattern, $replace, $value);
}
/**
* Convenience function for creating phrase syntax from a value
*
* @param string $value
* @return string
*/
static public function phrase($value)
{
return '"' . self::escapePhrase($value) . '"';
}
/**
* Constructor. All parameters are optional and will take on default values
* if not specified.
*
* @param string $host
* @param string $port
* @param string $path
* @param Apache_Solr_HttpTransport_Interface $httpTransport
*/
public function __construct($host = 'localhost', $port = 8180, $path = '/solr/', $httpTransport = false)
{
$this->setHost($host);
$this->setPort($port);
$this->setPath($path);
$this->_initUrls();
if ($httpTransport)
{
$this->setHttpTransport($httpTransport);
}
// check that our php version is >= 5.1.3 so we can correct for http_build_query behavior later
$this->_queryBracketsEscaped = version_compare(phpversion(), '5.1.3', '>=');
}
/**
* Return a valid http URL given this server's host, port and path and a provided servlet name
*
* @param string $servlet
* @return string
*/
protected function _constructUrl($servlet, $params = array())
{
if (count($params))
{
//escape all parameters appropriately for inclusion in the query string
$escapedParams = array();
foreach ($params as $key => $value)
{
$escapedParams[] = urlencode($key) . '=' . urlencode($value);
}
$queryString = $this->_queryDelimiter . implode($this->_queryStringDelimiter, $escapedParams);
}
else
{
$queryString = '';
}
return 'http://' . $this->_host . ':' . $this->_port . $this->_path . $servlet . $queryString;
}
/**
* Construct the Full URLs for the three servlets we reference
*/
protected function _initUrls()
{
//Initialize our full servlet URLs now that we have server information
$this->_extractUrl = $this->_constructUrl(self::EXTRACT_SERVLET);
$this->_pingUrl = $this->_constructUrl(self::PING_SERVLET);
$this->_searchUrl = $this->_constructUrl(self::SEARCH_SERVLET);
$this->_threadsUrl = $this->_constructUrl(self::THREADS_SERVLET, array('wt' => self::SOLR_WRITER ));
$this->_updateUrl = $this->_constructUrl(self::UPDATE_SERVLET, array('wt' => self::SOLR_WRITER ));
$this->_urlsInited = true;
}
protected function _generateQueryString($params)
{
// use http_build_query to encode our arguments because its faster
// than urlencoding all the parts ourselves in a loop
//
// because http_build_query treats arrays differently than we want to, correct the query
// string by changing foo[#]=bar (# being an actual number) parameter strings to just
// multiple foo=bar strings. This regex should always work since '=' will be urlencoded
// anywhere else the regex isn't expecting it
//
// NOTE: before php 5.1.3 brackets were not url encoded by http_build query - we've checked
// the php version in the constructor and put the results in the instance variable. Also, before
// 5.1.2 the arg_separator parameter was not available, so don't use it
if ($this->_queryBracketsEscaped)
{
$queryString = http_build_query($params, null, $this->_queryStringDelimiter);
return preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', $queryString);
}
else
{
$queryString = http_build_query($params);
return preg_replace('/\\[(?:[0-9]|[1-9][0-9]+)\\]=/', '=', $queryString);
}
}
/**
* Central method for making a get operation against this Solr Server
*
* @param string $url
* @param float $timeout Read timeout in seconds
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If a non 200 response status is returned
*/
protected function _sendRawGet($url, $timeout = FALSE)
{
$httpTransport = $this->getHttpTransport();
$httpResponse = $httpTransport->performGetRequest($url, $timeout);
$solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
if ($solrResponse->getHttpStatus() != 200)
{
throw new Apache_Solr_HttpTransportException($solrResponse);
}
return $solrResponse;
}
/**
* Central method for making a post operation against this Solr Server
*
* @param string $url
* @param string $rawPost
* @param float $timeout Read timeout in seconds
* @param string $contentType
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If a non 200 response status is returned
*/
protected function _sendRawPost($url, $rawPost, $timeout = FALSE, $contentType = 'text/xml; charset=UTF-8')
{
$httpTransport = $this->getHttpTransport();
$httpResponse = $httpTransport->performPostRequest($url, $rawPost, $contentType, $timeout);
$solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
if ($solrResponse->getHttpStatus() != 200)
{
throw new Apache_Solr_HttpTransportException($solrResponse);
}
return $solrResponse;
}
/**
* Returns the set host
*
* @return string
*/
public function getHost()
{
return $this->_host;
}
/**
* Set the host used. If empty will fallback to constants
*
* @param string $host
*
* @throws Apache_Solr_InvalidArgumentException If the host parameter is empty
*/
public function setHost($host)
{
//Use the provided host or use the default
if (empty($host))
{
throw new Apache_Solr_InvalidArgumentException('Host parameter is empty');
}
else
{
$this->_host = $host;
}
if ($this->_urlsInited)
{
$this->_initUrls();
}
}
/**
* Get the set port
*
* @return integer
*/
public function getPort()
{
return $this->_port;
}
/**
* Set the port used. If empty will fallback to constants
*
* @param integer $port
*
* @throws Apache_Solr_InvalidArgumentException If the port parameter is empty
*/
public function setPort($port)
{
//Use the provided port or use the default
$port = (int) $port;
if ($port <= 0)
{
throw new Apache_Solr_InvalidArgumentException('Port is not a valid port number');
}
else
{
$this->_port = $port;
}
if ($this->_urlsInited)
{
$this->_initUrls();
}
}
/**
* Get the set path.
*
* @return string
*/
public function getPath()
{
return $this->_path;
}
/**
* Set the path used. If empty will fallback to constants
*
* @param string $path
*/
public function setPath($path)
{
$path = trim($path, '/');
$this->_path = '/' . $path . '/';
if ($this->_urlsInited)
{
$this->_initUrls();
}
}
/**
* Get the current configured HTTP Transport
*
* @return HttpTransportInterface
*/
public function getHttpTransport()
{
// lazy load a default if one has not be set
if ($this->_httpTransport === false)
{
require_once(dirname(__FILE__) . '/HttpTransport/FileGetContents.php');
$this->_httpTransport = new Apache_Solr_HttpTransport_FileGetContents();
}
return $this->_httpTransport;
}
/**
* Set the HTTP Transport implemenation that will be used for all HTTP requests
*
* @param Apache_Solr_HttpTransport_Interface
*/
public function setHttpTransport(Apache_Solr_HttpTransport_Interface $httpTransport)
{
$this->_httpTransport = $httpTransport;
}
/**
* Set the create documents flag. This determines whether {@link Apache_Solr_Response} objects will
* parse the response and create {@link Apache_Solr_Document} instances in place.
*
* @param boolean $createDocuments
*/
public function setCreateDocuments($createDocuments)
{
$this->_createDocuments = (bool) $createDocuments;
}
/**
* Get the current state of teh create documents flag.
*
* @return boolean
*/
public function getCreateDocuments()
{
return $this->_createDocuments;
}
/**
* Set the collapse single value arrays flag.
*
* @param boolean $collapseSingleValueArrays
*/
public function setCollapseSingleValueArrays($collapseSingleValueArrays)
{
$this->_collapseSingleValueArrays = (bool) $collapseSingleValueArrays;
}
/**
* Get the current state of the collapse single value arrays flag.
*
* @return boolean
*/
public function getCollapseSingleValueArrays()
{
return $this->_collapseSingleValueArrays;
}
/**
* Get the current default timeout setting (initially the default_socket_timeout ini setting)
* in seconds
*
* @return float
*
* @deprecated Use the getDefaultTimeout method on the HTTP transport implementation
*/
public function getDefaultTimeout()
{
return $this->getHttpTransport()->getDefaultTimeout();
}
/**
* Set the default timeout for all calls that aren't passed a specific timeout
*
* @param float $timeout Timeout value in seconds
*
* @deprecated Use the setDefaultTimeout method on the HTTP transport implementation
*/
public function setDefaultTimeout($timeout)
{
$this->getHttpTransport()->setDefaultTimeout($timeout);
}
/**
* Set how NamedLists should be formatted in the response data. This mainly effects
* the facet counts format.
*
* @param string $namedListTreatment
* @throws Apache_Solr_InvalidArgumentException If invalid option is set
*/
public function setNamedListTreatment($namedListTreatment)
{
switch ((string) $namedListTreatment)
{
case Apache_Solr_Service::NAMED_LIST_FLAT:
$this->_namedListTreatment = Apache_Solr_Service::NAMED_LIST_FLAT;
break;
case Apache_Solr_Service::NAMED_LIST_MAP:
$this->_namedListTreatment = Apache_Solr_Service::NAMED_LIST_MAP;
break;
default:
throw new Apache_Solr_InvalidArgumentException('Not a valid named list treatement option');
}
}
/**
* Get the current setting for named list treatment.
*
* @return string
*/
public function getNamedListTreatment()
{
return $this->_namedListTreatment;
}
/**
* Set the string used to separate the path form the query string.
* Defaulted to '?'
*
* @param string $queryDelimiter
*/
public function setQueryDelimiter($queryDelimiter)
{
$this->_queryDelimiter = $queryDelimiter;
}
/**
* Set the string used to separate the parameters in thequery string
* Defaulted to '&'
*
* @param string $queryStringDelimiter
*/
public function setQueryStringDelimiter($queryStringDelimiter)
{
$this->_queryStringDelimiter = $queryStringDelimiter;
}
/**
* Call the /admin/ping servlet, can be used to quickly tell if a connection to the
* server is able to be made.
*
* @param float $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2)
* @return float Actual time taken to ping the server, FALSE if timeout or HTTP error status occurs
*/
public function ping($timeout = 2)
{
$start = microtime(true);
$httpTransport = $this->getHttpTransport();
$httpResponse = $httpTransport->performHeadRequest($this->_pingUrl, $timeout);
$solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
if ($solrResponse->getHttpStatus() == 200)
{
return microtime(true) - $start;
}
else
{
return false;
}
}
/**
* Call the /admin/threads servlet and retrieve information about all threads in the
* Solr servlet's thread group. Useful for diagnostics.
*
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function threads()
{
return $this->_sendRawGet($this->_threadsUrl);
}
/**
* Raw Add Method. Takes a raw post body and sends it to the update service. Post body
* should be a complete and well formed "add" xml document.
*
* @param string $rawPost
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function add($rawPost)
{
return $this->_sendRawPost($this->_updateUrl, $rawPost);
}
/**
* Add a Solr Document to the index
*
* @param Apache_Solr_Document $document
* @param boolean $allowDups
* @param boolean $overwritePending
* @param boolean $overwriteCommitted
* @param integer $commitWithin The number of milliseconds that a document must be committed within, see @{link http://wiki.apache.org/solr/UpdateXmlMessages#The_Update_Schema} for details. If left empty this property will not be set in the request.
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function addDocument(Apache_Solr_Document $document, $allowDups = false, $overwritePending = true, $overwriteCommitted = true, $commitWithin = 0)
{
$dupValue = $allowDups ? 'true' : 'false';
$pendingValue = $overwritePending ? 'true' : 'false';
$committedValue = $overwriteCommitted ? 'true' : 'false';
$commitWithin = (int) $commitWithin;
$commitWithinString = $commitWithin > 0 ? " commitWithin=\"{$commitWithin}\"" : '';
$rawPost = "<add allowDups=\"{$dupValue}\" overwritePending=\"{$pendingValue}\" overwriteCommitted=\"{$committedValue}\"{$commitWithinString}>";
$rawPost .= $this->_documentToXmlFragment($document);
$rawPost .= '</add>';
return $this->add($rawPost);
}
/**
* Add an array of Solr Documents to the index all at once
*
* @param array $documents Should be an array of Apache_Solr_Document instances
* @param boolean $allowDups
* @param boolean $overwritePending
* @param boolean $overwriteCommitted
* @param integer $commitWithin The number of milliseconds that a document must be committed within, see @{link http://wiki.apache.org/solr/UpdateXmlMessages#The_Update_Schema} for details. If left empty this property will not be set in the request.
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function addDocuments($documents, $allowDups = false, $overwritePending = true, $overwriteCommitted = true, $commitWithin = 0)
{
$dupValue = $allowDups ? 'true' : 'false';
$pendingValue = $overwritePending ? 'true' : 'false';
$committedValue = $overwriteCommitted ? 'true' : 'false';
$commitWithin = (int) $commitWithin;
$commitWithinString = $commitWithin > 0 ? " commitWithin=\"{$commitWithin}\"" : '';
$rawPost = "<add allowDups=\"{$dupValue}\" overwritePending=\"{$pendingValue}\" overwriteCommitted=\"{$committedValue}\"{$commitWithinString}>";
foreach ($documents as $document)
{
if ($document instanceof Apache_Solr_Document)
{
$rawPost .= $this->_documentToXmlFragment($document);
}
}
$rawPost .= '</add>';
return $this->add($rawPost);
}
/**
* Create an XML fragment from a {@link Apache_Solr_Document} instance appropriate for use inside a Solr add call
*
* @return string
*/
protected function _documentToXmlFragment(Apache_Solr_Document $document)
{
$xml = '<doc';
if ($document->getBoost() !== false)
{
$xml .= ' boost="' . $document->getBoost() . '"';
}
$xml .= '>';
foreach ($document as $key => $value)
{
$key = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
$fieldBoost = $document->getFieldBoost($key);
if (is_array($value))
{
foreach ($value as $multivalue)
{
$xml .= '<field name="' . $key . '"';
if ($fieldBoost !== false)
{
$xml .= ' boost="' . $fieldBoost . '"';
// only set the boost for the first field in the set
$fieldBoost = false;
}
$multivalue = htmlspecialchars($multivalue, ENT_NOQUOTES, 'UTF-8');
$xml .= '>' . $multivalue . '</field>';
}
}
else
{
$xml .= '<field name="' . $key . '"';
if ($fieldBoost !== false)
{
$xml .= ' boost="' . $fieldBoost . '"';
}
$value = htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8');
$xml .= '>' . $value . '</field>';
}
}
$xml .= '</doc>';
// replace any control characters to avoid Solr XML parser exception
return $this->_stripCtrlChars($xml);
}
/**
* Replace control (non-printable) characters from string that are invalid to Solr's XML parser with a space.
*
* @param string $string
* @return string
*/
protected function _stripCtrlChars($string)
{
// See: http://w3.org/International/questions/qa-forms-utf-8.html
// Printable utf-8 does not include any of these chars below x7F
return preg_replace('@[\x00-\x08\x0B\x0C\x0E-\x1F]@', ' ', $string);
}
/**
* Send a commit command. Will be synchronous unless both wait parameters are set to false.
*
* @param boolean $expungeDeletes Defaults to false, merge segments with deletes away
* @param boolean $waitFlush Defaults to true, block until index changes are flushed to disk
* @param boolean $waitSearcher Defaults to true, block until a new searcher is opened and registered as the main query searcher, making the changes visible
* @param float $timeout Maximum expected duration (in seconds) of the commit operation on the server (otherwise, will throw a communication exception). Defaults to 1 hour
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function commit($expungeDeletes = false, $waitFlush = true, $waitSearcher = true, $timeout = 3600)
{
$expungeValue = $expungeDeletes ? 'true' : 'false';
$flushValue = $waitFlush ? 'true' : 'false';
$searcherValue = $waitSearcher ? 'true' : 'false';
$rawPost = '<commit expungeDeletes="' . $expungeValue . '" waitFlush="' . $flushValue . '" waitSearcher="' . $searcherValue . '" />';
return $this->_sendRawPost($this->_updateUrl, $rawPost, $timeout);
}
/**
* Raw Delete Method. Takes a raw post body and sends it to the update service. Body should be
* a complete and well formed "delete" xml document
*
* @param string $rawPost Expected to be utf-8 encoded xml document
* @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function delete($rawPost, $timeout = 3600)
{
return $this->_sendRawPost($this->_updateUrl, $rawPost, $timeout);
}
/**
* Create a delete document based on document ID
*
* @param string $id Expected to be utf-8 encoded
* @param boolean $fromPending
* @param boolean $fromCommitted
* @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function deleteById($id, $fromPending = true, $fromCommitted = true, $timeout = 3600)
{
$pendingValue = $fromPending ? 'true' : 'false';
$committedValue = $fromCommitted ? 'true' : 'false';
//escape special xml characters
$id = htmlspecialchars($id, ENT_NOQUOTES, 'UTF-8');
$rawPost = '<delete fromPending="' . $pendingValue . '" fromCommitted="' . $committedValue . '"><id>' . $id . '</id></delete>';
return $this->delete($rawPost, $timeout);
}
/**
* Create and post a delete document based on multiple document IDs.
*
* @param array $ids Expected to be utf-8 encoded strings
* @param boolean $fromPending
* @param boolean $fromCommitted
* @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function deleteByMultipleIds($ids, $fromPending = true, $fromCommitted = true, $timeout = 3600)
{
$pendingValue = $fromPending ? 'true' : 'false';
$committedValue = $fromCommitted ? 'true' : 'false';
$rawPost = '<delete fromPending="' . $pendingValue . '" fromCommitted="' . $committedValue . '">';
foreach ($ids as $id)
{
//escape special xml characters
$id = htmlspecialchars($id, ENT_NOQUOTES, 'UTF-8');
$rawPost .= '<id>' . $id . '</id>';
}
$rawPost .= '</delete>';
return $this->delete($rawPost, $timeout);
}
/**
* Create a delete document based on a query and submit it
*
* @param string $rawQuery Expected to be utf-8 encoded
* @param boolean $fromPending
* @param boolean $fromCommitted
* @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function deleteByQuery($rawQuery, $fromPending = true, $fromCommitted = true, $timeout = 3600)
{
$pendingValue = $fromPending ? 'true' : 'false';
$committedValue = $fromCommitted ? 'true' : 'false';
// escape special xml characters
$rawQuery = htmlspecialchars($rawQuery, ENT_NOQUOTES, 'UTF-8');
$rawPost = '<delete fromPending="' . $pendingValue . '" fromCommitted="' . $committedValue . '"><query>' . $rawQuery . '</query></delete>';
return $this->delete($rawPost, $timeout);
}
/**
* Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how
* to use Solr Cell and what parameters are available.
*
* NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost."
* as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value
* pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also
* pass in a document isntance with an "id" field" - the document's value(s) will take precedence).
*
* @param string $file Path to file to extract data from
* @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation)
* @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params)
* @param string $mimetype optional mimetype specification (for the file being extracted)
*
* @return Apache_Solr_Response
*
* @throws Apache_Solr_InvalidArgumentException if $file, $params, or $document are invalid.
*/
public function extract($file, $params = array(), $document = null, $mimetype = 'application/octet-stream')
{
// check if $params is an array (allow null for default empty array)
if (!is_null($params))
{
if (!is_array($params))
{
throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null");
}
}
else
{
$params = array();
}
// if $file is an http request, defer to extractFromUrl instead
if (substr($file, 0, 7) == 'http://' || substr($file, 0, 8) == 'https://')
{
return $this->extractFromUrl($file, $params, $document, $mimetype);
}
// read the contents of the file
$contents = @file_get_contents($file);
if ($contents !== false)
{
// add the resource.name parameter if not specified
if (!isset($params['resource.name']))
{
$params['resource.name'] = basename($file);
}
// delegate the rest to extractFromString
return $this->extractFromString($contents, $params, $document, $mimetype);
}
else
{
throw new Apache_Solr_InvalidArgumentException("File '{$file}' is empty or could not be read");
}
}
/**
* Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how
* to use Solr Cell and what parameters are available.
*
* NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost."
* as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value
* pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also
* pass in a document isntance with an "id" field" - the document's value(s) will take precedence).
*
* @param string $data Data that will be passed to Solr Cell
* @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation)
* @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params)
* @param string $mimetype optional mimetype specification (for the file being extracted)
*
* @return Apache_Solr_Response
*
* @throws Apache_Solr_InvalidArgumentException if $file, $params, or $document are invalid.
*
* @todo Should be using multipart/form-data to post parameter values, but I could not get my implementation to work. Needs revisisted.
*/
public function extractFromString($data, $params = array(), $document = null, $mimetype = 'application/octet-stream')
{
// check if $params is an array (allow null for default empty array)
if (!is_null($params))
{
if (!is_array($params))
{
throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null");
}
}
else
{
$params = array();
}
// make sure we receive our response in JSON and have proper name list treatment
$params['wt'] = self::SOLR_WRITER;
$params['json.nl'] = $this->_namedListTreatment;
// check if $document is an Apache_Solr_Document instance
if (!is_null($document) && $document instanceof Apache_Solr_Document)
{
// iterate document, adding literal.* and boost.* fields to $params as appropriate
foreach ($document as $field => $fieldValue)
{
// check if we need to add a boost.* parameters
$fieldBoost = $document->getFieldBoost($field);
if ($fieldBoost !== false)
{
$params["boost.{$field}"] = $fieldBoost;
}
// add the literal.* parameter
$params["literal.{$field}"] = $fieldValue;
}
}
// params will be sent to SOLR in the QUERY STRING
$queryString = $this->_generateQueryString($params);
// the file contents will be sent to SOLR as the POST BODY - we use application/octect-stream as default mimetype
return $this->_sendRawPost($this->_extractUrl . $this->_queryDelimiter . $queryString, $data, false, $mimetype);
}
/**
* Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how
* to use Solr Cell and what parameters are available.
*
* NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost."
* as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value
* pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also
* pass in a document isntance with an "id" field" - the document's value(s) will take precedence).
*
* @param string $url URL
* @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation)
* @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params)
* @param string $mimetype optional mimetype specification (for the file being extracted)
*
* @return Apache_Solr_Response
*
* @throws Apache_Solr_InvalidArgumentException if $url, $params, or $document are invalid.
*/
public function extractFromUrl($url, $params = array(), $document = null, $mimetype = 'application/octet-stream')
{
// check if $params is an array (allow null for default empty array)
if (!is_null($params))
{
if (!is_array($params))
{
throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null");
}
}
else
{
$params = array();
}
$httpTransport = $this->getHttpTransport();
// read the contents of the URL using our configured Http Transport and default timeout
$httpResponse = $httpTransport->performGetRequest($url);
// check that its a 200 response
if ($httpResponse->getStatusCode() == 200)
{
// add the resource.name parameter if not specified
if (!isset($params['resource.name']))
{
$params['resource.name'] = $url;
}
// delegate the rest to extractFromString
return $this->extractFromString($httpResponse->getBody(), $params, $document, $mimetype);
}
else
{
throw new Apache_Solr_InvalidArgumentException("URL '{$url}' returned non 200 response code");
}
}
/**
* Send an optimize command. Will be synchronous unless both wait parameters are set
* to false.
*
* @param boolean $waitFlush
* @param boolean $waitSearcher
* @param float $timeout Maximum expected duration of the commit operation on the server (otherwise, will throw a communication exception)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function optimize($waitFlush = true, $waitSearcher = true, $timeout = 3600)
{
$flushValue = $waitFlush ? 'true' : 'false';
$searcherValue = $waitSearcher ? 'true' : 'false';
$rawPost = '<optimize waitFlush="' . $flushValue . '" waitSearcher="' . $searcherValue . '" />';
return $this->_sendRawPost($this->_updateUrl, $rawPost, $timeout);
}
/**
* Simple Search interface
*
* @param string $query The raw query string
* @param int $offset The starting offset for result documents
* @param int $limit The maximum number of result documents to return
* @param array $params key / value pairs for other query parameters (see Solr documentation), use arrays for parameter keys used more than once (e.g. facet.field)
* @param string $method The HTTP method (Apache_Solr_Service::METHOD_GET or Apache_Solr_Service::METHOD::POST)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
* @throws Apache_Solr_InvalidArgumentException If an invalid HTTP method is used
*/
public function search($query, $offset = 0, $limit = 10, $params = array(), $method = self::METHOD_GET)
{
// ensure params is an array
if (!is_null($params))
{
if (!is_array($params))
{
// params was specified but was not an array - invalid
throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null");
}
}
else
{
$params = array();
}
// construct our full parameters
// common parameters in this interface
$params['wt'] = self::SOLR_WRITER;
$params['json.nl'] = $this->_namedListTreatment;
$params['q'] = $query;
$params['start'] = $offset;
$params['rows'] = $limit;
$queryString = $this->_generateQueryString($params);
if ($method == self::METHOD_GET)
{
return $this->_sendRawGet($this->_searchUrl . $this->_queryDelimiter . $queryString);
}
else if ($method == self::METHOD_POST)
{
return $this->_sendRawPost($this->_searchUrl, $queryString, FALSE, 'application/x-www-form-urlencoded; charset=UTF-8');
}
else
{
throw new Apache_Solr_InvalidArgumentException("Unsupported method '$method', please use the Apache_Solr_Service::METHOD_* constants");
}
}
}

View File

@ -0,0 +1,914 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
* @version $Id: Balancer.php 54 2011-02-04 16:29:18Z donovan.jimenez $
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>, Dan Wolfe
*/
// See Issue #1 (http://code.google.com/p/solr-php-client/issues/detail?id=1)
// Doesn't follow typical include path conventions, but is more convenient for users
require_once(dirname(dirname(__FILE__)) . '/Service.php');
require_once(dirname(dirname(__FILE__)) . '/NoServiceAvailableException.php');
/**
* Reference Implementation for using multiple Solr services in a distribution. Functionality
* includes:
* routing of read / write operations
* failover (on selection) for multiple read servers
*/
class Apache_Solr_Service_Balancer
{
/**
* SVN Revision meta data for this class
*/
const SVN_REVISION = '$Revision: 54 $';
/**
* SVN ID meta data for this class
*/
const SVN_ID = '$Id: Balancer.php 54 2011-02-04 16:29:18Z donovan.jimenez $';
protected $_createDocuments = true;
protected $_readableServices = array();
protected $_writeableServices = array();
protected $_currentReadService = null;
protected $_currentWriteService = null;
protected $_readPingTimeout = 2;
protected $_writePingTimeout = 4;
// Configuration for server selection backoff intervals
protected $_useBackoff = false; // Set to true to use more resillient write server selection
protected $_backoffLimit = 600; // 10 minute default maximum
protected $_backoffEscalation = 2.0; // Rate at which to increase backoff period
protected $_defaultBackoff = 2.0; // Default backoff interval
/**
* Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
*
* NOTE: inside a phrase fewer characters need escaped, use {@link Apache_Solr_Service::escapePhrase()} instead
*
* @param string $value
* @return string
*/
static public function escape($value)
{
return Apache_Solr_Service::escape($value);
}
/**
* Escape a value meant to be contained in a phrase for special query characters
*
* @param string $value
* @return string
*/
static public function escapePhrase($value)
{
return Apache_Solr_Service::escapePhrase($value);
}
/**
* Convenience function for creating phrase syntax from a value
*
* @param string $value
* @return string
*/
static public function phrase($value)
{
return Apache_Solr_Service::phrase($value);
}
/**
* Constructor. Takes arrays of read and write service instances or descriptions
*
* @param array $readableServices
* @param array $writeableServices
*/
public function __construct($readableServices = array(), $writeableServices = array())
{
//setup readable services
foreach ($readableServices as $service)
{
$this->addReadService($service);
}
//setup writeable services
foreach ($writeableServices as $service)
{
$this->addWriteService($service);
}
}
public function setReadPingTimeout($timeout)
{
$this->_readPingTimeout = $timeout;
}
public function setWritePingTimeout($timeout)
{
$this->_writePingTimeout = $timeout;
}
public function setUseBackoff($enable)
{
$this->_useBackoff = $enable;
}
/**
* Generates a service ID
*
* @param string $host
* @param integer $port
* @param string $path
* @return string
*/
protected function _getServiceId($host, $port, $path)
{
return $host . ':' . $port . $path;
}
/**
* Adds a service instance or service descriptor (if it is already
* not added)
*
* @param mixed $service
*
* @throws Apache_Solr_InvalidArgumentException If service descriptor is not valid
*/
public function addReadService($service)
{
if ($service instanceof Apache_Solr_Service)
{
$id = $this->_getServiceId($service->getHost(), $service->getPort(), $service->getPath());
$this->_readableServices[$id] = $service;
}
else if (is_array($service))
{
if (isset($service['host']) && isset($service['port']) && isset($service['path']))
{
$id = $this->_getServiceId((string)$service['host'], (int)$service['port'], (string)$service['path']);
$this->_readableServices[$id] = $service;
}
else
{
throw new Apache_Solr_InvalidArgumentException('A Readable Service description array does not have all required elements of host, port, and path');
}
}
}
/**
* Removes a service instance or descriptor from the available services
*
* @param mixed $service
*
* @throws Apache_Solr_InvalidArgumentException If service descriptor is not valid
*/
public function removeReadService($service)
{
$id = '';
if ($service instanceof Apache_Solr_Service)
{
$id = $this->_getServiceId($service->getHost(), $service->getPort(), $service->getPath());
}
else if (is_array($service))
{
if (isset($service['host']) && isset($service['port']) && isset($service['path']))
{
$id = $this->_getServiceId((string)$service['host'], (int)$service['port'], (string)$service['path']);
}
else
{
throw new Apache_Solr_InvalidArgumentException('A Readable Service description array does not have all required elements of host, port, and path');
}
}
else if (is_string($service))
{
$id = $service;
}
if ($id && isset($this->_readableServices[$id]))
{
unset($this->_readableServices[$id]);
}
}
/**
* Adds a service instance or service descriptor (if it is already
* not added)
*
* @param mixed $service
*
* @throws Apache_Solr_InvalidArgumentException If service descriptor is not valid
*/
public function addWriteService($service)
{
if ($service instanceof Apache_Solr_Service)
{
$id = $this->_getServiceId($service->getHost(), $service->getPort(), $service->getPath());
$this->_writeableServices[$id] = $service;
}
else if (is_array($service))
{
if (isset($service['host']) && isset($service['port']) && isset($service['path']))
{
$id = $this->_getServiceId((string)$service['host'], (int)$service['port'], (string)$service['path']);
$this->_writeableServices[$id] = $service;
}
else
{
throw new Apache_Solr_InvalidArgumentException('A Writeable Service description array does not have all required elements of host, port, and path');
}
}
}
/**
* Removes a service instance or descriptor from the available services
*
* @param mixed $service
*
* @throws Apache_Solr_InvalidArgumentException If service descriptor is not valid
*/
public function removeWriteService($service)
{
$id = '';
if ($service instanceof Apache_Solr_Service)
{
$id = $this->_getServiceId($service->getHost(), $service->getPort(), $service->getPath());
}
else if (is_array($service))
{
if (isset($service['host']) && isset($service['port']) && isset($service['path']))
{
$id = $this->_getServiceId((string)$service['host'], (int)$service['port'], (string)$service['path']);
}
else
{
throw new Apache_Solr_InvalidArgumentException('A Readable Service description array does not have all required elements of host, port, and path');
}
}
else if (is_string($service))
{
$id = $service;
}
if ($id && isset($this->_writeableServices[$id]))
{
unset($this->_writeableServices[$id]);
}
}
/**
* Iterate through available read services and select the first with a ping
* that satisfies configured timeout restrictions (or the default)
*
* @return Apache_Solr_Service
*
* @throws Apache_Solr_NoServiceAvailableException If there are no read services that meet requirements
*/
protected function _selectReadService($forceSelect = false)
{
if (!$this->_currentReadService || !isset($this->_readableServices[$this->_currentReadService]) || $forceSelect)
{
if ($this->_currentReadService && isset($this->_readableServices[$this->_currentReadService]) && $forceSelect)
{
// we probably had a communication error, ping the current read service, remove it if it times out
if ($this->_readableServices[$this->_currentReadService]->ping($this->_readPingTimeout) === false)
{
$this->removeReadService($this->_currentReadService);
}
}
if (count($this->_readableServices))
{
// select one of the read services at random
$ids = array_keys($this->_readableServices);
$id = $ids[rand(0, count($ids) - 1)];
$service = $this->_readableServices[$id];
if (is_array($service))
{
//convert the array definition to a client object
$service = new Apache_Solr_Service($service['host'], $service['port'], $service['path']);
$this->_readableServices[$id] = $service;
}
$service->setCreateDocuments($this->_createDocuments);
$this->_currentReadService = $id;
}
else
{
throw new Apache_Solr_NoServiceAvailableException('No read services were available');
}
}
return $this->_readableServices[$this->_currentReadService];
}
/**
* Iterate through available write services and select the first with a ping
* that satisfies configured timeout restrictions (or the default)
*
* @return Apache_Solr_Service
*
* @throws Apache_Solr_NoServiceAvailableException If there are no write services that meet requirements
*/
protected function _selectWriteService($forceSelect = false)
{
if($this->_useBackoff)
{
return $this->_selectWriteServiceSafe($forceSelect);
}
if (!$this->_currentWriteService || !isset($this->_writeableServices[$this->_currentWriteService]) || $forceSelect)
{
if ($this->_currentWriteService && isset($this->_writeableServices[$this->_currentWriteService]) && $forceSelect)
{
// we probably had a communication error, ping the current read service, remove it if it times out
if ($this->_writeableServices[$this->_currentWriteService]->ping($this->_writePingTimeout) === false)
{
$this->removeWriteService($this->_currentWriteService);
}
}
if (count($this->_writeableServices))
{
// select one of the read services at random
$ids = array_keys($this->_writeableServices);
$id = $ids[rand(0, count($ids) - 1)];
$service = $this->_writeableServices[$id];
if (is_array($service))
{
//convert the array definition to a client object
$service = new Apache_Solr_Service($service['host'], $service['port'], $service['path']);
$this->_writeableServices[$id] = $service;
}
$this->_currentWriteService = $id;
}
else
{
throw new Apache_Solr_NoServiceAvailableException('No write services were available');
}
}
return $this->_writeableServices[$this->_currentWriteService];
}
/**
* Iterate through available write services and select the first with a ping
* that satisfies configured timeout restrictions (or the default). The
* timeout period will increase until a connection is made or the limit is
* reached. This will allow for increased reliability with heavily loaded
* server(s).
*
* @return Apache_Solr_Service
*
* @throws Apache_Solr_NoServiceAvailableException If there are no write services that meet requirements
*/
protected function _selectWriteServiceSafe($forceSelect = false)
{
if (!$this->_currentWriteService || !isset($this->_writeableServices[$this->_currentWriteService]) || $forceSelect)
{
if (count($this->_writeableServices))
{
$backoff = $this->_defaultBackoff;
do {
// select one of the read services at random
$ids = array_keys($this->_writeableServices);
$id = $ids[rand(0, count($ids) - 1)];
$service = $this->_writeableServices[$id];
if (is_array($service))
{
//convert the array definition to a client object
$service = new Apache_Solr_Service($service['host'], $service['port'], $service['path']);
$this->_writeableServices[$id] = $service;
}
$this->_currentWriteService = $id;
$backoff *= $this->_backoffEscalation;
if($backoff > $this->_backoffLimit)
{
throw new Apache_Solr_NoServiceAvailableException('No write services were available. All timeouts exceeded.');
}
} while($this->_writeableServices[$this->_currentWriteService]->ping($backoff) === false);
}
else
{
throw new Apache_Solr_NoServiceAvailableException('No write services were available');
}
}
return $this->_writeableServices[$this->_currentWriteService];
}
/**
* Set the create documents flag. This determines whether {@link Apache_Solr_Response} objects will
* parse the response and create {@link Apache_Solr_Document} instances in place.
*
* @param boolean $createDocuments
*/
public function setCreateDocuments($createDocuments)
{
$this->_createDocuments = (bool) $createDocuments;
// set on current read service
if ($this->_currentReadService)
{
$service = $this->_selectReadService();
$service->setCreateDocuments($createDocuments);
}
}
/**
* Get the current state of teh create documents flag.
*
* @return boolean
*/
public function getCreateDocuments()
{
return $this->_createDocuments;
}
/**
* Raw Add Method. Takes a raw post body and sends it to the update service. Post body
* should be a complete and well formed "add" xml document.
*
* @param string $rawPost
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function add($rawPost)
{
$service = $this->_selectWriteService();
do
{
try
{
return $service->add($rawPost);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectWriteService(true);
} while ($service);
return false;
}
/**
* Add a Solr Document to the index
*
* @param Apache_Solr_Document $document
* @param boolean $allowDups
* @param boolean $overwritePending
* @param boolean $overwriteCommitted
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function addDocument(Apache_Solr_Document $document, $allowDups = false, $overwritePending = true, $overwriteCommitted = true)
{
$service = $this->_selectWriteService();
do
{
try
{
return $service->addDocument($document, $allowDups, $overwritePending, $overwriteCommitted);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectWriteService(true);
} while ($service);
return false;
}
/**
* Add an array of Solr Documents to the index all at once
*
* @param array $documents Should be an array of Apache_Solr_Document instances
* @param boolean $allowDups
* @param boolean $overwritePending
* @param boolean $overwriteCommitted
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function addDocuments($documents, $allowDups = false, $overwritePending = true, $overwriteCommitted = true)
{
$service = $this->_selectWriteService();
do
{
try
{
return $service->addDocuments($documents, $allowDups, $overwritePending, $overwriteCommitted);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectWriteService(true);
} while ($service);
return false;
}
/**
* Send a commit command. Will be synchronous unless both wait parameters are set
* to false.
*
* @param boolean $waitFlush
* @param boolean $waitSearcher
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function commit($optimize = true, $waitFlush = true, $waitSearcher = true, $timeout = 3600)
{
$service = $this->_selectWriteService();
do
{
try
{
return $service->commit($optimize, $waitFlush, $waitSearcher, $timeout);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectWriteService(true);
} while ($service);
return false;
}
/**
* Raw Delete Method. Takes a raw post body and sends it to the update service. Body should be
* a complete and well formed "delete" xml document
*
* @param string $rawPost
* @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function delete($rawPost, $timeout = 3600)
{
$service = $this->_selectWriteService();
do
{
try
{
return $service->delete($rawPost, $timeout);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectWriteService(true);
} while ($service);
return false;
}
/**
* Create a delete document based on document ID
*
* @param string $id
* @param boolean $fromPending
* @param boolean $fromCommitted
* @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function deleteById($id, $fromPending = true, $fromCommitted = true, $timeout = 3600)
{
$service = $this->_selectWriteService();
do
{
try
{
return $service->deleteById($id, $fromPending, $fromCommitted, $timeout);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectWriteService(true);
} while ($service);
return false;
}
/**
* Create and post a delete document based on multiple document IDs.
*
* @param array $ids Expected to be utf-8 encoded strings
* @param boolean $fromPending
* @param boolean $fromCommitted
* @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function deleteByMultipleIds($ids, $fromPending = true, $fromCommitted = true, $timeout = 3600)
{
$service = $this->_selectWriteService();
do
{
try
{
return $service->deleteByMultipleId($ids, $fromPending, $fromCommitted, $timeout);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectWriteService(true);
} while ($service);
return false;
}
/**
* Create a delete document based on a query and submit it
*
* @param string $rawQuery
* @param boolean $fromPending
* @param boolean $fromCommitted
* @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function deleteByQuery($rawQuery, $fromPending = true, $fromCommitted = true, $timeout = 3600)
{
$service = $this->_selectWriteService();
do
{
try
{
return $service->deleteByQuery($rawQuery, $fromPending, $fromCommitted, $timeout);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectWriteService(true);
} while ($service);
return false;
}
/**
* Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how
* to use Solr Cell and what parameters are available.
*
* NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost."
* as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value
* pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also
* pass in a document isntance with an "id" field" - the document's value(s) will take precedence).
*
* @param string $file Path to file to extract data from
* @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation)
* @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params)
* @param string $mimetype optional mimetype specification (for the file being extracted)
*
* @return Apache_Solr_Response
*
* @throws Apache_Solr_InvalidArgumentException if $file, $params, or $document are invalid.
*/
public function extract($file, $params = array(), $document = null, $mimetype = 'application/octet-stream')
{
$service = $this->_selectWriteService();
do
{
try
{
return $service->extract($file, $params, $document, $mimetype);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectWriteService(true);
} while ($service);
return false;
}
/**
* Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how
* to use Solr Cell and what parameters are available.
*
* NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost."
* as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value
* pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also
* pass in a document isntance with an "id" field" - the document's value(s) will take precedence).
*
* @param string $data Data that will be passed to Solr Cell
* @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation)
* @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params)
* @param string $mimetype optional mimetype specification (for the file being extracted)
*
* @return Apache_Solr_Response
*
* @throws Apache_Solr_InvalidArgumentException if $file, $params, or $document are invalid.
*
* @todo Should be using multipart/form-data to post parameter values, but I could not get my implementation to work. Needs revisisted.
*/
public function extractFromString($data, $params = array(), $document = null, $mimetype = 'application/octet-stream')
{
$service = $this->_selectWriteService();
do
{
try
{
return $service->extractFromString($data, $params, $document, $mimetype);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectWriteService(true);
} while ($service);
return false;
}
/**
* Send an optimize command. Will be synchronous unless both wait parameters are set
* to false.
*
* @param boolean $waitFlush
* @param boolean $waitSearcher
* @param float $timeout Maximum expected duration of the optimize operation on the server (otherwise, will throw a communication exception)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function optimize($waitFlush = true, $waitSearcher = true, $timeout = 3600)
{
$service = $this->_selectWriteService();
do
{
try
{
return $service->optimize($waitFlush, $waitSearcher, $timeout);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectWriteService(true);
} while ($service);
return false;
}
/**
* Simple Search interface
*
* @param string $query The raw query string
* @param int $offset The starting offset for result documents
* @param int $limit The maximum number of result documents to return
* @param array $params key / value pairs for query parameters, use arrays for multivalued parameters
* @param string $method The HTTP method (Apache_Solr_Service::METHOD_GET or Apache_Solr_Service::METHOD::POST)
* @return Apache_Solr_Response
*
* @throws Apache_Solr_HttpTransportException If an error occurs during the service call
*/
public function search($query, $offset = 0, $limit = 10, $params = array(), $method = Apache_Solr_Service::METHOD_GET)
{
$service = $this->_selectReadService();
do
{
try
{
return $service->search($query, $offset, $limit, $params, $method);
}
catch (Apache_Solr_HttpTransportException $e)
{
if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR
{
throw $e;
}
}
$service = $this->_selectReadService(true);
} while ($service);
return false;
}
}

26
thirdparty/solr-php-client/COPYING vendored Normal file
View File

@ -0,0 +1,26 @@
Copyright (c) 2007-2011, Servigistics, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Servigistics, Inc. nor the names of
its contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,439 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Apache_Solr_Document Unit Test
*/
class Apache_Solr_DocumentTest extends PHPUnit_Framework_TestCase
{
/**
* Fixture used for testing
*
* @var Apache_Solr_Document
*/
private $_fixture;
/**
* Setup for the fixture before each unit test - part of test case API
*/
protected function setup()
{
$this->_fixture = new Apache_Solr_Document();
}
/**
* Teardown after each unit test - part of test case API
*/
protected function tearDown()
{
unset($this->_fixture);
}
public function testDefaultStateAfterConstructor()
{
// document boost should be false
$this->assertFalse($this->_fixture->getBoost());
// document fields should be empty
$this->assertEquals(0, count($this->_fixture->getFieldNames()));
$this->assertEquals(0, count($this->_fixture->getFieldValues()));
$this->assertEquals(0, count($this->_fixture->getFieldBoosts()));
// document iterator should be empty
$this->assertEquals(0, iterator_count($this->_fixture));
}
public function testSetAndGetField()
{
$field = 'field';
$value = 'value';
$boost = 0.5;
// set the field
$this->_fixture->setField($field, $value, $boost);
$result = $this->_fixture->getField($field);
// check the array values
$this->assertTrue(is_array($result));
$this->assertEquals($field, $result['name']);
$this->assertEquals($value, $result['value']);
$this->assertEquals($boost, $result['boost']);
}
public function testGetFieldReturnsFalseForNonExistentField()
{
$this->assertFalse($this->_fixture->getField('field'));
}
public function testMagicGetForFieldValues()
{
$field = 'field';
$value = 'value';
$this->_fixture->setField($field, $value);
// test the __get value
$this->assertEquals($value, $this->_fixture->{$field});
}
/**
* Added for issue #48 (http://code.google.com/p/solr-php-client/issues/detail?id=48)
*/
public function testMagicGetReturnsNullForNonExistentField()
{
$this->assertNull($this->_fixture->nonExistent);
}
public function testMagicSetForFieldValues()
{
$field = 'field';
$value = 'value';
// set field value with magic __set
$this->_fixture->{$field} = $value;
$fieldArray = $this->_fixture->getField($field);
// set values
$this->assertEquals($field, $fieldArray['name']);
$this->assertEquals($value, $fieldArray['value']);
$this->assertTrue($fieldArray['boost'] === false);
}
public function testMagicIssetForNonExistentField()
{
$this->assertFalse(isset($this->_fixture->field));
}
public function testMagicIssetForExistingField()
{
$field = 'field';
$this->_fixture->{$field} = 'value';
$this->assertTrue(isset($this->_fixture->{$field}));
}
public function testMagicUnsetForExistingField()
{
$field = 'field';
$this->_fixture->{$field} = 'value';
// now unset the field
unset($this->_fixture->{$field});
// now test that its unset
$this->assertFalse(isset($this->_fixture->{$field}));
}
public function testMagicUnsetForNonExistingField()
{
$field = 'field';
unset($this->_fixture->{$field});
// now test that it still does not exist
$this->assertFalse(isset($this->_fixture->{$field}));
}
public function testSetAndGetFieldBoostWithPositiveNumberSetsBoost()
{
$field = 'field';
$boost = 0.5;
$this->_fixture->setFieldBoost($field, $boost);
// test the field boost
$this->assertEquals($boost, $this->_fixture->getFieldBoost($field));
}
public function testSetAndGetFieldBoostWithZeroRemovesBoost()
{
$field = 'field';
$boost = 0;
$this->_fixture->setFieldBoost($field, $boost);
// test the field boost
$this->assertTrue($this->_fixture->getFieldBoost($field) === false);
}
public function testSetAndGetFieldBoostWithNegativeNumberRemovesBoost()
{
$field = 'field';
$boost = -1;
$this->_fixture->setFieldBoost($field, $boost);
// test the field boost
$this->assertTrue($this->_fixture->getFieldBoost($field) === false);
}
public function testSetAndGetFieldBoostWithNonNumberRemovesBoost()
{
$field = 'field';
$boost = "i am not a number";
$this->_fixture->setFieldBoost($field, $boost);
// test the field boost
$this->assertTrue($this->_fixture->getFieldBoost($field) === false);
}
public function testSetAndGetBoostWithPositiveNumberSetsBoost()
{
$boost = 0.5;
$this->_fixture->setBoost($boost);
// the boost should now be set
$this->assertEquals($boost, $this->_fixture->getBoost());
}
public function testSetAndGetBoostWithZeroRemovesBoost()
{
$this->_fixture->setBoost(0);
// should be boolean false
$this->assertTrue($this->_fixture->getBoost() === false);
}
public function testSetAndGetBoostWithNegativeNumberRemovesBoost()
{
$this->_fixture->setBoost(-1);
// should be boolean false
$this->assertTrue($this->_fixture->getBoost() === false);
}
public function testSetAndGetBoostWithNonNumberRemovesBoost()
{
$this->_fixture->setBoost("i am not a number");
// should be boolean false
$this->assertTrue($this->_fixture->getBoost() === false);
}
public function testAddFieldCreatesMultiValueWhenFieldDoesNotExist()
{
$field = 'field';
$value = 'value';
$this->_fixture->addField($field, $value);
// check that value is an array with correct values
$fieldValue = $this->_fixture->{$field};
$this->assertTrue(is_array($fieldValue));
$this->assertEquals(1, count($fieldValue));
$this->assertEquals($value, $fieldValue[0]);
}
/**
* setMultiValue has been deprecated and defers to addField
*
* @deprecated
*/
public function testSetMultiValueCreateMultiValueWhenFieldDoesNotExist()
{
$field = 'field';
$value = 'value';
$this->_fixture->setMultiValue($field, $value);
// check that value is an array with correct values
$fieldValue = $this->_fixture->{$field};
$this->assertTrue(is_array($fieldValue));
$this->assertEquals(1, count($fieldValue));
$this->assertEquals($value, $fieldValue[0]);
}
public function testAddFieldCreatesMultiValueWhenFieldDoesExistAsSingleValue()
{
$field = 'field';
$value1 = 'value1';
$value2 = 'value2';
// set first value as singular value
$this->_fixture->{$field} = $value1;
// add a second value with addField
$this->_fixture->addField($field, $value2);
// check that value is an array with correct values
$fieldValue = $this->_fixture->{$field};
$this->assertTrue(is_array($fieldValue));
$this->assertEquals(2, count($fieldValue));
$this->assertEquals($value1, $fieldValue[0]);
$this->assertEquals($value2, $fieldValue[1]);
}
/**
* setMultiValue has been deprecated and defers to addField
*
* @deprecated
*/
public function testSetMultiValueCreatesMultiValueWhenFieldDoesExistAsSingleValue()
{
$field = 'field';
$value1 = 'value1';
$value2 = 'value2';
// set first value as singular value
$this->_fixture->{$field} = $value1;
// add a second value with addField
$this->_fixture->setMultiValue($field, $value2);
// check that value is an array with correct values
$fieldValue = $this->_fixture->{$field};
$this->assertTrue(is_array($fieldValue));
$this->assertEquals(2, count($fieldValue));
$this->assertEquals($value1, $fieldValue[0]);
$this->assertEquals($value2, $fieldValue[1]);
}
public function testAddFieldWithBoostSetsFieldBoost()
{
$field = 'field';
$boost = 0.5;
$this->_fixture->addField($field, 'value', $boost);
// check the field boost
$this->assertEquals($boost, $this->_fixture->getFieldBoost($field));
}
public function testAddFieldWithBoostMultipliesWithAPreexistingBoost()
{
$field = 'field';
$boost = 0.5;
// set a field with a boost
$this->_fixture->setField($field, 'value1', $boost);
// now add another value with the same boost
$this->_fixture->addField($field, 'value2', $boost);
// new boost should be $boost * $boost
$this->assertEquals($boost * $boost, $this->_fixture->getFieldBoost($field));
}
public function testGetFieldNamesIsInitiallyEmpty()
{
$fieldNames = $this->_fixture->getFieldNames();
$this->assertTrue(empty($fieldNames));
}
public function testGetFieldNamesAfterFieldIsSetIsNotEmpty()
{
$field = 'field';
$this->_fixture->{$field} = 'value';
$fieldNames = $this->_fixture->getFieldNames();
$this->assertTrue(!empty($fieldNames));
$this->assertEquals(1, count($fieldNames));
$this->assertEquals($field, $fieldNames[0]);
}
public function testGetFieldValuesIsInitiallyEmpty()
{
$fieldValues = $this->_fixture->getFieldValues();
$this->assertTrue(empty($fieldValues));
}
public function testGetFieldValuessAfterFieldIsSetIsNotEmpty()
{
$value = 'value';
$this->_fixture->field = $value;
$fieldValues = $this->_fixture->getFieldValues();
$this->assertTrue(!empty($fieldValues));
$this->assertEquals(1, count($fieldValues));
$this->assertEquals($value, $fieldValues[0]);
}
public function testGetIteratorAfterFieldValueIsSet()
{
$field = 'field';
$value = 'value';
$this->_fixture->{$field} = $value;
$itemCount = 0;
foreach ($this->_fixture as $iteratedField => $iteratedValue)
{
++$itemCount;
// test field and value
$this->assertEquals($field, $iteratedField);
$this->assertEquals($value, $iteratedValue);
}
// test number of iterations is 1
$this->assertEquals(1, $itemCount);
}
public function testClearReturnsDocumentToDefaultState()
{
// set the document boost
$this->_fixture->setBoost(0.5);
// set a field
$this->_fixture->someField = "some value";
// clear the document to remove boost and fields
$this->_fixture->clear();
// document boost should now be false
$this->assertFalse($this->_fixture->getBoost());
// document fields should now be empty
$this->assertEquals(0, count($this->_fixture->getFieldNames()));
$this->assertEquals(0, count($this->_fixture->getFieldValues()));
$this->assertEquals(0, count($this->_fixture->getFieldBoosts()));
// document iterator should now be empty
$this->assertEquals(0, iterator_count($this->_fixture));
}
}

View File

@ -0,0 +1,208 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Apache_Solr_HttpTransport_Abstract Unit Tests
*/
abstract class Apache_Solr_HttpTransport_AbstractTest extends PHPUnit_Framework_TestCase
{
const TIMEOUT = 2;
// request our copyright file from googlecode for GET and HEAD
const GET_URL = "http://solr-php-client.googlecode.com/svn/trunk/COPYING";
const GET_RESPONSE_MIME_TYPE = 'text/plain';
const GET_RESPONSE_ENCODING = 'UTF-8';
const GET_RESPONSE_MATCH = 'Copyright (c) ';
// post to the issue list page with a search for 'meh'
const POST_URL = "http://code.google.com/p/solr-php-client/issues/list";
const POST_DATA = "can=2&q=meh&colspec=ID+Type+Status+Priority+Milestone+Owner+Summary&cells=tiles";
const POST_REQUEST_CONTENT_TYPE = 'application/x-www-form-urlencoded; charset=UTF-8';
const POST_RESPONSE_MIME_TYPE = 'text/html';
const POST_RESPONSE_ENCODING = 'UTF-8';
//const POST_RESPONSE_MATCH = 'not sure';
abstract public function getFixture();
public function testGetDefaultTimeoutWithDefaultConstructor()
{
$fixture = $this->getFixture();
$timeout = $fixture->getDefaultTimeout();
$this->assertGreaterThan(0, $timeout);
}
public function testGetDefaultTimeoutSetToSixtyForBadValues()
{
// first set our default_socket_timeout ini setting
$previousValue = ini_get('default_socket_timeout');
ini_set('default_socket_timeout', 0);
$fixture = $this->getFixture();
$timeout = $fixture->getDefaultTimeout();
// reset timeout
ini_set('default_socket_timeout', $previousValue);
$this->assertEquals(60, $timeout);
}
public function testSetDefaultTimeout()
{
$newTimeout = 1234;
$fixture = $this->getFixture();
$fixture->setDefaultTimeout($newTimeout);
$timeout = $fixture->getDefaultTimeout();
$this->assertEquals($newTimeout, $timeout);
}
public function testPerformGetRequest()
{
$fixture = $this->getFixture();
$fixture->setDefaultTimeout(self::TIMEOUT);
$response = $fixture->performGetRequest(self::GET_URL);
$this->assertType('Apache_Solr_HttpTransport_Response', $response);
$this->assertEquals(200, $response->getStatusCode(), 'Status code was not 200');
$this->assertEquals(self::GET_RESPONSE_MIME_TYPE, $response->getMimeType(), 'mimetype was not correct');
$this->assertEquals(self::GET_RESPONSE_ENCODING, $response->getEncoding(), 'character encoding was not correct');
$this->assertStringStartsWith(self::GET_RESPONSE_MATCH, $response->getBody(), 'body did not start with match text');
}
public function testPerformGetRequestWithTimeout()
{
$fixture = $this->getFixture();
$response = $fixture->performGetRequest(self::GET_URL, self::TIMEOUT);
$this->assertType('Apache_Solr_HttpTransport_Response', $response);
$this->assertEquals(200, $response->getStatusCode(), 'Status code was not 200');
$this->assertEquals(self::GET_RESPONSE_MIME_TYPE, $response->getMimeType(), 'mimetype was not correct');
$this->assertEquals(self::GET_RESPONSE_ENCODING, $response->getEncoding(), 'character encoding was not correct');
$this->assertStringStartsWith(self::GET_RESPONSE_MATCH, $response->getBody(), 'body did not start with match text');
}
public function testPerformHeadRequest()
{
$fixture = $this->getFixture();
$fixture->setDefaultTimeout(self::TIMEOUT);
$response = $fixture->performHeadRequest(self::GET_URL);
// we should get everything the same as a get, except the body
$this->assertType('Apache_Solr_HttpTransport_Response', $response);
$this->assertEquals(200, $response->getStatusCode(), 'Status code was not 200');
$this->assertEquals(self::GET_RESPONSE_MIME_TYPE, $response->getMimeType(), 'mimetype was not correct');
$this->assertEquals(self::GET_RESPONSE_ENCODING, $response->getEncoding(), 'character encoding was not correct');
$this->assertEquals("", $response->getBody(), 'body was not empty');
}
public function testPerformHeadRequestWithTimeout()
{
$fixture = $this->getFixture();
$response = $fixture->performHeadRequest(self::GET_URL, self::TIMEOUT);
// we should get everything the same as a get, except the body
$this->assertType('Apache_Solr_HttpTransport_Response', $response);
$this->assertEquals(200, $response->getStatusCode(), 'Status code was not 200');
$this->assertEquals(self::GET_RESPONSE_MIME_TYPE, $response->getMimeType(), 'mimetype was not correct');
$this->assertEquals(self::GET_RESPONSE_ENCODING, $response->getEncoding(), 'character encoding was not correct');
$this->assertEquals("", $response->getBody(), 'body was not empty');
}
public function testPerformPostRequest()
{
$fixture = $this->getFixture();
$fixture->setDefaultTimeout(self::TIMEOUT);
$response = $fixture->performPostRequest(self::POST_URL, self::POST_DATA, self::POST_REQUEST_CONTENT_TYPE);
$this->assertType('Apache_Solr_HttpTransport_Response', $response);
$this->assertEquals(200, $response->getStatusCode(), 'Status code was not 200');
$this->assertEquals(self::POST_RESPONSE_MIME_TYPE, $response->getMimeType(), 'mimetype was not correct');
$this->assertEquals(self::POST_RESPONSE_ENCODING, $response->getEncoding(), 'character encoding was not correct');
//$this->assertStringStartsWith(self::POST_RESPONSE_MATCH, $response->getBody(), 'body did not start with match text');
}
public function testPerformPostRequestWithTimeout()
{
$fixture = $this->getFixture();
$response = $fixture->performPostRequest(self::POST_URL, self::POST_DATA, self::POST_REQUEST_CONTENT_TYPE, self::TIMEOUT);
$this->assertType('Apache_Solr_HttpTransport_Response', $response);
$this->assertEquals(200, $response->getStatusCode(), 'Status code was not 200');
$this->assertEquals(self::POST_RESPONSE_MIME_TYPE, $response->getMimeType(), 'mimetype was not correct');
$this->assertEquals(self::POST_RESPONSE_ENCODING, $response->getEncoding(), 'character encoding was not correct');
//$this->assertStringStartsWith(self::POST_RESPONSE_MATCH, $response->getBody(), 'body did not start with match text');
}
/**
* Test one session doing multiple requests in multiple orders
*/
public function testMultipleRequests()
{
// initial get request
$this->testPerformGetRequest();
// head following get
$this->testPerformHeadRequest();
// post following head
$this->testPerformPostRequest();
// get following post
$this->testPerformGetRequest();
// post following get
$this->testPerformPostRequest();
// head following post
$this->testPerformHeadRequest();
// get following post
$this->testPerformGetRequest();
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Apache_Solr_HttpTransport_CurlNoReuse Unit Tests
*/
class Apache_Solr_HttpTransport_CurlNoReuseTest extends Apache_Solr_HttpTransport_AbstractTest
{
public function getFixture()
{
// ensure curl is enabled
if (!extension_loaded('curl'))
{
$this->markTestSkipped("curl module is not enabled");
}
return new Apache_Solr_HttpTransport_CurlNoReuse();
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Apache_Solr_HttpTransport_Curl Unit Tests
*/
class Apache_Solr_HttpTransport_CurlTest extends Apache_Solr_HttpTransport_AbstractTest
{
public function getFixture()
{
// ensure curl is enabled
if (!extension_loaded('curl'))
{
$this->markTestSkipped("curl module is not enabled");
}
return new Apache_Solr_HttpTransport_Curl();
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Apache_Solr_HttpTransport_FileGetContents Unit Tests
*/
class Apache_Solr_HttpTransport_FileGetContentsTest extends Apache_Solr_HttpTransport_AbstractTest
{
public function getFixture()
{
// make sure allow_url_fopen is on
if (!ini_get("allow_url_fopen"))
{
$this->markTestSkipped("allow_url_fopen is not enabled");
}
return new Apache_Solr_HttpTransport_FileGetContents();
}
}

View File

@ -0,0 +1,164 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Apache_Solr_HttpTransport_Response Unit Tests
*/
class Apache_Solr_HttpTransport_ResponseTest extends PHPUnit_Framework_TestCase
{
// generated with the following query string: select?q=solr&wt=json
const STATUS_CODE_200 = 200;
const STATUS_MESSAGE_200 = "OK";
const BODY_200 = '{"responseHeader":{"status":0,"QTime":0,"params":{"q":"solr","wt":"json"}},"response":{"numFound":0,"start":0,"docs":[]}}';
const BODY_200_WITH_DOCUMENTS = '{"responseHeader":{"status":0,"QTime":0,"params":{"q":"*:*","wt":"json"}},"response":{"numFound":1,"start":0,"docs":[{"guid":"dev/2/products/45410/1236981","cit_domain":"products","cit_client":"2","cit_instance":"dev","cit_timestamp":"2010-10-06T18:16:51.573Z","product_code_t":["235784"],"product_id":[1236981],"dealer_id":[45410],"category_id":[1030],"manufacturer_id":[0],"vendor_id":[472],"catalog_id":[202]}]}}';
const CONTENT_TYPE_200 = "text/plain; charset=utf-8";
const MIME_TYPE_200 = "text/plain";
const ENCODING_200 = "utf-8";
// generated with the following query string: select?qt=standad&q=solr&wt=json
// NOTE: the intentional mispelling of the standard in the qt parameter
const STATUS_CODE_400 = 400;
const STATUS_MESSAGE_400 = "Bad Request";
const BODY_400 = '<html><head><title>Apache Tomcat/6.0.24 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 400 - unknown handler: standad</h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u>unknown handler: standad</u></p><p><b>description</b> <u>The request sent by the client was syntactically incorrect (unknown handler: standad).</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/6.0.24</h3></body></html>';
const CONTENT_TYPE_400 = "text/html; charset=utf-8";
const MIME_TYPE_400 = "text/html";
const ENCODING_400 = "utf-8";
// generated with the following query string: select?q=solr&wt=json on a core that does not exist
const STATUS_CODE_404 = 404;
const STATUS_MESSAGE_404 = "Not Found";
const BODY_404 = '<html><head><title>Apache Tomcat/6.0.24 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 404 - /solr/doesnotexist/select</h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u>/solr/doesnotexist/select</u></p><p><b>description</b> <u>The requested resource (/solr/doesnotexist/select) is not available.</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/6.0.24</h3></body></html>';
const CONTENT_TYPE_404 = "text/html; charset=utf-8";
const MIME_TYPE_404 = "text/html";
const ENCODING_404 = "utf-8";
public static function get0Response()
{
return new Apache_Solr_HttpTransport_Response(null, null, null);
}
public static function get200Response()
{
return new Apache_Solr_HttpTransport_Response(self::STATUS_CODE_200, self::CONTENT_TYPE_200, self::BODY_200);
}
public static function get200ResponseWithDocuments()
{
return new Apache_Solr_HttpTransport_Response(self::STATUS_CODE_200, self::CONTENT_TYPE_200, self::BODY_200_WITH_DOCUMENTS);
}
public static function get400Response()
{
return new Apache_Solr_HttpTransport_Response(self::STATUS_CODE_400, self::CONTENT_TYPE_400, self::BODY_400);
}
public static function get404Response()
{
return new Apache_Solr_HttpTransport_Response(self::STATUS_CODE_404, self::CONTENT_TYPE_404, self::BODY_404);
}
public function testGetStatusCode()
{
$fixture = self::get200Response();
$statusCode = $fixture->getStatusCode();
$this->assertEquals(self::STATUS_CODE_200, $statusCode);
}
public function testGetStatusMessage()
{
$fixture = self::get200Response();
$statusMessage = $fixture->getStatusMessage();
$this->assertEquals(self::STATUS_MESSAGE_200, $statusMessage);
}
public function testGetStatusMessageWithUnknownCode()
{
$fixture = new Apache_Solr_HttpTransport_Response(499, null, null);
$statusMessage = $fixture->getStatusMessage();
$this->assertEquals("Unknown Status", $statusMessage);
}
public function testGetBody()
{
$fixture = self::get200Response();
$body = $fixture->getBody();
$this->assertEquals(self::BODY_200, $body);
}
public function testGetMimeType()
{
$fixture = self::get200Response();
$mimeType = $fixture->getMimeType();
$this->assertEquals(self::MIME_TYPE_200, $mimeType);
}
public function testGetEncoding()
{
$fixture = self::get200Response();
$encoding = $fixture->getEncoding();
$this->assertEquals(self::ENCODING_200, $encoding);
}
public function testGetStatusMessageWhenNotProvided()
{
// test 4 of the most common status code responses, probably don't need
// to test all the codes we have
$fixture = new Apache_Solr_HttpTransport_Response(null, null, null, null, null);
$this->assertEquals("Communication Error", $fixture->getStatusMessage(), 'Did not get correct default status message for status code 0');
$fixture = new Apache_Solr_HttpTransport_Response(200, null, null, null, null);
$this->assertEquals("OK", $fixture->getStatusMessage(), 'Did not get correct default status message for status code 200');
$fixture = new Apache_Solr_HttpTransport_Response(400, null, null, null, null);
$this->assertEquals("Bad Request", $fixture->getStatusMessage(), 'Did not get correct default status message for status code 400');
$fixture = new Apache_Solr_HttpTransport_Response(404, null, null, null, null);
$this->assertEquals("Not Found", $fixture->getStatusMessage(), 'Did not get correct default status message for status code 404');
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Apache_Solr_HttpTransportException Unit Tests
*/
class Apache_Solr_HttpTransportExceptionTest extends PHPUnit_Framework_TestCase
{
/**
* @expectedException PHPUnit_Framework_Error
*/
public function testConstructorRequiresResponse()
{
$fixture = new Apache_Solr_HttpTransportException();
}
public function testGetResponse()
{
$response = Apache_Solr_ResponseTest::get0Response();
$fixture = new Apache_Solr_HttpTransportException($response);
$this->assertEquals($response, $fixture->getResponse());
}
}

View File

@ -0,0 +1,194 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Apache_Solr_Response Unit Test
*/
class Apache_Solr_ResponseTest extends PHPUnit_Framework_TestCase
{
static public function get0Response($createDocuments = true, $collapseSingleValueArrays = true)
{
return new Apache_Solr_Response(Apache_Solr_HttpTransport_ResponseTest::get0Response(), $createDocuments, $collapseSingleValueArrays);
}
static public function get200Response($createDocuments = true, $collapseSingleValueArrays = true)
{
return new Apache_Solr_Response(Apache_Solr_HttpTransport_ResponseTest::get200Response(), $createDocuments, $collapseSingleValueArrays);
}
static public function get200ResponseWithDocuments($createDocuments = true, $collapseSingleValueArrays = true)
{
return new Apache_Solr_Response(Apache_Solr_HttpTransport_ResponseTest::get200ResponseWithDocuments(), $createDocuments, $collapseSingleValueArrays);
}
static public function get400Response($createDocuments = true, $collapseSingleValueArrays = true)
{
return new Apache_Solr_Response(Apache_Solr_HttpTransport_ResponseTest::get400Response(), $createDocuments, $collapseSingleValueArrays);
}
static public function get404Response($createDocuments = true, $collapseSingleValueArrays = true)
{
return new Apache_Solr_Response(Apache_Solr_HttpTransport_ResponseTest::get404Response(), $createDocuments, $collapseSingleValueArrays);
}
public function testConstuctorWithValidBodyAndHeaders()
{
$fixture = self::get200Response();
// check that we parsed the HTTP status correctly
$this->assertEquals(Apache_Solr_HttpTransport_ResponseTest::STATUS_CODE_200, $fixture->getHttpStatus());
// check that we received the body correctly
$this->assertEquals(Apache_Solr_HttpTransport_ResponseTest::BODY_200, $fixture->getRawResponse());
// check that our defaults are correct
$this->assertEquals(Apache_Solr_HttpTransport_ResponseTest::ENCODING_200, $fixture->getEncoding());
$this->assertEquals(Apache_Solr_HttpTransport_ResponseTest::MIME_TYPE_200, $fixture->getType());
}
public function testConstructorWithBadBodyAndHeaders()
{
$fixture = self::get0Response();
// check that our defaults are correct
$this->assertEquals(0, $fixture->getHttpStatus());
$this->assertEquals("UTF-8", $fixture->getEncoding());
$this->assertEquals("text/plain", $fixture->getType());
}
public function testMagicGetWithValidBodyAndHeaders()
{
$fixture = self::get200Response();
// test top level gets
$this->assertType('stdClass', $fixture->responseHeader);
$this->assertEquals(0, $fixture->responseHeader->status);
$this->assertEquals(0, $fixture->responseHeader->QTime);
$this->assertType('stdClass', $fixture->response);
$this->assertEquals(0, $fixture->response->numFound);
$this->assertTrue(is_array($fixture->response->docs));
$this->assertEquals(0, count($fixture->response->docs));
}
/**
* @expectedException Apache_Solr_ParserException
*/
public function testMagicGetWith0Response()
{
$fixture = self::get0Response();
// attempting to magic get a part of the response
// should throw a ParserException
$fixture->responseHeader;
$this->fail("Expected Apache_Solr_ParserException was not raised");
}
/**
* @expectedException Apache_Solr_ParserException
*/
public function testMagicGetWith400Response()
{
$fixture = self::get400Response();
// attempting to magic get a part of the response
// should throw a ParserException
$fixture->responseHeader;
$this->fail("Expected Apache_Solr_ParserException was not raised");
}
/**
* @expectedException Apache_Solr_ParserException
*/
public function testMagicGetWith404Response()
{
$fixture = self::get404Response();
// attempting to magic get a part of the response
// should throw a ParserException
$fixture->responseHeader;
$this->fail("Expected Apache_Solr_ParserException was not raised");
}
public function testCreateDocuments()
{
$fixture = self::get200ResponseWithDocuments();
$this->assertTrue(count($fixture->response->docs) > 0, 'There are not 1 or more documents, cannot test');
$this->assertType('Apache_Solr_Document', $fixture->response->docs[0], 'The first document is not of type Apache_Solr_Document');
}
public function testDontCreateDocuments()
{
$fixture = self::get200ResponseWithDocuments(false);
$this->assertTrue(count($fixture->response->docs) > 0, 'There are not 1 or more documents, cannot test');
$this->assertType('stdClass', $fixture->response->docs[0], 'The first document is not of type stdClass');
}
public function testGetHttpStatusMessage()
{
$fixture = self::get200Response();
$this->assertEquals("OK", $fixture->getHttpStatusMessage());
}
public function testMagicGetReturnsNullForUndefinedData()
{
$fixture = self::get200Response();
$this->assertNull($fixture->doesnotexist);
}
public function testMagicIssetForDefinedProperty()
{
$fixture = self::get200Response();
$this->assertTrue(isset($fixture->responseHeader));
}
public function testMagicIssetForUndefinedProperty()
{
$fixture = self::get200Response();
$this->assertFalse(isset($fixture->doesnotexist));
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Apache_Solr_Service_Balancer Unit Tests
*/
class Apache_Solr_Service_BalancerTest extends Apache_Solr_ServiceAbstractTest
{
public function getFixture()
{
return new Apache_Solr_Service_Balancer();
}
}

View File

@ -0,0 +1,139 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Provides base funcationality test for both Apache_Solr_Service and the
* Apache_Solr_Service_Balancer classes.
*/
abstract class Apache_Solr_ServiceAbstractTest extends PHPUnit_Framework_TestCase
{
/**
* Method that gets the appropriate instance for testing
*/
abstract public function getFixture();
/**
* @dataProvider testEscapeDataProvider
*/
public function testEscape($input, $expectedOutput)
{
$fixture = $this->getFixture();
$this->assertEquals($expectedOutput, $fixture->escape($input));
}
public function testEscapeDataProvider()
{
return array(
array(
"I should look the same",
"I should look the same"
),
array(
"(There) are: ^lots \\ && of spec!al charaters",
"\\(There\\) are\\: \\^lots \\\\ \\&& of spec\\!al charaters"
)
);
}
/**
* @dataProvider testEscapePhraseDataProvider
*/
public function testEscapePhrase($input, $expectedOutput)
{
$fixture = $this->getFixture();
$this->assertEquals($expectedOutput, $fixture->escapePhrase($input));
}
public function testEscapePhraseDataProvider()
{
return array(
array(
"I'm a simple phrase",
"I'm a simple phrase"
),
array(
"I have \"phrase\" characters",
'I have \\"phrase\\" characters'
)
);
}
/**
* @dataProvider testPhraseDataProvider
*/
public function testPhrase($input, $expectedOutput)
{
$fixture = $this->getFixture();
$this->assertEquals($expectedOutput, $fixture->phrase($input));
}
public function testPhraseDataProvider()
{
return array(
array(
"I'm a simple phrase",
'"I\'m a simple phrase"'
),
array(
"I have \"phrase\" characters",
'"I have \\"phrase\\" characters"'
)
);
}
public function testGetCreateDocumentWithDefaultConstructor()
{
$fixture = $this->getFixture();
$this->assertTrue($fixture->getCreateDocuments());
}
public function testSetCreateDocuments()
{
$fixture = $this->getFixture();
$fixture->setCreateDocuments(false);
$this->assertFalse($fixture->getCreateDocuments());
}
}

View File

@ -0,0 +1,1119 @@
<?php
/**
* Copyright (c) 2007-2011, Servigistics, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Servigistics, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
* @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
*
* @package Apache
* @subpackage Solr
* @author Donovan Jimenez <djimenez@conduit-it.com>
*/
/**
* Apache_Solr_Service Unit Test
*/
class Apache_Solr_ServiceTest extends Apache_Solr_ServiceAbstractTest
{
public function getFixture()
{
return new Apache_Solr_Service();
}
public function getMockHttpTransportInterface()
{
return $this->getMock(
'Apache_Solr_HttpTransport_Interface',
array(
'getDefaultTimeout',
'setDefaultTimeout',
'performGetRequest',
'performHeadRequest',
'performPostRequest',
)
);
}
//================================================================//
// ATTEMPT TO MOVE THESE TO ServiceAbstractTest AT SOME POINT //
// Apache_Solr_Service_Balancer will need functions added //
//================================================================//
public function testGetHttpTransportWithDefaultConstructor()
{
$fixture = new Apache_Solr_Service();
$httpTransport = $fixture->getHttpTransport();
$this->assertInstanceOf('Apache_Solr_HttpTransport_Interface', $httpTransport, 'Default http transport does not implement interface');
$this->assertInstanceOf('Apache_Solr_HttpTransport_FileGetContents', $httpTransport, 'Default http transport is not URL Wrapper implementation');
}
public function testSetHttpTransport()
{
$newTransport = new Apache_Solr_HttpTransport_Curl();
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($newTransport);
$httpTransport = $fixture->getHttpTransport();
$this->assertInstanceOf('Apache_Solr_HttpTransport_Interface', $httpTransport);
$this->assertInstanceOf('Apache_Solr_HttpTransport_Curl', $httpTransport);
$this->assertEquals($newTransport, $httpTransport);
}
public function testSetHttpTransportWithConstructor()
{
$newTransport = new Apache_Solr_HttpTransport_Curl();
$fixture = new Apache_Solr_Service('localhost', 8180, '/solr/', $newTransport);
$fixture->setHttpTransport($newTransport);
$httpTransport = $fixture->getHttpTransport();
$this->assertInstanceOf('Apache_Solr_HttpTransport_Interface', $httpTransport);
$this->assertInstanceOf('Apache_Solr_HttpTransport_Curl', $httpTransport);
$this->assertEquals($newTransport, $httpTransport);
}
public function testGetCollapseSingleValueArraysWithDefaultConstructor()
{
$fixture = $this->getFixture();
$this->assertTrue($fixture->getCollapseSingleValueArrays());
}
public function testSetCollapseSingleValueArrays()
{
$fixture = $this->getFixture();
$fixture->setCollapseSingleValueArrays(false);
$this->assertFalse($fixture->getCollapseSingleValueArrays());
}
public function testGetNamedListTreatmetnWithDefaultConstructor()
{
$fixture = $this->getFixture();
$this->assertEquals(Apache_Solr_Service::NAMED_LIST_MAP, $fixture->getNamedListTreatment());
}
public function testSetNamedListTreatment()
{
$fixture = $this->getFixture();
$fixture->setNamedListTreatment(Apache_Solr_Service::NAMED_LIST_FLAT);
$this->assertEquals(Apache_Solr_Service::NAMED_LIST_FLAT, $fixture->getNamedListTreatment());
$fixture->setNamedListTreatment(Apache_Solr_Service::NAMED_LIST_MAP);
$this->assertEquals(Apache_Solr_Service::NAMED_LIST_MAP, $fixture->getNamedListTreatment());
}
/**
* @expectedException Apache_Solr_InvalidArgumentException
*/
public function testSetNamedListTreatmentInvalidArgumentException()
{
$fixture = $this->getFixture();
$fixture->setNamedListTreatment("broken");
}
//================================================================//
// END SECTION OF CODE THAT SHOULD BE MOVED //
// Apache_Solr_Service_Balancer will need functions added //
//================================================================//
public function testConstructorDefaultArguments()
{
$fixture = new Apache_Solr_Service();
$this->assertInstanceOf('Apache_Solr_Service', $fixture);
}
public function testGetHostWithDefaultConstructor()
{
$fixture = new Apache_Solr_Service();
$host = $fixture->getHost();
$this->assertEquals("localhost", $host);
}
public function testSetHost()
{
$newHost = "example.com";
$fixture = new Apache_Solr_Service();
$fixture->setHost($newHost);
$host = $fixture->getHost();
$this->assertEquals($newHost, $host);
}
/**
* @expectedException Apache_Solr_InvalidArgumentException
*/
public function testSetEmptyHost()
{
$fixture = new Apache_Solr_Service();
// should throw an invalid argument exception
$fixture->setHost("");
}
public function testSetHostWithConstructor()
{
$newHost = "example.com";
$fixture = new Apache_Solr_Service($newHost);
$host = $fixture->getHost();
$this->assertEquals($newHost, $host);
}
public function testGetPortWithDefaultConstructor()
{
$fixture = new Apache_Solr_Service();
$port = $fixture->getPort();
$this->assertEquals(8180, $port);
}
public function testSetPort()
{
$newPort = 12345;
$fixture = new Apache_Solr_Service();
$fixture->setPort($newPort);
$port = $fixture->getPort();
$this->assertEquals($newPort, $port);
}
/**
* @expectedException Apache_Solr_InvalidArgumentException
*/
public function testSetPortWithInvalidArgument()
{
$fixture = new Apache_Solr_Service();
$fixture->setPort("broken");
}
public function testSetPortWithConstructor()
{
$newPort = 12345;
$fixture = new Apache_Solr_Service('locahost', $newPort);
$port = $fixture->getPort();
$this->assertEquals($newPort, $port);
}
public function testGetPathWithDefaultConstructor()
{
$fixture = new Apache_Solr_Service();
$path = $fixture->getPath();
$this->assertEquals("/solr/", $path);
}
public function testSetPath()
{
$newPath = "/new/path/";
$fixture = new Apache_Solr_Service();
$fixture->setPath($newPath);
$path = $fixture->getPath();
$this->assertEquals($path, $newPath);
}
public function testSetPathWillAddContainingSlashes()
{
$newPath = "new/path";
$containedPath = "/{$newPath}/";
$fixture = new Apache_Solr_Service();
$fixture->setPath($newPath);
$path = $fixture->getPath();
$this->assertEquals($containedPath, $path, 'setPath did not ensure propertly wrapped with slashes');
}
public function testSetPathWithConstructor()
{
$newPath = "/new/path/";
$fixture = new Apache_Solr_Service('localhost', 8180, $newPath);
$path = $fixture->getPath();
$this->assertEquals($newPath, $path);
}
public function testGetDefaultTimeoutCallsThroughToTransport()
{
$fixture = new Apache_Solr_Service();
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call
$mockTransport->expects($this->once())->method('getDefaultTimeout');
$fixture->setHttpTransport($mockTransport);
$fixture->getDefaultTimeout();
}
public function testSetDefaultTimeoutCallsThroughToTransport()
{
$timeout = 12345;
$fixture = new Apache_Solr_Service();
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call
$mockTransport->expects($this->once())->method('setDefaultTimeout')->with($this->equalTo($timeout));
$fixture->setHttpTransport($mockTransport);
$fixture->setDefaultTimeout($timeout);
}
public function testPing()
{
$expectedUrl = "http://localhost:8180/solr/admin/ping";
$expectedTimeout = 2;
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performHeadRequest')
->with($this->equalTo($expectedUrl), $this->equalTo($expectedTimeout))
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
// call ping
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$time = $fixture->ping();
$this->assertGreaterThan(0, $time);
}
public function testPingReturnsFalse()
{
$expectedUrl = "http://localhost:8180/solr/admin/ping";
$expectedTimeout = 2;
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performHeadRequest')
->with($this->equalTo($expectedUrl), $this->equalTo($expectedTimeout))
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get0Response()));
// call ping
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$this->assertFalse($fixture->ping());
}
public function testThreads()
{
$expectedUrl = "http://localhost:8180/solr/admin/threads?wt=json";
$expectedTimeout = false;
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performGetRequest')
->with($this->equalTo($expectedUrl), $this->equalTo($expectedTimeout))
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
// call threads
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$fixture->threads();
}
/**
* @expectedException Apache_Solr_HttpTransportException
*/
public function testThreads404()
{
$expectedUrl = "http://localhost:8180/solr/admin/threads?wt=json";
$expectedTimeout = false;
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performGetRequest')
->with($this->equalTo($expectedUrl), $this->equalTo($expectedTimeout))
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get404Response()));
// call threads
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$fixture->threads();
}
public function testAdd()
{
$postData = "does not have to be valid";
$expectedUrl = "http://localhost:8180/solr/update?wt=json";
$expectedTimeout = false;
$expectedPostData = $postData;
$expectedContentType = "text/xml; charset=UTF-8"; // default for _sendRawPost
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with($this->equalTo($expectedUrl), $this->equalTo($postData), $this->equalTo($expectedContentType), $this->equalTo($expectedTimeout))
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
// call add
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$fixture->add($postData);
}
/**
* @expectedException Apache_Solr_HttpTransportException
*/
public function testAdd400()
{
$postData = "does not have to be valid";
$expectedUrl = "http://localhost:8180/solr/update?wt=json";
$expectedTimeout = false;
$expectedPostData = $postData;
$expectedContentType = "text/xml; charset=UTF-8"; // default for _sendRawPost
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with($this->equalTo($expectedUrl), $this->equalTo($postData), $this->equalTo($expectedContentType), $this->equalTo($expectedTimeout))
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get400Response()));
// call add
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$fixture->add($postData);
}
public function testAddDocument()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with(
// url
$this->equalTo('http://localhost:8180/solr/update?wt=json'),
// raw post
$this->equalTo('<add allowDups="false" overwritePending="true" overwriteCommitted="true"><doc></doc></add>'),
// content type
$this->equalTo('text/xml; charset=UTF-8'),
// timeout
$this->equalTo(false)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$document = new Apache_Solr_Document();
$fixture->addDocument($document);
}
public function testAddDocumentWithNonDefaultParameters()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with(
// url
$this->equalTo('http://localhost:8180/solr/update?wt=json'),
// raw post
$this->equalTo('<add allowDups="true" overwritePending="false" overwriteCommitted="false" commitWithin="3600"><doc></doc></add>'),
// content type
$this->equalTo('text/xml; charset=UTF-8'),
// timeout
$this->equalTo(false)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$document = new Apache_Solr_Document();
$fixture->addDocument($document, true, false, false, 3600);
}
public function testAddDocumentWithFields()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with(
// url
$this->equalTo('http://localhost:8180/solr/update?wt=json'),
// raw post
$this->equalTo('<add allowDups="false" overwritePending="true" overwriteCommitted="true"><doc><field name="guid">global unique id</field><field name="field">value</field><field name="multivalue">value 1</field><field name="multivalue">value 2</field></doc></add>'),
// content type
$this->equalTo('text/xml; charset=UTF-8'),
// timeout
$this->equalTo(false)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$document = new Apache_Solr_Document();
$document->guid = "global unique id";
$document->field = "value";
$document->multivalue = array("value 1", "value 2");
$fixture->addDocument($document);
}
public function testAddDocumentWithFieldBoost()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with(
// url
$this->equalTo('http://localhost:8180/solr/update?wt=json'),
// raw post
$this->equalTo('<add allowDups="false" overwritePending="true" overwriteCommitted="true"><doc><field name="guid">global unique id</field><field name="field" boost="2">value</field><field name="multivalue" boost="3">value 1</field><field name="multivalue">value 2</field></doc></add>'),
// content type
$this->equalTo('text/xml; charset=UTF-8'),
// timeout
$this->equalTo(false)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$document = new Apache_Solr_Document();
$document->guid = "global unique id";
$document->field = "value";
$document->setFieldBoost('field', 2);
$document->multivalue = array("value 1", "value 2");
$document->setFieldBoost('multivalue', 3);
$fixture->addDocument($document);
}
public function testAddDocumentWithDocumentBoost()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with(
// url
$this->equalTo('http://localhost:8180/solr/update?wt=json'),
// raw post
$this->equalTo('<add allowDups="false" overwritePending="true" overwriteCommitted="true"><doc boost="2"><field name="guid">global unique id</field><field name="field">value</field></doc></add>'),
// content type
$this->equalTo('text/xml; charset=UTF-8'),
// timeout
$this->equalTo(false)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$document = new Apache_Solr_Document();
$document->setBoost(2);
$document->guid = "global unique id";
$document->field = "value";
$fixture->addDocument($document);
}
public function testAddDocuments()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with(
// url
$this->equalTo('http://localhost:8180/solr/update?wt=json'),
// raw post
$this->equalTo('<add allowDups="false" overwritePending="true" overwriteCommitted="true"><doc></doc><doc></doc></add>'),
// content type
$this->equalTo('text/xml; charset=UTF-8'),
// timeout
$this->equalTo(false)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$documents = array(
new Apache_Solr_Document(),
new Apache_Solr_Document()
);
$fixture->addDocuments($documents);
}
public function testAddDocumentsWithNonDefaultParameters()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with(
// url
$this->equalTo('http://localhost:8180/solr/update?wt=json'),
// raw post
$this->equalTo('<add allowDups="true" overwritePending="false" overwriteCommitted="false" commitWithin="3600"><doc></doc><doc></doc></add>'),
// content type
$this->equalTo('text/xml; charset=UTF-8'),
// timeout
$this->equalTo(false)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$documents = array(
new Apache_Solr_Document(),
new Apache_Solr_Document()
);
$fixture->addDocuments($documents, true, false, false, 3600);
}
public function testCommit()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with(
// url
$this->equalTo('http://localhost:8180/solr/update?wt=json'),
// raw post
$this->equalTo('<commit expungeDeletes="false" waitFlush="true" waitSearcher="true" />'),
// content type
$this->equalTo('text/xml; charset=UTF-8'),
// timeout
$this->equalTo(3600)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->commit();
}
public function testCommitWithNonDefaultParameters()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with(
// url
$this->equalTo('http://localhost:8180/solr/update?wt=json'),
// raw post
$this->equalTo('<commit expungeDeletes="true" waitFlush="false" waitSearcher="false" />'),
// content type
$this->equalTo('text/xml; charset=UTF-8'),
// timeout
$this->equalTo(7200)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->commit(true, false, false, 7200);
}
public function testDelete()
{
$postData = "does not have to be valid";
$expectedUrl = "http://localhost:8180/solr/update?wt=json";
$expectedTimeout = 3600; // default for delete
$expectedPostData = $postData;
$expectedContentType = "text/xml; charset=UTF-8"; // default for _sendRawPost
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with($this->equalTo($expectedUrl), $this->equalTo($postData), $this->equalTo($expectedContentType), $this->equalTo($expectedTimeout))
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
// call add
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$fixture->delete($postData);
}
public function testDeleteById()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->deleteById("does not exist");
}
public function testDeleteByMultipleIds()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->deleteByMultipleIds(array(1, 2, 3));
}
public function testDeleteByQuery()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->deleteByQuery("*:*");
}
public function testExtracts()
{
$extractFile = __FILE__;
$expectedUrl = "http://localhost:8180/solr/update/extract?resource.name=ServiceTest.php&wt=json&json.nl=map";
$expectedPostData = file_get_contents($extractFile);
$expectedContentType = 'application/octet-stream'; // default for extract
$expectedTimeout = false;
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with($this->equalTo($expectedUrl), $this->equalTo($expectedPostData), $this->equalTo($expectedContentType), $this->equalTo($expectedTimeout))
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->extract($extractFile);
}
/**
* @expectedException Apache_Solr_InvalidArgumentException
*/
public function testExtractWithInvalidParams()
{
$extractFile = __FILE__;
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->extract($extractFile, "invalid");
}
/**
* @expectedException Apache_Solr_InvalidArgumentException
*/
public function testExtractFromStringWithInvalidParams()
{
$extractFileData = "does not matter what it is";
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->extractFromString($extractFileData, "invalid");
}
public function testExtractsWithNullParams()
{
$extractFile = __FILE__;
$expectedUrl = "http://localhost:8180/solr/update/extract?resource.name=ServiceTest.php&wt=json&json.nl=map";
$expectedPostData = file_get_contents($extractFile);
$expectedContentType = 'application/octet-stream'; // default for extract
$expectedTimeout = false;
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with($this->equalTo($expectedUrl), $this->equalTo($expectedPostData), $this->equalTo($expectedContentType), $this->equalTo($expectedTimeout))
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->extract($extractFile, null);
}
/**
* @expectedException Apache_Solr_InvalidArgumentException
*/
public function testExtractWithEmptyFile()
{
$extractFile = "iDontExist.txt";
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->extract($extractFile);
}
public function testExtractsWithDocument()
{
$extractFile = __FILE__;
$expectedUrl = "http://localhost:8180/solr/update/extract?resource.name=ServiceTest.php&wt=json&json.nl=map&boost.field=2&literal.field=literal+value";
$expectedPostData = file_get_contents($extractFile);
$expectedContentType = 'application/octet-stream'; // default for extract
$expectedTimeout = false;
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->with($this->equalTo($expectedUrl), $this->equalTo($expectedPostData), $this->equalTo($expectedContentType), $this->equalTo($expectedTimeout))
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$literals = new Apache_Solr_Document();
$literals->field = "literal value";
$literals->setFieldBoost('field', 2);
$fixture->extract($extractFile, null, $literals);
}
public function testExtractWithUrlDefers()
{
$extractUrl = "http://example.com";
$expectedUrl = "http://localhost:8180/solr/update/extract?resource.name=http%3A%2F%2Fexample.com&wt=json&json.nl=map";
$expectedPostData = Apache_Solr_HttpTransport_ResponseTest::BODY_200;
$expectedContentType = 'application/octet-stream'; // default for extract
$expectedTimeout = false;
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performGetRequest')
->with(
$this->equalTo($extractUrl)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$mockTransport->expects($this->once())
->method('performPostRequest')
->with(
$this->equalTo($expectedUrl),
$this->equalTo($expectedPostData),
$this->equalTo($expectedContentType),
$this->equalTo($expectedTimeout)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->extract($extractUrl);
}
public function testExtractFromUrl()
{
$extractUrl = "http://example.com";
$expectedUrl = "http://localhost:8180/solr/update/extract?resource.name=http%3A%2F%2Fexample.com&wt=json&json.nl=map";
$expectedPostData = Apache_Solr_HttpTransport_ResponseTest::BODY_200;
$expectedContentType = 'application/octet-stream'; // default for extract
$expectedTimeout = false;
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performGetRequest')
->with(
$this->equalTo($extractUrl)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$mockTransport->expects($this->once())
->method('performPostRequest')
->with(
$this->equalTo($expectedUrl),
$this->equalTo($expectedPostData),
$this->equalTo($expectedContentType),
$this->equalTo($expectedTimeout)
)
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->extractFromUrl($extractUrl);
}
/**
* @expectedException Apache_Solr_InvalidArgumentException
*/
public function testExtractFromUrlWithInvalidParams()
{
$extractUrl = "http://example.com";
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->extractFromUrl($extractUrl, "invalid");
}
public function testOptimize()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_Service();
$fixture->setHttpTransport($mockTransport);
$fixture->optimize();
}
public function testSearch()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performGetRequest')
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$fixture->search("solr");
}
/**
* @expectedException Apache_Solr_InvalidArgumentException
*/
public function testSearchWithInvalidParams()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$fixture->search("solr", 0, 10, "invalid");
$this->fail("Should have through InvalidArgumentException");
}
public function testSearchWithEmptyParams()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performGetRequest')
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$fixture->search("solr", 0, 10, null);
}
public function testSearchWithPostMethod()
{
// set a mock transport
$mockTransport = $this->getMockHttpTransportInterface();
// setup expected call and response
$mockTransport->expects($this->once())
->method('performPostRequest')
->will($this->returnValue(Apache_Solr_HttpTransport_ResponseTest::get200Response()));
$fixture = new Apache_Solr_service();
$fixture->setHttpTransport($mockTransport);
$fixture->search("solr", 0, 10, array(), Apache_Solr_Service::METHOD_POST);
}
/**
* @expectedException Apache_Solr_InvalidArgumentException
*/
public function testSearchWithInvalidMethod()
{
$fixture = new Apache_Solr_service();
$fixture->search("solr", 0, 10, array(), "INVALID METHOD");
}
}

20
thirdparty/solr-php-client/tests/README vendored Normal file
View File

@ -0,0 +1,20 @@
Use the run.php script included in this directory to run all unit tests for the
Solr PHP Client library. Your system will require phpunit PEAR package - which
you can get install instructions for at:
http://www.phpunit.de/
To generate the code coverage report, you will also need the XDebug pecl package
installed, typically this can be done with a simple:
pecl install xdebug
If you need more information on installation, then please see the official website:
http://www.xdebug.org
The scripts, configuration, and test files in this directory have been confirmed to
work with the following versions:
phpunit: 3.3.16
xdebug: 2.0.4

View File

@ -0,0 +1,28 @@
<?php
// set error reporting high
error_reporting(E_ALL | E_STRICT);
// make sure we see them
ini_set('display_errors', 'On');
// make sure current directory and class directories are on include path
// this is necessary for auto load to work
set_include_path(
// distribution files (where the zip / tgz is unpacked)
dirname(dirname(__FILE__)) . PATH_SEPARATOR .
// test file directory "tests"
dirname(__FILE__) . PATH_SEPARATOR .
// current include path (for PHPUnit, etc.)
get_include_path()
);
// set up an autoload for Zend / Pear style class loading
spl_autoload_register(
function($class)
{
include(str_replace("_", DIRECTORY_SEPARATOR, $class) . ".php");
}
);

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit bootstrap="phpunit.bootstrap.inc" colors="true">
<logging>
<log type="coverage-html" target="coverage-report" charset="UTF-8"/>
<!--<log type="testdox-text" target="testdox.txt" charset="UTF-8"/>-->
</logging>
<filter>
<whitelist>
<directory suffix=".php">../Apache</directory>
<exclude>
<file>./run.php</file>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,42 @@
#! /usr/bin/php
<?php
/**
* Copyright (c) 2007-2010, Conduit Internet Technologies, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Conduit Internet Technologies, Inc. nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// make sure the working directory is correct (parent directory)
// phpunit will use it to include our configuration files and find
// the following specified test suite
chdir(dirname(__FILE__));
// run phpunit - will automatically use ./phpunit.xml for configuration
// that configuration file points to a bootstrap that will include our test suite
passthru("phpunit .");
// extra newline so our next prompt isn't stuck appended to the output
echo "\n";

1069
thirdparty/solr/LICENSE.txt vendored Normal file
View File

@ -0,0 +1,1069 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache 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.apache.org/licenses/LICENSE-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.
==========================================================================
Portions of Jetty 6 are bundled in the Solr example server.
Jetty 6 includes a binary javax.servlet package licensed under the
Common Development and Distribution License.
--------------------------------------------------------------------------
COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
1. Definitions.
1.1. Contributor means each individual or entity that creates or contributes to
the creation of Modifications.
1.2. Contributor Version means the combination of the Original Software, prior
Modifications used by a Contributor (if any), and the Modifications made by
that particular Contributor.
1.3. Covered Software means (a) the Original Software, or (b) Modifications, or
(c) the combination of files containing Original Software with files containing
Modifications, in each case including portions thereof.
1.4. Executable means the Covered Software in any form other than Source Code.
1.5. Initial Developer means the individual or entity that first makes Original
Software available under this License.
1.6. Larger Work means a work which combines Covered Software or portions
thereof with code not governed by the terms of this License.
1.7. License means this document.
1.8. Licensable means having the right to grant, to the maximum extent
possible, whether at the time of the initial grant or subsequently acquired,
any and all of the rights conveyed herein.
1.9. Modifications means the Source Code and Executable form of any of the
following:
A. Any file that results from an addition to, deletion from or modification of
the contents of a file containing Original Software or previous Modifications;
B. Any new file that contains any part of the Original Software or previous
Modification; or
C. Any new file that is contributed or otherwise made available under the terms
of this License.
1.10. Original Software means the Source Code and Executable form of computer
software code that is originally released under this License.
1.11. Patent Claims means any patent claim(s), now owned or hereafter acquired,
including without limitation, method, process, and apparatus claims, in any
patent Licensable by grantor.
1.12. Source Code means (a) the common form of computer software code in which
modifications are made and (b) associated documentation included in or with
such code.
1.13. You (or Your) means an individual or a legal entity exercising rights
under, and complying with all of the terms of, this License. For legal
entities, You includes any entity which controls, is controlled by, or is under
common control with You. For purposes of this definition, control means (a) the
power, direct or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than fifty percent
(50%) of the outstanding shares or beneficial ownership of such entity.
2. License Grants.
2.1. The Initial Developer Grant. Conditioned upon Your compliance with
Section 3.1 below and subject to third party intellectual property claims, the
Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive
license: (a) under intellectual property rights (other than patent or
trademark) Licensable by Initial Developer, to use, reproduce, modify, display,
perform, sublicense and distribute the Original Software (or portions thereof),
with or without Modifications, and/or as part of a Larger Work; and (b) under
Patent Claims infringed by the making, using or selling of Original Software,
to make, have made, use, practice, sell, and offer for sale, and/or otherwise
dispose of the Original Software (or portions thereof). (c) The licenses
granted in Sections 2.1(a) and (b) are effective on the date Initial Developer
first distributes or otherwise makes the Original Software available to a third
party under the terms of this License. (d) Notwithstanding Section 2.1(b)
above, no patent license is granted: (1) for code that You delete from the
Original Software, or (2) for infringements caused by: (i) the modification of
the Original Software, or (ii) the combination of the Original Software with
other software or devices.
2.2. Contributor Grant. Conditioned upon Your compliance with Section 3.1
below and subject to third party intellectual property claims, each Contributor
hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under
intellectual property rights (other than patent or trademark) Licensable by
Contributor to use, reproduce, modify, display, perform, sublicense and
distribute the Modifications created by such Contributor (or portions thereof),
either on an unmodified basis, with other Modifications, as Covered Software
and/or as part of a Larger Work; and (b) under Patent Claims infringed by the
making, using, or selling of Modifications made by that Contributor either
alone and/or in combination with its Contributor Version (or portions of such
combination), to make, use, sell, offer for sale, have made, and/or otherwise
dispose of: (1) Modifications made by that Contributor (or portions thereof);
and (2) the combination of Modifications made by that Contributor with its
Contributor Version (or portions of such combination). (c) The licenses
granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor
first distributes or otherwise makes the Modifications available to a third
party. (d) Notwithstanding Section 2.2(b) above, no patent license is granted:
(1) for any code that Contributor has deleted from the Contributor Version;
(2) for infringements caused by: (i) third party modifications of Contributor
Version, or (ii) the combination of Modifications made by that Contributor with
other software (except as part of the Contributor Version) or other devices; or
(3) under Patent Claims infringed by Covered Software in the absence of
Modifications made by that Contributor.
3. Distribution Obligations.
3.1. Availability of Source Code.
Any Covered Software that You distribute or otherwise make available in
Executable form must also be made available in Source Code form and that Source
Code form must be distributed only under the terms of this License. You must
include a copy of this License with every copy of the Source Code form of the
Covered Software You distribute or otherwise make available. You must inform
recipients of any such Covered Software in Executable form as to how they can
obtain such Covered Software in Source Code form in a reasonable manner on or
through a medium customarily used for software exchange.
3.2. Modifications.
The Modifications that You create or to which You contribute are governed by
the terms of this License. You represent that You believe Your Modifications
are Your original creation(s) and/or You have sufficient rights to grant the
rights conveyed by this License.
3.3. Required Notices. You must include a notice in each of Your Modifications
that identifies You as the Contributor of the Modification. You may not remove
or alter any copyright, patent or trademark notices contained within the
Covered Software, or any notices of licensing or any descriptive text giving
attribution to any Contributor or the Initial Developer.
3.4. Application of Additional Terms. You may not offer or impose any terms on
any Covered Software in Source Code form that alters or restricts the
applicable version of this License or the recipients rights hereunder. You may
choose to offer, and to charge a fee for, warranty, support, indemnity or
liability obligations to one or more recipients of Covered Software. However,
you may do so only on Your own behalf, and not on behalf of the Initial
Developer or any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity or liability obligation is offered by You alone,
and You hereby agree to indemnify the Initial Developer and every Contributor
for any liability incurred by the Initial Developer or such Contributor as a
result of warranty, support, indemnity or liability terms You offer.
3.5. Distribution of Executable Versions. You may distribute the Executable
form of the Covered Software under the terms of this License or under the terms
of a license of Your choice, which may contain terms different from this
License, provided that You are in compliance with the terms of this License and
that the license for the Executable form does not attempt to limit or alter the
recipients rights in the Source Code form from the rights set forth in this
License. If You distribute the Covered Software in Executable form under a
different license, You must make it absolutely clear that any terms which
differ from this License are offered by You alone, not by the Initial Developer
or Contributor. You hereby agree to indemnify the Initial Developer and every
Contributor for any liability incurred by the Initial Developer or such
Contributor as a result of any such terms You offer.
3.6. Larger Works. You may create a Larger Work by combining Covered Software
with other code not governed by the terms of this License and distribute the
Larger Work as a single product. In such a case, You must make sure the
requirements of this License are fulfilled for the Covered Software.
4. Versions of the License.
4.1. New Versions. Sun Microsystems, Inc. is the initial license steward and
may publish revised and/or new versions of this License from time to time. Each
version will be given a distinguishing version number. Except as provided in
Section 4.3, no one other than the license steward has the right to modify this
License.
4.2. Effect of New Versions.
You may always continue to use, distribute or otherwise make the Covered
Software available under the terms of the version of the License under which
You originally received the Covered Software. If the Initial Developer includes
a notice in the Original Software prohibiting it from being distributed or
otherwise made available under any subsequent version of the License, You must
distribute and make the Covered Software available under the terms of the
version of the License under which You originally received the Covered
Software. Otherwise, You may also choose to use, distribute or otherwise make
the Covered Software available under the terms of any subsequent version of the
License published by the license steward. 4.3. Modified Versions.
When You are an Initial Developer and You want to create a new license for Your
Original Software, You may create and use a modified version of this License if
You: (a) rename the license and remove any references to the name of the
license steward (except to note that the license differs from this License);
and (b) otherwise make it clear that the license contains terms which differ
from this License.
5. DISCLAIMER OF WARRANTY.
COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN AS IS BASIS, WITHOUT
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT
LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS,
MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK
AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD
ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL
DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING,
REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART
OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT
UNDER THIS DISCLAIMER.
6. TERMINATION.
6.1. This License and the rights granted hereunder will terminate automatically
if You fail to comply with terms herein and fail to cure such breach within 30
days of becoming aware of the breach. Provisions which, by their nature, must
remain in effect beyond the termination of this License shall survive.
6.2. If You assert a patent infringement claim (excluding declaratory judgment
actions) against Initial Developer or a Contributor (the Initial Developer or
Contributor against whom You assert such claim is referred to as Participant)
alleging that the Participant Software (meaning the Contributor Version where
the Participant is a Contributor or the Original Software where the Participant
is the Initial Developer) directly or indirectly infringes any patent, then any
and all rights granted directly or indirectly to You by such Participant, the
Initial Developer (if the Initial Developer is not the Participant) and all
Contributors under Sections 2.1 and/or 2.2 of this License shall, upon 60 days
notice from Participant terminate prospectively and automatically at the
expiration of such 60 day notice period, unless if within such 60 day period
You withdraw Your claim with respect to the Participant Software against such
Participant either unilaterally or pursuant to a written agreement with
Participant.
6.3. In the event of termination under Sections 6.1 or 6.2 above, all end user
licenses that have been validly granted by You or any distributor hereunder
prior to termination (excluding licenses granted to You by any distributor)
shall survive termination.
7. LIMITATION OF LIABILITY.
UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING
NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY
OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF
ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL,
INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER
FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN
IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS
LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
INJURY RESULTING FROM SUCH PARTYS NEGLIGENCE TO THE EXTENT APPLICABLE LAW
PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR
LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND
LIMITATION MAY NOT APPLY TO YOU.
8. U.S. GOVERNMENT END USERS.
The Covered Software is a commercial item, as that term is defined in
48 C.F.R. 2.101 (Oct. 1995), consisting of commercial computer software (as
that term is defined at 48 C.F.R. § 252.227-7014(a)(1)) and commercial computer
software documentation as such terms are used in 48 C.F.R. 12.212 Sept. 1995).
Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4
(June 1995), all U.S. Government End Users acquire Covered Software with only
those rights set forth herein. This U.S. Government Rights clause is in lieu
of, and supersedes, any other FAR, DFAR, or other clause or provision that
addresses Government rights in computer software under this License.
9. MISCELLANEOUS.
This License represents the complete agreement concerning subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. This License shall be governed by the law of the jurisdiction
specified in a notice contained within the Original Software (except to the
extent applicable law, if any, provides otherwise), excluding such
jurisdictions conflict-of-law provisions. Any litigation relating to this
License shall be subject to the jurisdiction of the courts located in the
jurisdiction and venue specified in a notice contained within the Original
Software, with the losing party responsible for costs, including, without
limitation, court costs and reasonable attorneys fees and expenses. The
application of the United Nations Convention on Contracts for the International
Sale of Goods is expressly excluded. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
apply to this License. You agree that You alone are responsible for compliance
with the United States export administration regulations (and the export
control laws and regulation of any other countries) when You use, distribute or
otherwise make available any Covered Software.
10. RESPONSIBILITY FOR CLAIMS.
As between Initial Developer and the Contributors, each party is responsible
for claims and damages arising, directly or indirectly, out of its utilization
of rights under this License and You agree to work with Initial Developer and
Contributors to distribute such responsibility on an equitable basis. Nothing
herein is intended or shall be deemed to constitute any admission of liability.
NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
(CDDL) The GlassFish code released under the CDDL shall be governed by the laws
of the State of California (excluding conflict-of-law provisions). Any
litigation relating to this License shall be subject to the jurisdiction of the
Federal Courts of the Northern District of California and the state courts of
the State of California, with venue lying in Santa Clara County, California.
==========================================================================
The following license applies to parts of the lucene-snowball jar
that are generated from the snowball sources at http://snowball.tartarus.org/
--------------------------------------------------------------------------
The BSD License
Copyright (c) 2001, Dr Martin Porter, Copyright (c) 2002, Richard Boulton
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the <ORGANIZATION> nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==========================================================================
The following license applies to easymock-2.2.jar
--------------------------------------------------------------------------
EasyMock 2 License (MIT License)
Copyright (c) 2001-2007 OFFIS, Tammo Freese.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
==========================================================================
The following license applies to the JQuery JavaScript library
--------------------------------------------------------------------------
Copyright (c) 2010 John Resig, http://jquery.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
==========================================================================
The following license applies to stax-utils.jar
--------------------------------------------------------------------------
Copyright (c) 2004, Christian Niles, unit12.net
Copyright (c) 2004, Sun Microsystems, Inc.
Copyright (c) 2006, John Kristian
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the listed copyright holders nor the names
of its contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==========================================================================
The following license applies to JUnit
--------------------------------------------------------------------------
Common Public License - v 1.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
"Program" means the Contributions distributed in accordance with this Agreement.
"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.
d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained within the Program.
Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.
For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against a Contributor with respect to a patent applicable to software (including a cross-claim or counterclaim in a lawsuit), then any patent licenses granted by that Contributor to such Recipient under this Agreement shall terminate as of the date such litigation is filed. In addition, if Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. IBM is the initial Agreement Steward. IBM may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved.
This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.
==========================================================================
The following license applies to slf4j
--------------------------------------------------------------------------
Copyright (c) 2004-2008 QOS.ch
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
==========================================================================
contrib/clustering
==========================================================================
Carrot2 Project
Copyright (C) 2002-2008, Dawid Weiss, Stanislaw Osinski.
Portions (C) Contributors listed in "carrot2.CONTRIBUTORS" file.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
- Neither the name of the Poznan University of Technology, Poznan, Poland nor
the names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
- We request that you include in the end-user documentation provided with the
redistribution and/or in the software itself an acknowledgement equivalent to
the following: "This product includes software developed by the Carrot2
Project."
- No algorithms or technical solutions in the project may be patented or claimed
proprietary.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==========================================================================
Guava
/**
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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.
*/
==========================================================================
Jackson
/**
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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.
*/
===========================================================================
Apache Tika Licenses - contrib/extraction
---------------------------------------------------------------------------
Apache Tika is licensed under the ASL 2.0. See above for the text of the license
APACHE TIKA SUBCOMPONENTS
Apache Tika includes a number of subcomponents with separate copyright notices
and license terms. Your use of these subcomponents is subject to the terms and
conditions of the following licenses.
Bouncy Castle libraries (bcmail and bcprov)
Copyright (c) 2000-2006 The Legion Of The Bouncy Castle
(http://www.bouncycastle.org)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
PDFBox library (pdfbox)
Copyright (c) 2003-2005, www.pdfbox.org
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of pdfbox; nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
FontBox and JempBox libraries (fontbox, jempbox)
Copyright (c) 2003-2005, www.fontbox.org
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of fontbox; nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
ICU4J library (icu4j)
Copyright (c) 1995-2005 International Business Machines Corporation
and others
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, and/or sell copies of the Software, and to permit persons
to whom the Software is furnished to do so, provided that the above
copyright notice(s) and this permission notice appear in all copies
of the Software and that both the above copyright notice(s) and this
permission notice appear in supporting documentation.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in this Software without prior written authorization of the
copyright holder.
ASM library (asm)
Copyright (c) 2000-2005 INRIA, France Telecom
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holders nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
=================================================================================================
The following license applies to JavaMail API 1.4.1 and JavaBeans Activation Framework (JAF) 1.1
-------------------------------------------------------------------------------------------------
COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 1.
Definitions.
1.1. Contributor means each individual or entity that creates or contributes to the creation of Modifications.
1.2. Contributor Version means the combination of the Original Software, prior Modifications used by a Contributor (if any), and the Modifications made by that particular Contributor.
1.3. Covered Software means (a) the Original Software, or (b) Modifications, or (c) the combination of files containing Original Software with files containing Modifications, in each case including portions thereof.
1.4. Executable means the Covered Software in any form other than Source Code.
1.5. Initial Developer means the individual or entity that first makes Original Software available under this License.
1.6. Larger Work means a work which combines Covered Software or portions thereof with code not governed by the terms of this License.
1.7. License means this document.
1.8. Licensable means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein.
1.9. Modifications means the Source Code and Executable form of any of the following: A. Any file that results from an addition to, deletion from or modification of the contents of a file containing Original Software or previous Modifications; B. Any new file that contains any part of the Original Software or previous Modification; or C. Any new file that is contributed or otherwise made available under the terms of this License.
1.10. Original Software means the Source Code and Executable form of computer software code that is originally released under this License.
1.11. Patent Claims means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor.
1.12. Source Code means (a) the common form of computer software code in which modifications are made and (b) associated documentation included in or with such code.
1.13. You (or Your) means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, You includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, control means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.
2. License Grants.
2.1. The Initial Developer Grant. Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, the Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license:
(a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer, to use, reproduce, modify, display, perform, sublicense and distribute the Original Software (or portions thereof), with or without Modifications, and/or as part of a Larger Work; and
(b) under Patent Claims infringed by the making, using or selling of Original Software, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Software (or portions thereof);
(c) The licenses granted in Sections 2.1(a) and (b) are effective on the date Initial Developer first distributes or otherwise makes the Original Software available to a third party under the terms of this License;
(d) Notwithstanding Section 2.1(b) above, no patent license is granted: (1) for code that You delete from the Original Software, or (2) for infringements caused by: (i) the modification of the Original Software, or (ii) the combination of the Original Software with other software or devices.
2.2. Contributor Grant. Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:
(a) under intellectual property rights (other than patent or trademark) Licensable by Contributor to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof), either on an unmodified basis, with other Modifications, as Covered Software and/or as part of a Larger Work; and
(b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: (1) Modifications made by that Contributor (or portions thereof); and (2) the combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination).
(c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor first distributes or otherwise makes the Modifications available to a third party.
(d) Notwithstanding Section 2.2(b) above, no patent license is granted: (1) for any code that Contributor has deleted from the Contributor Version; (2) for infringements caused by: (i) third party modifications of Contributor Version, or (ii) the combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or (3) under Patent Claims infringed by Covered Software in the absence of Modifications made by that Contributor.
3. Distribution Obligations.
3.1. Availability of Source Code. Any Covered Software that You distribute or otherwise make available in Executable form must also be made available in Source Code form and that Source Code form must be distributed only under the terms of this License. You must include a copy of this License with every copy of the Source Code form of the Covered Software You distribute or otherwise make available. You must inform recipients of any such Covered Software in Executable form as to how they can obtain such Covered Software in Source Code form in a reasonable manner on or through a medium customarily used for software exchange.
3.2. Modifications. The Modifications that You create or to which You contribute are governed by the terms of this License. You represent that You believe Your Modifications are Your original creation(s) and/or You have sufficient rights to grant the rights conveyed by this License.
3.3. Required Notices. You must include a notice in each of Your Modifications that identifies You as the Contributor of the Modification. You may not remove or alter any copyright, patent or trademark notices contained within the Covered Software, or any notices of licensing or any descriptive text giving attribution to any Contributor or the Initial Developer.
3.4. Application of Additional Terms. You may not offer or impose any terms on any Covered Software in Source Code form that alters or restricts the applicable version of this License or the recipients rights hereunder. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, you may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear that any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer.
3.5. Distribution of Executable Versions. You may distribute the Executable form of the Covered Software under the terms of this License or under the terms of a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable form does not attempt to limit or alter the recipients rights in the Source Code form from the rights set forth in this License. If You distribute the Covered Software in Executable form under a different license, You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer.
3.6. Larger Works. You may create a Larger Work by combining Covered Software with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Software.
4. Versions of the License.
4.1. New Versions. Sun Microsystems, Inc. is the initial license steward and may publish revised and/or new versions of this License from time to time. Each version will be given a distinguishing version number. Except as provided in Section 4.3, no one other than the license steward has the right to modify this License.
4.2. Effect of New Versions. You may always continue to use, distribute or otherwise make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. If the Initial Developer includes a notice in the Original Software prohibiting it from being distributed or otherwise made available under any subsequent version of the License, You must distribute and make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. Otherwise, You may also choose to use, distribute or otherwise make the Covered Software available under the terms of any subsequent version of the License published by the license steward.
4.3. Modified Versions. When You are an Initial Developer and You want to create a new license for Your Original Software, You may create and use a modified version of this License if You: (a) rename the license and remove any references to the name of the license steward (except to note that the license differs from this License); and (b) otherwise make it clear that the license contains terms which differ from this License.
5. DISCLAIMER OF WARRANTY. COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN AS IS BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
6. TERMINATION.
6.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive.
6.2. If You assert a patent infringement claim (excluding declaratory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You assert such claim is referred to as Participant) alleging that the Participant Software (meaning the Contributor Version where the Participant is a Contributor or the Original Software where the Participant is the Initial Developer) directly or indirectly infringes any patent, then any and all rights granted directly or indirectly to You by such Participant, the Initial Developer (if the Initial Developer is not the Participant) and all Contributors under Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively and automatically at the expiration of such 60 day notice period, unless if within such 60 day period You withdraw Your claim with respect to the Participant Software against such Participant either unilaterally or pursuant to a written agreement with Participant.
6.3. In the event of termination under Sections 6.1 or 6.2 above, all end user licenses that have been validly granted by You or any distributor hereunder prior to termination (excluding licenses granted to You by any distributor) shall survive termination.
7. LIMITATION OF LIABILITY. UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTYS NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
8. U.S. GOVERNMENT END USERS. The Covered Software is a commercial item, as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of commercial computer software (as that term is defined at 48 C.F.R. 252.227-7014(a)(1)) and commercial computer software documentation as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Software with only those rights set forth herein. This U.S. Government Rights clause is in lieu of, and supersedes, any other FAR, DFAR, or other clause or provision that addresses Government rights in computer software under this License.
9. MISCELLANEOUS. This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by the law of the jurisdiction specified in a notice contained within the Original Software (except to the extent applicable law, if any, provides otherwise), excluding such jurisdictions conflict-of-law provisions. Any litigation relating to this License shall be subject to the jurisdiction of the courts located in the jurisdiction and venue specified in a notice contained within the Original Software, with the losing party responsible for costs, including, without limitation, court costs and reasonable attorneys fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. You agree that You alone are responsible for compliance with the United States export administration regulations (and the export control laws and regulation of any other countries) when You use, distribute or otherwise make available any Covered Software.
10. RESPONSIBILITY FOR CLAIMS. As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability.
NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) The code released under the CDDL shall be governed by the laws of the State of California (excluding conflict-of-law provisions). Any litigation relating to this License shall be subject to the jurisdiction of the Federal Courts of the Northern District of California and the state courts of the State of California, with venue lying in Santa Clara County, California.

342
thirdparty/solr/NOTICE.txt vendored Normal file
View File

@ -0,0 +1,342 @@
==============================================================
Apache Solr
Copyright 2006-2011 The Apache Software Foundation
==============================================================
This product includes software developed by
The Apache Software Foundation (http://www.apache.org/).
Includes software from other Apache Software Foundation projects,
including, but not limited to:
- Apache Lucene Java
- Apache Tomcat (lib/servlet-api-2.4.jar)
- Apache Commons
- Apache Geronimo (stax API jar)
- Apache Log4j (contrib/clustering)
This product includes tests written with EasyMock Copyright 2001-2007
Tammo Freese (http://www.easymock.org/)
This product includes the JQuery JavaScript library created by John Resig.
Copyright (c) 2010 John Resig, http://jquery.com/
This product includes the stax-utils jar: https://stax-utils.dev.java.net/
Copyright (c) 2004, Christian Niles, unit12.net
Copyright (c) 2004, Sun Microsystems, Inc.
Copyright (c) 2006, John Kristian
License: The BSD License (http://www.opensource.org/licenses/bsd-license.php)
This product includes a JUnit jar: http://junit.sourceforge.net/
License: Common Public License - v 1.0 (http://junit.sourceforge.net/cpl-v10.html)
This product includes the JavaMail API 1.4.1 jar: https://glassfish.dev.java.net/javaee5/mail/
License: Common Development and Distribution License (CDDL) v1.0 (https://glassfish.dev.java.net/public/CDDLv1.0.html)
This product includes the JavaBeans Activation Framework (JAF) 1.1 jar: http://java.sun.com/products/javabeans/jaf/index.jsp
License: Common Development and Distribution License (CDDL) v1.0 (https://glassfish.dev.java.net/public/CDDLv1.0.html)
This product includes the HSQL Database (HSQLDB) 1.8.0.10 jar: http://hsqldb.org/
License: http://hsqldb.org/web/hsqlLicense.html
This product includes code (JaspellTernarySearchTrie) from Java Spelling Checking Package (jaspell): http://jaspell.sourceforge.net/
License: The BSD License (http://www.opensource.org/licenses/bsd-license.php)
=========================================================================
== Apache Lucene Notice ==
=========================================================================
Includes lib/servlet-api-2.4.jar from Apache Tomcat
Includes lib/ant-1.7.1.jar and lib/ant-junit-1.7.1.jar from Apache Ant
Includes contrib/queries/lib/jakarta-regexp-1.4.jar from Apache Jakarta Regexp
ICU4J, (under contrib/icu) is licensed under an MIT styles license
(contrib/icu/lib/ICU-LICENSE.txt) and Copyright (c) 1995-2008
International Business Machines Corporation and others
Some data files (under contrib/icu/src/data) are derived from Unicode data such
as the Unicode Character Database. See http://unicode.org/copyright.html for more
details.
The class org.apache.lucene.SorterTemplate was inspired by CGLIB's class with
the same name. The implementation part is mainly done using pre-existing
Lucene sorting code. In-place stable mergesort was borrowed from CGLIB,
which is Apache-licensed.
The Google Code Prettify is Apache License 2.0.
See http://code.google.com/p/google-code-prettify/
JUnit (under lib/junit-4.7.jar) is licensed under the Common Public License v. 1.0
See http://junit.sourceforge.net/cpl-v10.html
JLine (under contrib/lucli/lib/jline.jar) is licensed under the BSD License.
See http://jline.sourceforge.net/
Includes software from other Apache Software Foundation projects,
including, but not limited to:
- Commons Beanutils (lib/commons-beanutils-1.7.0.jar)
- Commons Collections (lib/commons-collections-3.1.jar)
- Commons Compress (lib/commons-compress-1.0.jar)
- Commons Digester (lib/commons-digester-1.7.jar)
- Commons Logging (lib/commons-logging-1.0.4.jar)
- Xerces (lib/xercesImpl-2.9.1-patched-XERCESJ-1257.jar)
- Apache Commons
The snowball stemmers in
contrib/analyzers/common/src/java/net/sf/snowball
were developed by Martin Porter and Richard Boulton.
The snowball stopword lists in
contrib/analyzers/common/src/resources/org/apache/lucene/analysis/snowball
were developed by Martin Porter and Richard Boulton.
The full snowball package is available from
http://snowball.tartarus.org/
The Arabic,Persian,Romanian,Bulgarian, and Hindi analyzers (common) come with a default
stopword list that is BSD-licensed created by Jacques Savoy. These files reside in:
contrib/analyzers/common/src/resources/org/apache/lucene/analysis/ar/stopwords.txt,
contrib/analyzers/common/src/resources/org/apache/lucene/analysis/fa/stopwords.txt,
contrib/analyzers/common/src/resources/org/apache/lucene/analysis/ro/stopwords.txt,
contrib/analyzers/common/src/resources/org/apache/lucene/analysis/bg/stopwords.txt,
contrib/analyzers/common/src/resources/org/apache/lucene/analysis/hi/stopwords.txt
See http://members.unine.ch/jacques.savoy/clef/index.html.
The German,Spanish,Finnish,French,Hungarian,Italian,Portuguese,Russian and Swedish light stemmers
(common) are based on BSD-licensed reference implementations created by Jacques Savoy and
Ljiljana Dolamic. These files reside in:
contrib/analyzers/common/src/java/org/apache/lucene/analysis/de/GermanLightStemmer.java
contrib/analyzers/common/src/java/org/apache/lucene/analysis/de/GermanMinimalStemmer.java
contrib/analyzers/common/src/java/org/apache/lucene/analysis/es/SpanishLightStemmer.java
contrib/analyzers/common/src/java/org/apache/lucene/analysis/fi/FinnishLightStemmer.java
contrib/analyzers/common/src/java/org/apache/lucene/analysis/fr/FrenchLightStemmer.java
contrib/analyzers/common/src/java/org/apache/lucene/analysis/fr/FrenchMinimalStemmer.java
contrib/analyzers/common/src/java/org/apache/lucene/analysis/hu/HungarianLightStemmer.java
contrib/analyzers/common/src/java/org/apache/lucene/analysis/it/ItalianLightStemmer.java
contrib/analyzers/common/src/java/org/apache/lucene/analysis/pt/PortugueseLightStemmer.java
contrib/analyzers/common/src/java/org/apache/lucene/analysis/ru/RussianLightStemmer.java
contrib/analyzers/common/src/java/org/apache/lucene/analysis/sv/SwedishLightStemmer.java
The Stempel analyzer (stempel) includes BSD-licensed software developed
by the Egothor project http://egothor.sf.net/, created by Leo Galambos, Martin Kvapil,
and Edmond Nolan.
The Polish analyzer (stempel) comes with a default
stopword list that is BSD-licensed created by the Carrot2 project. The file resides
in contrib/analyzers/stempel/src/resources/org/apache/lucene/analysis/pl/stopwords.txt.
See http://project.carrot2.org/license.html.
The SmartChineseAnalyzer source code (smartcn) was
provided by Xiaoping Gao and copyright 2009 by www.imdict.net.
WordBreakTestUnicode_*.java (under src/test/)
is derived from Unicode data such as the Unicode Character Database.
See http://unicode.org/copyright.html for more details.
---
This product includes/uses software, Woodstox (http://woodstox.codehaus.org),
developed by Codehaus (http://www.codehaus.org/)
License: The Apache Software License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
=========================================================================
== Woodstox Notice ==
=========================================================================
This product currently only contains code developed by authors
of specific components, as identified by the source code files.
Since product implements StAX API, it has dependencies to StAX API
classes.
For additional credits (generally to people who reported problems)
see CREDITS file.
---
This product includes software developed by Mort Bay Consulting
(specifically, Jetty 6.1.3, the bundled servlet container in example)
The jboss integration module is not included.
=========================================================================
== Jetty Notice ==
=========================================================================
==============================================================
Jetty Web Container
Copyright 1995-2006 Mort Bay Consulting Pty Ltd
==============================================================
This product includes some software developed at The Apache Software
Foundation (http://www.apache.org/).
The javax.servlet package used by Jetty is copyright
Sun Microsystems, Inc and Apache Software Foundation. It is
distributed under the Common Development and Distribution License.
You can obtain a copy of the license at
https://glassfish.dev.java.net/public/CDDLv1.0.html.
The UnixCrypt.java code ~Implements the one way cryptography used by
Unix systems for simple password protection. Copyright 1996 Aki Yoshida,
modified April 2001 by Iris Van den Broeke, Daniel Deville.
The default JSP implementation is provided by the Glassfish JSP engine
from project Glassfish http://glassfish.dev.java.net. Copyright 2005
Sun Microsystems, Inc. and portions Copyright Apache Software Foundation.
Some portions of the code are Copyright:
2006 Tim Vernum
1999 Jason Gilbert.
The jboss integration module contains some LGPL code.
=========================================================================
== SLF4J Notice -- http://www.slf4j.org/license.html ==
=========================================================================
Copyright (c) 2004-2008 QOS.ch
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================================================
== Apache Tika Notices ==
=========================================================================
The following notices apply to the Apache Tika libraries in contrib/extraction/lib:
This product includes software developed by the following copyright owners:
Copyright (c) 2000-2006 The Legion Of The Bouncy Castle
(http://www.bouncycastle.org)
Copyright (c) 2003-2005, www.pdfbox.org
Copyright (c) 2003-2005, www.fontbox.org
Copyright (c) 1995-2005 International Business Machines Corporation and others
Copyright (c) 2000-2005 INRIA, France Telecom
Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
Copyright 2004 Sun Microsystems, Inc. (Rome JAR)
Copyright 2002-2008 by John Cowan (TagSoup -- http://ccil.org/~cowan/XML/tagsoup/)
=========================================================================
== Carrot2 Notice ==
=========================================================================
Copyright (C) 2002-2010, Dawid Weiss, Stanislaw Osinski.
Portions (C) Contributors listed in "carrot2.CONTRIBUTORS" file.
All rights reserved.
This product includes software developed by the Carrot2 Project.
See http://project.carrot2.org/
=========================================================================
== Guava Notice ==
=========================================================================
Copyright (C) 2009 Google Inc.
This product includes software developed by the Google Guava project.
See http://code.google.com/p/guava-libraries/
=========================================================================
== Prettify Notice ==
=========================================================================
Copyright (C) 2009 Google Inc.
This product includes software developed by the Google Prettify project.
See http://code.google.com/p/google-code-prettify/
=========================================================================
== Jackson Notice ==
=========================================================================
Copyright 2010 FasterXML, LLC
This product includes software developed by the Jackson project.
See http://jackson.codehaus.org/
=========================================================================
== HSQLDB Notice ==
=========================================================================
For content, code, and products originally developed by Thomas Mueller and the Hypersonic SQL Group:
Copyright (c) 1995-2000 by the Hypersonic SQL Group.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
Neither the name of the Hypersonic SQL Group nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This software consists of voluntary contributions made by many individuals on behalf of the
Hypersonic SQL Group.
For work added by the HSQL Development Group (a.k.a. hsqldb_lic.txt):
Copyright (c) 2001-2005, The HSQL Development Group
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
Neither the name of the HSQL Development Group nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

6
thirdparty/solr/README.md vendored Normal file
View File

@ -0,0 +1,6 @@
This is a cut-down version of the Solr 3.1 distro for getting started with Solr and the fulltextsearch module.
For the full version, and more information about Solr, visit http://lucene.apache.org/solr/
If using the full version, make sure the Solr home directory is a copy of server/solr - it contains configuration (multi-core enabled, etc) that
the fulltextsearch module expects

51
thirdparty/solr/server/README.txt vendored Normal file
View File

@ -0,0 +1,51 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.
Solr example configuration
--------------------------
To run this example configuration, use
java -jar start.jar
in this directory, and when Solr is started connect to
http://localhost:8983/solr/admin/
To add documents to the index, use the post.sh script in the exampledocs
subdirectory (while Solr is running), for example:
cd exampledocs
./post.sh *.xml
See also README.txt in the solr subdirectory, and check
http://wiki.apache.org/solr/SolrResources for a list of tutorials and
introductory articles.
NOTE: This Solr example server references certain Solr jars outside of
this server directory for non-core modules with <lib> statements in
solrconfig.xml. If you make a copy of this example server and wish
to use the ExtractingRequestHandler (SolrCell), DataImportHandler (DIH),
UIMA, the clustering component, or other modules in "contrib",
you will need to copy the required jars into solr/lib or update the paths to
the jars in your solrconfig.xml.
By default, start.jar starts Solr in Jetty using the default solr home
directory of "./solr/" -- To run other example configurations, you can
speciy the solr.solr.home system property when starting jetty...
java -Dsolr.solr.home=multicore -jar start.jar
java -Dsolr.solr.home=example-DIH -jar start.jar

227
thirdparty/solr/server/etc/jetty.xml vendored Normal file
View File

@ -0,0 +1,227 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
<!-- =============================================================== -->
<!-- Configure the Jetty Server -->
<!-- -->
<!-- Documentation of this file format can be found at: -->
<!-- http://docs.codehaus.org/display/JETTY/jetty.xml -->
<!-- -->
<!-- =============================================================== -->
<Configure id="Server" class="org.mortbay.jetty.Server">
<!-- Increase the maximum POST size to 1 MB to be able to handle large shard requests -->
<Call class="java.lang.System" name="setProperty">
<Arg>org.mortbay.jetty.Request.maxFormContentSize</Arg>
<Arg>1000000</Arg>
</Call>
<!-- =========================================================== -->
<!-- Server Thread Pool -->
<!-- =========================================================== -->
<Set name="ThreadPool">
<New class="org.mortbay.thread.QueuedThreadPool">
<Set name="minThreads">10</Set>
<Set name="maxThreads">10000</Set>
<Set name="lowThreads">20</Set>
</New>
<!-- Optional Java 5 bounded threadpool with job queue
<New class="org.mortbay.thread.concurrent.ThreadPool">
<Set name="corePoolSize">50</Set>
<Set name="maximumPoolSize">50</Set>
</New>
-->
</Set>
<!-- =========================================================== -->
<!-- Set connectors -->
<!-- =========================================================== -->
<!-- One of each type! -->
<!-- =========================================================== -->
<!-- Use this connector for many frequently idle connections
and for threadless continuations.
-->
<!--
<Call name="addConnector">
<Arg>
<New class="org.mortbay.jetty.nio.SelectChannelConnector">
<Set name="host"><SystemProperty name="jetty.host" /></Set>
<Set name="port"><SystemProperty name="jetty.port" default="8983"/></Set>
<Set name="maxIdleTime">30000</Set>
<Set name="Acceptors">2</Set>
<Set name="statsOn">false</Set>
<Set name="confidentialPort">8443</Set>
<Set name="lowResourcesConnections">5000</Set>
<Set name="lowResourcesMaxIdleTime">5000</Set>
</New>
</Arg>
</Call>
-->
<!-- This connector is currently being used for Solr because it
showed better performance than nio.SelectChannelConnector
for typical Solr requests. -->
<Call name="addConnector">
<Arg>
<New class="org.mortbay.jetty.bio.SocketConnector">
<Set name="host"><SystemProperty name="jetty.host" /></Set>
<Set name="port"><SystemProperty name="jetty.port" default="8983"/></Set>
<Set name="maxIdleTime">50000</Set>
<Set name="lowResourceMaxIdleTime">1500</Set>
<Set name="statsOn">false</Set>
</New>
</Arg>
</Call>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- To add a HTTPS SSL listener -->
<!-- see jetty-ssl.xml to add an ssl connector. use -->
<!-- java -jar start.jar etc/jetty.xml etc/jetty-ssl.xml -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- To allow Jetty to be started from xinetd -->
<!-- mixin jetty-xinetd.xml: -->
<!-- java -jar start.jar etc/jetty.xml etc/jetty-xinetd.xml -->
<!-- -->
<!-- See jetty-xinetd.xml for further instructions. -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- =========================================================== -->
<!-- Set up global session ID manager -->
<!-- =========================================================== -->
<!--
<Set name="sessionIdManager">
<New class="org.mortbay.jetty.servlet.HashSessionIdManager">
<Set name="workerName">node1</Set>
</New>
</Set>
-->
<!-- =========================================================== -->
<!-- Set handler Collection Structure -->
<!-- =========================================================== -->
<Set name="handler">
<New id="Handlers" class="org.mortbay.jetty.handler.HandlerCollection">
<Set name="handlers">
<Array type="org.mortbay.jetty.Handler">
<Item>
<New id="Contexts" class="org.mortbay.jetty.handler.ContextHandlerCollection"/>
</Item>
<Item>
<New id="DefaultHandler" class="org.mortbay.jetty.handler.DefaultHandler"/>
</Item>
<Item>
<New id="RequestLog" class="org.mortbay.jetty.handler.RequestLogHandler"/>
</Item>
</Array>
</Set>
</New>
</Set>
<!-- =========================================================== -->
<!-- Configure the context deployer -->
<!-- A context deployer will deploy contexts described in -->
<!-- configuration files discovered in a directory. -->
<!-- The configuration directory can be scanned for hot -->
<!-- deployments at the configured scanInterval. -->
<!-- -->
<!-- This deployer is configured to deploy contexts configured -->
<!-- in the $JETTY_HOME/contexts directory -->
<!-- -->
<!-- =========================================================== -->
<Call name="addLifeCycle">
<Arg>
<New class="org.mortbay.jetty.deployer.ContextDeployer">
<Set name="contexts"><Ref id="Contexts"/></Set>
<Set name="configurationDir"><SystemProperty name="jetty.home" default="."/>/contexts</Set>
<Set name="scanInterval">5</Set>
</New>
</Arg>
</Call>
<!-- =========================================================== -->
<!-- Configure the webapp deployer. -->
<!-- A webapp deployer will deploy standard webapps discovered -->
<!-- in a directory at startup, without the need for additional -->
<!-- configuration files. It does not support hot deploy or -->
<!-- non standard contexts (see ContextDeployer above). -->
<!-- -->
<!-- This deployer is configured to deploy webapps from the -->
<!-- $JETTY_HOME/webapps directory -->
<!-- -->
<!-- Normally only one type of deployer need be used. -->
<!-- -->
<!-- =========================================================== -->
<Call name="addLifeCycle">
<Arg>
<New class="org.mortbay.jetty.deployer.WebAppDeployer">
<Set name="contexts"><Ref id="Contexts"/></Set>
<Set name="webAppDir"><SystemProperty name="jetty.home" default="."/>/webapps</Set>
<Set name="parentLoaderPriority">false</Set>
<Set name="extract">true</Set>
<Set name="allowDuplicates">false</Set>
<Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
</New>
</Arg>
</Call>
<!-- =========================================================== -->
<!-- Configure Authentication Realms -->
<!-- Realms may be configured for the entire server here, or -->
<!-- they can be configured for a specific web app in a context -->
<!-- configuration (see $(jetty.home)/contexts/test.xml for an -->
<!-- example). -->
<!-- =========================================================== -->
<!--
<Set name="UserRealms">
<Array type="org.mortbay.jetty.security.UserRealm">
<Item>
<New class="org.mortbay.jetty.security.HashUserRealm">
<Set name="name">Test Realm</Set>
<Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/realm.properties</Set>
<Set name="refreshInterval">0</Set>
</New>
</Item>
</Array>
</Set>
-->
<!-- =========================================================== -->
<!-- Configure Request Log -->
<!-- Request logs may be configured for the entire server here, -->
<!-- or they can be configured for a specific web app in a -->
<!-- contexts configuration (see $(jetty.home)/contexts/test.xml -->
<!-- for an example). -->
<!-- =========================================================== -->
<!--
<Ref id="RequestLog">
<Set name="requestLog">
<New id="RequestLogImpl" class="org.mortbay.jetty.NCSARequestLog">
<Set name="filename"><SystemProperty name="jetty.logs" default="./logs"/>/yyyy_mm_dd.request.log</Set>
<Set name="filenameDateFormat">yyyy_MM_dd</Set>
<Set name="retainDays">90</Set>
<Set name="append">true</Set>
<Set name="extended">false</Set>
<Set name="logCookies">false</Set>
<Set name="LogTimeZone">GMT</Set>
</New>
</Set>
</Ref>
-->
<!-- =========================================================== -->
<!-- extra options -->
<!-- =========================================================== -->
<Set name="stopAtShutdown">true</Set>
<Set name="sendServerVersion">false</Set>
<Set name="sendDateHeader">false</Set>
<Set name="gracefulShutdown">1000</Set>
</Configure>

View File

@ -0,0 +1,410 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- ===================================================================== -->
<!-- This file contains the default descriptor for web applications. -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- The intent of this descriptor is to include jetty specific or common -->
<!-- configuration for all webapps. If a context has a webdefault.xml -->
<!-- descriptor, it is applied before the contexts own web.xml file -->
<!-- -->
<!-- A context may be assigned a default descriptor by: -->
<!-- + Calling WebApplicationContext.setDefaultsDescriptor -->
<!-- + Passed an arg to addWebApplications -->
<!-- -->
<!-- This file is used both as the resource within the jetty.jar (which is -->
<!-- used as the default if no explicit defaults descriptor is set) and it -->
<!-- is copied to the etc directory of the Jetty distro and explicitly -->
<!-- by the jetty.xml file. -->
<!-- -->
<!-- ===================================================================== -->
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
metadata-complete="true"
version="2.5">
<description>
Default web.xml file.
This file is applied to a Web application before it's own WEB_INF/web.xml file
</description>
<!-- ==================================================================== -->
<!-- Context params to control Session Cookies -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- UNCOMMENT TO ACTIVATE
<context-param>
<param-name>org.mortbay.jetty.servlet.SessionDomain</param-name>
<param-value>127.0.0.1</param-value>
</context-param>
<context-param>
<param-name>org.mortbay.jetty.servlet.SessionPath</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>org.mortbay.jetty.servlet.MaxAge</param-name>
<param-value>-1</param-value>
</context-param>
-->
<context-param>
<param-name>org.mortbay.jetty.webapp.NoTLDJarPattern</param-name>
<param-value>start.jar|ant-.*\.jar|dojo-.*\.jar|jetty-.*\.jar|jsp-api-.*\.jar|junit-.*\.jar|servlet-api-.*\.jar|dnsns\.jar|rt\.jar|jsse\.jar|tools\.jar|sunpkcs11\.jar|sunjce_provider\.jar|xerces.*\.jar</param-value>
</context-param>
<!-- ==================================================================== -->
<!-- The default servlet. -->
<!-- This servlet, normally mapped to /, provides the handling for static -->
<!-- content, OPTIONS and TRACE methods for the context. -->
<!-- The following initParameters are supported: -->
<!-- -->
<!-- acceptRanges If true, range requests and responses are -->
<!-- supported -->
<!-- -->
<!-- dirAllowed If true, directory listings are returned if no -->
<!-- welcome file is found. Else 403 Forbidden. -->
<!-- -->
<!-- welcomeServlets If true, attempt to dispatch to welcome files -->
<!-- that are servlets, if no matching static -->
<!-- resources can be found. -->
<!-- -->
<!-- redirectWelcome If true, redirect welcome file requests -->
<!-- else use request dispatcher forwards -->
<!-- -->
<!-- gzip If set to true, then static content will be served-->
<!-- as gzip content encoded if a matching resource is -->
<!-- found ending with ".gz" -->
<!-- -->
<!-- resoureBase Can be set to replace the context resource base -->
<!-- -->
<!-- relativeResourceBase -->
<!-- Set with a pathname relative to the base of the -->
<!-- servlet context root. Useful for only serving -->
<!-- static content from only specific subdirectories. -->
<!-- -->
<!-- useFileMappedBuffer -->
<!-- If set to true (the default), a memory mapped -->
<!-- file buffer will be used to serve static content -->
<!-- when using an NIO connector. Setting this value -->
<!-- to false means that a direct buffer will be used -->
<!-- instead. If you are having trouble with Windows -->
<!-- file locking, set this to false. -->
<!-- -->
<!-- cacheControl If set, all static content will have this value -->
<!-- set as the cache-control header. -->
<!-- -->
<!-- maxCacheSize Maximum size of the static resource cache -->
<!-- -->
<!-- maxCachedFileSize Maximum size of any single file in the cache -->
<!-- -->
<!-- maxCachedFiles Maximum number of files in the cache -->
<!-- -->
<!-- cacheType "nio", "bio" or "both" to determine the type(s) -->
<!-- of resource cache. A bio cached buffer may be used-->
<!-- by nio but is not as efficient as a nio buffer. -->
<!-- An nio cached buffer may not be used by bio. -->
<!-- -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.mortbay.jetty.servlet.DefaultServlet</servlet-class>
<init-param>
<param-name>acceptRanges</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>dirAllowed</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>welcomeServlets</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>redirectWelcome</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>maxCacheSize</param-name>
<param-value>256000000</param-value>
</init-param>
<init-param>
<param-name>maxCachedFileSize</param-name>
<param-value>10000000</param-value>
</init-param>
<init-param>
<param-name>maxCachedFiles</param-name>
<param-value>1000</param-value>
</init-param>
<init-param>
<param-name>cacheType</param-name>
<param-value>both</param-value>
</init-param>
<init-param>
<param-name>gzip</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>useFileMappedBuffer</param-name>
<param-value>true</param-value>
</init-param>
<!--
<init-param>
<param-name>cacheControl</param-name>
<param-value>max-age=3600,public</param-value>
</init-param>
-->
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
<!-- ==================================================================== -->
<!-- JSP Servlet -->
<!-- This is the jasper JSP servlet from the jakarta project -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- The JSP page compiler and execution servlet, which is the mechanism -->
<!-- used by Glassfish to support JSP pages. Traditionally, this servlet -->
<!-- is mapped to URL patterh "*.jsp". This servlet supports the -->
<!-- following initialization parameters (default values are in square -->
<!-- brackets): -->
<!-- -->
<!-- checkInterval If development is false and reloading is true, -->
<!-- background compiles are enabled. checkInterval -->
<!-- is the time in seconds between checks to see -->
<!-- if a JSP page needs to be recompiled. [300] -->
<!-- -->
<!-- compiler Which compiler Ant should use to compile JSP -->
<!-- pages. See the Ant documenation for more -->
<!-- information. [javac] -->
<!-- -->
<!-- classdebuginfo Should the class file be compiled with -->
<!-- debugging information? [true] -->
<!-- -->
<!-- classpath What class path should I use while compiling -->
<!-- generated servlets? [Created dynamically -->
<!-- based on the current web application] -->
<!-- Set to ? to make the container explicitly set -->
<!-- this parameter. -->
<!-- -->
<!-- development Is Jasper used in development mode (will check -->
<!-- for JSP modification on every access)? [true] -->
<!-- -->
<!-- enablePooling Determines whether tag handler pooling is -->
<!-- enabled [true] -->
<!-- -->
<!-- fork Tell Ant to fork compiles of JSP pages so that -->
<!-- a separate JVM is used for JSP page compiles -->
<!-- from the one Tomcat is running in. [true] -->
<!-- -->
<!-- ieClassId The class-id value to be sent to Internet -->
<!-- Explorer when using <jsp:plugin> tags. -->
<!-- [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93] -->
<!-- -->
<!-- javaEncoding Java file encoding to use for generating java -->
<!-- source files. [UTF-8] -->
<!-- -->
<!-- keepgenerated Should we keep the generated Java source code -->
<!-- for each page instead of deleting it? [true] -->
<!-- -->
<!-- logVerbosityLevel The level of detailed messages to be produced -->
<!-- by this servlet. Increasing levels cause the -->
<!-- generation of more messages. Valid values are -->
<!-- FATAL, ERROR, WARNING, INFORMATION, and DEBUG. -->
<!-- [WARNING] -->
<!-- -->
<!-- mappedfile Should we generate static content with one -->
<!-- print statement per input line, to ease -->
<!-- debugging? [false] -->
<!-- -->
<!-- -->
<!-- reloading Should Jasper check for modified JSPs? [true] -->
<!-- -->
<!-- suppressSmap Should the generation of SMAP info for JSR45 -->
<!-- debugging be suppressed? [false] -->
<!-- -->
<!-- dumpSmap Should the SMAP info for JSR45 debugging be -->
<!-- dumped to a file? [false] -->
<!-- False if suppressSmap is true -->
<!-- -->
<!-- scratchdir What scratch directory should we use when -->
<!-- compiling JSP pages? [default work directory -->
<!-- for the current web application] -->
<!-- -->
<!-- tagpoolMaxSize The maximum tag handler pool size [5] -->
<!-- -->
<!-- xpoweredBy Determines whether X-Powered-By response -->
<!-- header is added by generated servlet [false] -->
<!-- -->
<!-- If you wish to use Jikes to compile JSP pages: -->
<!-- Set the init parameter "compiler" to "jikes". Define -->
<!-- the property "-Dbuild.compiler.emacs=true" when starting Jetty -->
<!-- to cause Jikes to emit error messages in a format compatible with -->
<!-- Jasper. -->
<!-- If you get an error reporting that jikes can't use UTF-8 encoding, -->
<!-- try setting the init parameter "javaEncoding" to "ISO-8859-1". -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<servlet id="jsp">
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>logVerbosityLevel</param-name>
<param-value>DEBUG</param-value>
</init-param>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<!--
<init-param>
<param-name>classpath</param-name>
<param-value>?</param-value>
</init-param>
-->
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspf</url-pattern>
<url-pattern>*.jspx</url-pattern>
<url-pattern>*.xsp</url-pattern>
<url-pattern>*.JSP</url-pattern>
<url-pattern>*.JSPF</url-pattern>
<url-pattern>*.JSPX</url-pattern>
<url-pattern>*.XSP</url-pattern>
</servlet-mapping>
<!-- ==================================================================== -->
<!-- Dynamic Servlet Invoker. -->
<!-- This servlet invokes anonymous servlets that have not been defined -->
<!-- in the web.xml or by other means. The first element of the pathInfo -->
<!-- of a request passed to the envoker is treated as a servlet name for -->
<!-- an existing servlet, or as a class name of a new servlet. -->
<!-- This servlet is normally mapped to /servlet/* -->
<!-- This servlet support the following initParams: -->
<!-- -->
<!-- nonContextServlets If false, the invoker can only load -->
<!-- servlets from the contexts classloader. -->
<!-- This is false by default and setting this -->
<!-- to true may have security implications. -->
<!-- -->
<!-- verbose If true, log dynamic loads -->
<!-- -->
<!-- * All other parameters are copied to the -->
<!-- each dynamic servlet as init parameters -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Uncomment for dynamic invocation
<servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>org.mortbay.jetty.servlet.Invoker</servlet-class>
<init-param>
<param-name>verbose</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>nonContextServlets</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>dynamicParam</param-name>
<param-value>anyValue</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping> <servlet-name>invoker</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
-->
<!-- ==================================================================== -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<!-- ==================================================================== -->
<!-- Default MIME mappings -->
<!-- The default MIME mappings are provided by the mime.properties -->
<!-- resource in the org.mortbay.jetty.jar file. Additional or modified -->
<!-- mappings may be specified here -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- UNCOMMENT TO ACTIVATE
<mime-mapping>
<extension>mysuffix</extension>
<mime-type>mymime/type</mime-type>
</mime-mapping>
-->
<!-- ==================================================================== -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- ==================================================================== -->
<locale-encoding-mapping-list>
<locale-encoding-mapping><locale>ar</locale><encoding>ISO-8859-6</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>be</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>bg</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>ca</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>cs</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>da</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>de</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>el</locale><encoding>ISO-8859-7</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>en</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>es</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>et</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>fi</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>fr</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>hr</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>hu</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>is</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>it</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>iw</locale><encoding>ISO-8859-8</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>ja</locale><encoding>Shift_JIS</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>ko</locale><encoding>EUC-KR</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>lt</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>lv</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>mk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>nl</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>no</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>pl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>pt</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>ro</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>ru</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>sh</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>sk</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>sl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>sq</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>sr</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>sv</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>tr</locale><encoding>ISO-8859-9</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>uk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>zh</locale><encoding>GB2312</encoding></locale-encoding-mapping>
<locale-encoding-mapping><locale>zh_TW</locale><encoding>Big5</encoding></locale-encoding-mapping>
</locale-encoding-mapping-list>
<security-constraint>
<web-resource-collection>
<web-resource-name>Disable TRACE</web-resource-name>
<url-pattern>/</url-pattern>
<http-method>TRACE</http-method>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
</web-app>

Binary file not shown.

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache 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.apache.org/licenses/LICENSE-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.

View File

@ -0,0 +1,36 @@
==============================================================
Jetty Web Container
Copyright 1995-2009 Mort Bay Consulting Pty Ltd
==============================================================
The Jetty Web Container is Copyright Mort Bay Consulting Pty Ltd
unless otherwise noted. It is licensed under the apache 2.0
license.
The javax.servlet package used by Jetty is copyright
Sun Microsystems, Inc and Apache Software Foundation. It is
distributed under the Common Development and Distribution License.
You can obtain a copy of the license at
https://glassfish.dev.java.net/public/CDDLv1.0.html.
The UnixCrypt.java code ~Implements the one way cryptography used by
Unix systems for simple password protection. Copyright 1996 Aki Yoshida,
modified April 2001 by Iris Van den Broeke, Daniel Deville.
Permission to use, copy, modify and distribute UnixCrypt
for non-commercial or commercial purposes and without fee is
granted provided that the copyright notice appears in all copies.
The default JSP implementation is provided by the Glassfish JSP engine
from project Glassfish http://glassfish.dev.java.net. Copyright 2005
Sun Microsystems, Inc. and portions Copyright Apache Software Foundation.
Some portions of the code are Copyright:
2006 Tim Vernum
1999 Jason Gilbert.
The jboss integration module contains some LGPL code.
The win32 Java Service Wrapper (v3.2.3) is Copyright (c) 1999, 2006
Tanuki Software, Inc. and 2001 Silver Egg Technology. It is
covered by an open license which is viewable at
http://svn.codehaus.org/jetty/jetty/branches/jetty-6.1/extras/win32service/LICENSE.txt

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,3 @@
This is an alternative setup structure to support multiple cores.
For general examples on standard solr configuration, see the "solr" directory.

View File

@ -0,0 +1,52 @@
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Solr index</display-name>
<servlet>
<servlet-name>webdav</servlet-name>
<servlet-class>org.apache.catalina.servlets.WebdavServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>1</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>webdav</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Solr Index</web-resource-name>
<!-- Detect WebDAV Methods in URL For Whole Application -->
<url-pattern>/*</url-pattern>
</web-resource-collection>
<!-- Restrict access by role -->
<auth-constraint>
<role-name>solrwebdavaccess</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Solr Index</realm-name>
</login-config>
<security-role>
<description>Solr index access</description>
<role-name>solrwebdavaccess</role-name>
</security-role>
</web-app>

33
thirdparty/solr/server/solr/solr.xml vendored Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.
-->
<!--
All (relative) paths are relative to the installation path
persistent: Save changes made via the API to this file
sharedLib: path to a lib directory that will be shared across all cores
-->
<solr persistent="true">
<!--
adminPath: RequestHandler path to manage cores.
If 'null' (or absent), cores will not be manageable via request handler
-->
<cores adminPath="/admin/cores">
</cores>
</solr>

BIN
thirdparty/solr/server/start.jar vendored Normal file

Binary file not shown.

BIN
thirdparty/solr/server/webapps/solr.war vendored Normal file

Binary file not shown.