active-fedora 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ module ActiveFedora
2
+ class Property
3
+
4
+ attr_accessor :name, :instance_variable_name
5
+
6
+ def initialize(model, name, type, options = {})
7
+ @name = name
8
+ @instance_variable_name = "@#{@name}"
9
+ end
10
+
11
+ def field
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,80 @@
1
+ module ActiveFedora
2
+ #This class represents a Qualified Dublin Core Datastream. A special case of ActiveFedora::MetdataDatastream
3
+ #The implementation of this class defines the terms from the Qualified Dublin Core specification.
4
+ #This implementation features customized xml generators and deserialization routines to handle the
5
+ #Fedora Dublin Core XML datastreams structure.
6
+ #
7
+ #Fields can still be overridden if more specificity is desired (see ActiveFedora::Datastream#fields method).
8
+ class QualifiedDublinCoreDatastream < MetadataDatastream
9
+ #A frozen array of Dublincore Terms.
10
+ DCTERMS = [
11
+ :contributor, :coverage, :creator, :description, :format, :identifier, :language, :publisher, :relation, :source, :title, :abstract, :accessRights, :accrualMethod, :accrualPeriodicity, :accrualPolicy, :alternative, :audience, :available, :bibliographicCitation, :conformsTo, :contributor, :coverage, :created, :creator, :date, :dateAccepted, :dateCopyrighted, :dateSubmitted, :description, :educationLevel, :extent, :format, :hasFormat, :hasPart, :hasVersion, :identifier, :instructionalMethod, :isFormatOf, :isPartOf, :isReferencedBy, :isReplacedBy, :isRequiredBy, :issued, :isVersionOf, :language, :license, :mediator, :medium, :modified, :provenance, :publisher, :references, :relation, :replaces, :requires, :rights, :rightsHolder, :source, :spatial, :subject, :tableOfContents, :temporal, :type, :valid
12
+ ]
13
+ DCTERMS.freeze
14
+
15
+ #Constructor. this class will call self.field for each DCTERM. In short, all DCTERMS fields will already exist
16
+ #when this method returns. Each term is marked as a multivalue string.
17
+ def initialize(attrs=nil)
18
+ super
19
+ DCTERMS.each do |el|
20
+ field el, :string, :multiple=>true
21
+ end
22
+ self
23
+ end
24
+
25
+ def set_blob_for_save # :nodoc:
26
+ self.blob = self.to_dc_xml
27
+ end
28
+
29
+ def self.from_xml(tmpl, el) # :nodoc:
30
+ tmpl.fields.each do |z|
31
+ fname = z.first
32
+ fspec = z.last
33
+ node = "dcterms:#{fspec[:xml_node] ? fspec[:xml_node] : fname}"
34
+ attr_modifier= "[@xsi:type='#{fspec[:encoding]}']" if fspec[:encoding]
35
+ query = "./foxml:datastreamVersion[last()]/foxml:xmlContent/dc/#{node}#{attr_modifier}"
36
+ el.elements.each(query) do |f|
37
+ tmpl.send("#{fname}_append", f.text)
38
+ end
39
+
40
+ end
41
+ tmpl.instance_variable_set(:@dirty, false)
42
+ tmpl
43
+ end
44
+
45
+ #Render self as a Fedora DC xml document.
46
+ def to_dc_xml
47
+ #TODO: pull the modifiers up into MDDS
48
+ xml = REXML::Document.new("<dc xmlns:dcterms='http://purl.org/dc/terms/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'/>")
49
+ fields.each do |field_name,field_info|
50
+ el = REXML::Element.new("dcterms:#{field_name.to_s}")
51
+ if field_info.class == Hash
52
+ field_info.each do |k, v|
53
+ case k
54
+ when :element_attrs
55
+ v.each{|k,v| el.add_attribute(k.to_s, v.to_s)}
56
+ when :values, :type
57
+ # do nothing to the :values array
58
+ when :xml_node
59
+ el.name = "dcterms:#{v}"
60
+ when :encoding, :encoding_scheme
61
+ el.add_attribute("xsi:type", v)
62
+ when :multiple
63
+ next
64
+ else
65
+ el.add_attribute(k.to_s, v)
66
+ end
67
+ end
68
+ field_info = field_info[:values]
69
+ end
70
+ field_info.each do |val|
71
+ el = el.clone
72
+ el.text = val.to_s
73
+ xml.root.elements.add(el)
74
+ end
75
+ end
76
+ return xml.to_s
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,43 @@
1
+ module ActiveFedora
2
+
3
+ class Relationship
4
+
5
+ attr_accessor :subject, :predicate, :object, :is_literal, :data_type
6
+ def initialize(attr={})
7
+ attr.merge!({:is_literal => false})
8
+ self.subject = attr[:subject]
9
+ @predicate = attr[:predicate]
10
+ self.object = attr[:object]
11
+ @is_literal = attr[:is_literal]
12
+ @data_type = attr[:data_type]
13
+ end
14
+
15
+ def subject=(subject)
16
+ @subject = generate_uri(subject)
17
+ end
18
+
19
+ def subject_pid=(pid)
20
+ @subject = "info:fedora/#{pid}"
21
+ end
22
+
23
+ def object=(object)
24
+ @object = generate_uri(object)
25
+ end
26
+
27
+ def object_pid=(pid)
28
+ @object = "info:fedora/#{pid}"
29
+ end
30
+
31
+ def generate_uri(input)
32
+ if input.class == Symbol || input == nil
33
+ return input
34
+ elsif input.respond_to?(:pid)
35
+ return "info:fedora/#{input.pid}"
36
+ else
37
+ input.include?("info:fedora") ? input : "info:fedora/#{input}"
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,42 @@
1
+
2
+ module ActiveFedora
3
+ class RelsExtDatastream < Datastream
4
+
5
+ include ActiveFedora::SemanticNode
6
+
7
+ def initialize(attrs=nil)
8
+ super
9
+ self.dsid = "RELS-EXT"
10
+ end
11
+
12
+ def save
13
+ if @dirty == true
14
+ self.content = to_rels_ext(self.pid)
15
+ end
16
+ super
17
+ end
18
+
19
+ def pid=(pid)
20
+ super
21
+ self.blob = <<-EOL
22
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
23
+ <rdf:Description rdf:about="info:fedora/#{pid}">
24
+ </rdf:Description>
25
+ </rdf:RDF>
26
+ EOL
27
+ end
28
+
29
+ def to_solr(solr_doc = Solr::Document.new)
30
+ self.relationships.each_pair do |subject, predicates|
31
+ if subject == :self || subject == "info:fedora/#{self.pid}"
32
+ predicates.each_pair do |predicate, values|
33
+ values.each do |val|
34
+ solr_doc << Solr::Field.new("#{predicate}_field" => val)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ return solr_doc
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,224 @@
1
+ module ActiveFedora
2
+ module SemanticNode
3
+ include MediaShelfClassLevelInheritableAttributes
4
+ ms_inheritable_attributes :class_relationships, :internal_uri
5
+
6
+ attr_accessor :internal_uri, :relationships
7
+
8
+ PREDICATE_MAPPINGS = Hash[:is_member_of => "isMemberOf",
9
+ :is_part_of => "isPartOf",
10
+ :has_part => "hasPart",
11
+ :conforms_to => "conformsTo"]
12
+ PREDICATE_MAPPINGS.freeze
13
+
14
+ def self.included(klass)
15
+ klass.extend(ClassMethods)
16
+ end
17
+ def assert_kind_of(n, o,t)
18
+ raise "Assertion failure: #{n}: #{o} is not of type #{t}" unless o.kind_of?(t)
19
+ end
20
+
21
+ def add_relationship(relationship)
22
+ # Only accept ActiveFedora::Relationships as input arguments
23
+ assert_kind_of 'relationship', relationship, ActiveFedora::Relationship
24
+ register_triple(relationship.subject, relationship.predicate, relationship.object)
25
+ end
26
+
27
+ def register_triple(subject, predicate, object)
28
+ register_subject(subject)
29
+ register_predicate(subject, predicate)
30
+ relationships[subject][predicate] << object
31
+ end
32
+
33
+ def register_subject(subject)
34
+ if !relationships.has_key?(subject)
35
+ relationships[subject] = {}
36
+ end
37
+ end
38
+
39
+ def register_predicate(subject, predicate)
40
+ register_subject(subject)
41
+ if !relationships[subject].has_key?(predicate)
42
+ relationships[subject][predicate] = []
43
+ end
44
+ end
45
+
46
+ def outbound_relationships()
47
+ if !internal_uri.nil? && !relationships[internal_uri].nil?
48
+ return relationships[:self].merge(relationships[internal_uri])
49
+ else
50
+ return relationships[:self]
51
+ end
52
+ end
53
+
54
+ def relationships
55
+ @relationships ||= relationships_from_class
56
+ end
57
+
58
+ def relationships_from_class
59
+ rels = {}
60
+ self.class.relationships.each_pair do |subj, pred|
61
+ rels[subj] = {}
62
+ pred.each_key do |pred_key|
63
+ rels[subj][pred_key] = []
64
+ end
65
+ end
66
+ #puts "rels from class: #{rels.inspect}"
67
+ return rels
68
+ end
69
+
70
+ # Creates a RELS-EXT datastream for insertion into a Fedora Object
71
+ # @pid
72
+ # Note: This method is implemented on SemanticNode instead of RelsExtDatastream because SemanticNode contains the relationships array
73
+ def to_rels_ext(pid, relationships=self.relationships)
74
+ starter_xml = <<-EOL
75
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
76
+ <rdf:Description rdf:about="info:fedora/#{pid}">
77
+ </rdf:Description>
78
+ </rdf:RDF>
79
+ EOL
80
+ xml = REXML::Document.new(starter_xml)
81
+
82
+ # Iterate through the hash of predicates, adding an element to the RELS-EXT for each "object" in the predicate's corresponding array.
83
+ # puts ""
84
+ # puts "Iterating through a(n) #{self.class}"
85
+ # puts "=> whose relationships are #{self.relationships.inspect}"
86
+ # puts "=> and whose outbound relationships are #{self.outbound_relationships.inspect}"
87
+ self.outbound_relationships.each do |predicate, targets_array|
88
+ targets_array.each do |target|
89
+ # puts ". #{predicate} #{target}"
90
+ xml.root.elements["rdf:Description"].add_element(self.class.predicate_lookup(predicate), {"xmlns" => "info:fedora/fedora-system:def/relations-external#", "rdf:resource"=>target})
91
+ end
92
+ end
93
+ xml.to_s
94
+ end
95
+
96
+ module ClassMethods
97
+
98
+ # Anticipates usage of a relationship in classes that include this module
99
+ # Creates a key in the @relationships array for the predicate provided. Assumes
100
+ # :self as the subject of the relationship unless :inbound => true, in which case the
101
+ # predicate is registered under @relationships[:inbound][#{predicate}]
102
+ #
103
+ # TODO:
104
+ # Custom Methods:
105
+ # A custom finder method will be appended based on the relationship name.
106
+ # ie.
107
+ # class Foo
108
+ # relationship "container", :is_member_of
109
+ # end
110
+ # foo = Foo.new
111
+ # foo.parts
112
+ #
113
+ # Special Predicate Short Hand:
114
+ # These symbols map to the uris of corresponding Fedora RDF predicates
115
+ # :is_member_of, :has_member, :is_part_of, :has_part
116
+ def has_relationship(name, predicate, opts = {})
117
+ opts = {:singular => nil, :inbound => false}.merge(opts)
118
+ opts[:inbound] == true ? register_predicate(:inbound, predicate) : register_predicate(:self, predicate)
119
+
120
+ if opts[:inbound] == true
121
+ create_inbound_relationship_finders(name, predicate, opts)
122
+ else
123
+ create_outbound_relationship_finders(name, predicate, opts)
124
+ end
125
+
126
+ end
127
+
128
+ def create_inbound_relationship_finders(name, predicate, opts = {})
129
+ class_eval <<-END
130
+ def #{name}
131
+ escaped_uri = self.internal_uri.gsub(/(:)/, '\\:')
132
+ hits = SolrService.instance.conn.query("#{predicate}_field:\#{escaped_uri}").hits
133
+ results = []
134
+ hits.each do |hit|
135
+ classname = Kernel.const_get(hit["active_fedora_model_field"].first)
136
+ results << Fedora::Repository.instance.find_model(hit["id"], classname)
137
+ end
138
+ return results
139
+ end
140
+ def #{name}_ids
141
+ escaped_uri = self.internal_uri.gsub(/(:)/, '\\:')
142
+ SolrService.instance.conn.query("#{predicate}_field:\#{escaped_uri}")
143
+ end
144
+ END
145
+ end
146
+
147
+ def create_outbound_relationship_finders(name, predicate, opts = {})
148
+ class_eval <<-END
149
+ def #{name}
150
+ results = []
151
+ outbound_relationships[#{predicate.inspect}].each do |rel|
152
+ uri_as_pid = rel.gsub("info:fedora/", "")
153
+ results << Fedora::Repository.instance.find_model(uri_as_pid, ActiveFedora::Base)
154
+ end
155
+ return results
156
+ end
157
+ def #{name}_ids
158
+ results = []
159
+ outbound_relationships[#{predicate.inspect}].each do |rel|
160
+ results << rel
161
+ end
162
+ end
163
+ END
164
+ end
165
+
166
+ # relationships are tracked as a hash of structure {subject => {predicate => [object]}}
167
+ def relationships
168
+ @class_relationships ||= Hash[:self => {}]
169
+ end
170
+
171
+
172
+ def register_subject(subject)
173
+ if !relationships.has_key?(subject)
174
+ relationships[subject] = {}
175
+ end
176
+ end
177
+
178
+ def register_predicate(subject, predicate)
179
+ register_subject(subject)
180
+ if !relationships[subject].has_key?(predicate)
181
+ relationships[subject][predicate] = []
182
+ end
183
+ end
184
+
185
+ #alias_method :register_target, :register_object
186
+
187
+ # Creates a RELS-EXT datastream for insertion into a Fedora Object
188
+ # @pid
189
+ # Note: This method is implemented on SemanticNode instead of RelsExtDatastream because SemanticNode contains the relationships array
190
+ def relationships_to_rels_ext(pid, relationships=self.relationships)
191
+ starter_xml = <<-EOL
192
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
193
+ <rdf:Description rdf:about="info:fedora/#{pid}">
194
+ </rdf:Description>
195
+ </rdf:RDF>
196
+ EOL
197
+ xml = REXML::Document.new(starter_xml)
198
+
199
+ # Iterate through the hash of predicates, adding an element to the RELS-EXT for each "object" in the predicate's corresponding array.
200
+ self.outbound_relationships.each do |predicate, targets_array|
201
+ targets_array.each do |target|
202
+ #puts ". #{predicate} #{target}"
203
+ xml.root.elements["rdf:Description"].add_element(predicate_lookup(predicate), {"xmlns" => "info:fedora/fedora-system:def/relations-external#", "rdf:resource"=>target})
204
+ end
205
+ end
206
+ xml.to_s
207
+ end
208
+
209
+ # If predicate is a symbol, looks up the predicate in the PREDICATE_MAPPINGS
210
+ # If predicate is not a Symbol, returns the predicate untouched
211
+ # @throws UnregisteredPredicateError if the predicate is a symbol but is not found in the PREDICATE_MAPPINGS
212
+ def predicate_lookup(predicate)
213
+ if predicate.class == Symbol
214
+ if PREDICATE_MAPPINGS.has_key?(predicate)
215
+ return PREDICATE_MAPPINGS[predicate]
216
+ else
217
+ throw UnregisteredPredicateError
218
+ end
219
+ end
220
+ return predicate
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,20 @@
1
+ require 'solr'
2
+ module ActiveFedora
3
+ class SolrService
4
+ attr_reader :conn
5
+ def self.register(host=nil, args={})
6
+ Thread.current[:solr_service]=self.new(host, args)
7
+
8
+ end
9
+ def initialize(host, args)
10
+ host = 'http://localhost:8080/solr' unless host
11
+ opts = {:autocommit=>:on}.merge(args)
12
+ @conn = Solr::Connection.new(host, opts)
13
+ end
14
+ def self.instance
15
+ raise SolrNotInitialized unless Thread.current[:solr_service]
16
+ Thread.current[:solr_service]
17
+ end
18
+ end
19
+ class SolrNotInitialized < StandardError;end
20
+ end
@@ -0,0 +1,229 @@
1
+ <?xml version="1.0" ?>
2
+ <!--
3
+ Licensed to the Apache Software Foundation (ASF) under one or more
4
+ contributor license agreements. See the NOTICE file distributed with
5
+ this work for additional information regarding copyright ownership.
6
+ The ASF licenses this file to You under the Apache License, Version 2.0
7
+ (the "License"); you may not use this file except in compliance with
8
+ the License. You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS,
14
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ See the License for the specific language governing permissions and
16
+ limitations under the License.
17
+ -->
18
+
19
+ <!-- This is the Solr schema file. This file should be named "schema.xml" and
20
+ should be in the conf directory under the solr home
21
+ (i.e. ./solr/conf/schema.xml by default)
22
+ or located where the classloader for the Solr webapp can find it.
23
+
24
+ For more information, on how to customize this file, please see
25
+ http://wiki.apache.org/solr/SchemaXml
26
+ -->
27
+
28
+ <schema name="active_fedora" version="0.1">
29
+ <!-- attribute "name" is the name of this schema and is only used for display purposes.
30
+ Applications should change this to reflect the nature of the search collection.
31
+ version="1.1" is Solr's version number for the schema syntax and semantics. It should
32
+ not normally be changed by applications.
33
+ 1.0: multiValued attribute did not exist, all fields are multiValued by nature
34
+ 1.1: multiValued attribute introduced, false by default -->
35
+
36
+ <types>
37
+ <!-- field type definitions. The "name" attribute is
38
+ just a label to be used by field definitions. The "class"
39
+ attribute and any other attributes determine the real
40
+ behavior of the fieldtype.
41
+ Class names starting with "solr" refer to java classes in the
42
+ org.apache.solr.analysis package.
43
+ -->
44
+
45
+ <!-- The StrField type is not analyzed, but indexed/stored verbatim.
46
+ - StrField and TextField support an optional compressThreshold which
47
+ limits compression (if enabled in the derived fields) to values which
48
+ exceed a certain size (in characters).
49
+ -->
50
+ <fieldtype name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
51
+
52
+ <!-- boolean type: "true" or "false" -->
53
+ <fieldtype name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
54
+
55
+ <!-- The optional sortMissingLast and sortMissingFirst attributes are
56
+ currently supported on types that are sorted internally as strings.
57
+ - If sortMissingLast="true", then a sort on this field will cause documents
58
+ without the field to come after documents with the field,
59
+ regardless of the requested sort order (asc or desc).
60
+ - If sortMissingFirst="true", then a sort on this field will cause documents
61
+ without the field to come before documents with the field,
62
+ regardless of the requested sort order.
63
+ - If sortMissingLast="false" and sortMissingFirst="false" (the default),
64
+ then default lucene sorting will be used which places docs without the
65
+ field first in an ascending sort and last in a descending sort.
66
+ -->
67
+
68
+
69
+ <!-- numeric field types that store and index the text
70
+ value verbatim (and hence don't support range queries, since the
71
+ lexicographic ordering isn't equal to the numeric ordering) -->
72
+ <fieldtype name="integer" class="solr.IntField" omitNorms="true"/>
73
+ <fieldtype name="long" class="solr.LongField" omitNorms="true"/>
74
+ <fieldtype name="float" class="solr.FloatField" omitNorms="true"/>
75
+ <fieldtype name="double" class="solr.DoubleField" omitNorms="true"/>
76
+
77
+
78
+ <!-- Numeric field types that manipulate the value into
79
+ a string value that isn't human-readable in its internal form,
80
+ but with a lexicographic ordering the same as the numeric ordering,
81
+ so that range queries work correctly. -->
82
+ <fieldtype name="sint" class="solr.SortableIntField" sortMissingLast="true" omitNorms="true"/>
83
+ <fieldtype name="slong" class="solr.SortableLongField" sortMissingLast="true" omitNorms="true"/>
84
+ <fieldtype name="sfloat" class="solr.SortableFloatField" sortMissingLast="true" omitNorms="true"/>
85
+ <fieldtype name="sdouble" class="solr.SortableDoubleField" sortMissingLast="true" omitNorms="true"/>
86
+
87
+
88
+ <!-- The format for this date field is of the form 1995-12-31T23:59:59Z, and
89
+ is a more restricted form of the canonical representation of dateTime
90
+ http://www.w3.org/TR/xmlschema-2/#dateTime
91
+ The trailing "Z" designates UTC time and is mandatory.
92
+ Optional fractional seconds are allowed: 1995-12-31T23:59:59.999Z
93
+ All other components are mandatory.
94
+
95
+ Expressions can also be used to denote calculations that should be
96
+ performed relative to "NOW" to determine the value, ie...
97
+
98
+ NOW/HOUR
99
+ ... Round to the start of the current hour
100
+ NOW-1DAY
101
+ ... Exactly 1 day prior to now
102
+ NOW/DAY+6MONTHS+3DAYS
103
+ ... 6 months and 3 days in the future from the start of
104
+ the current day
105
+
106
+ Consult the DateField javadocs for more information.
107
+ -->
108
+ <fieldtype name="date" class="solr.DateField" sortMissingLast="true" omitNorms="true"/>
109
+
110
+ <!-- solr.TextField allows the specification of custom text analyzers
111
+ specified as a tokenizer and a list of token filters. Different
112
+ analyzers may be specified for indexing and querying.
113
+
114
+ The optional positionIncrementGap puts space between multiple fields of
115
+ this type on the same document, with the purpose of preventing false phrase
116
+ matching across fields.
117
+
118
+ For more info on customizing your analyzer chain, please see
119
+ http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters
120
+ -->
121
+
122
+ <!-- One can also specify an existing Analyzer class that has a
123
+ default constructor via the class attribute on the analyzer element
124
+ <fieldtype name="text_greek" class="solr.TextField">
125
+ <analyzer class="org.apache.lucene.analysis.el.GreekAnalyzer"/>
126
+ </fieldType>
127
+ -->
128
+
129
+ <!-- A text field that only splits on whitespace for exact matching of words -->
130
+ <fieldtype name="text_ws" class="solr.TextField" positionIncrementGap="100">
131
+ <analyzer>
132
+ <tokenizer class="solr.WhitespaceTokenizerFactory"/>
133
+ </analyzer>
134
+ </fieldtype>
135
+
136
+ <fieldtype name="text" class="solr.TextField" positionIncrementGap="100">
137
+ <analyzer>
138
+ <tokenizer class="solr.StandardTokenizerFactory"/>
139
+ <filter class="solr.StandardFilterFactory"/>
140
+ <filter class="solr.LowerCaseFilterFactory"/>
141
+ </analyzer>
142
+ </fieldtype>
143
+
144
+ <fieldtype name="text_zh" class="solr.TextField">
145
+ <analyzer class="org.apache.lucene.analysis.cn.ChineseAnalyzer"/>
146
+ </fieldtype>
147
+
148
+
149
+ <!-- Less flexible matching, but less false matches. Probably not ideal for product names,
150
+ but may be good for SKUs. Can insert dashes in the wrong place and still match. -->
151
+ <fieldtype name="textTight" class="solr.TextField" positionIncrementGap="100" >
152
+ <analyzer>
153
+ <tokenizer class="solr.WhitespaceTokenizerFactory"/>
154
+ <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
155
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
156
+ <filter class="solr.WordDelimiterFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
157
+ <filter class="solr.LowerCaseFilterFactory"/>
158
+ <filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/>
159
+ <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
160
+ </analyzer>
161
+ </fieldtype>
162
+
163
+ </types>
164
+
165
+
166
+ <fields>
167
+ <!-- Valid attributes for fields:
168
+ name: mandatory - the name for the field
169
+ type: mandatory - the name of a previously defined type from the <types> section
170
+ indexed: true if this field should be indexed (searchable or sortable)
171
+ stored: true if this field should be retrievable
172
+ compressed: [false] if this field should be stored using gzip compression
173
+ (this will only apply if the field type is compressable; among
174
+ the standard field types, only TextField and StrField are)
175
+ multiValued: true if this field may contain multiple values per document
176
+ omitNorms: (expert) set to true to omit the norms associated with
177
+ this field (this disables length normalization and index-time
178
+ boosting for the field, and saves some memory). Only full-text
179
+ fields or fields that need an index-time boost need norms.
180
+ -->
181
+
182
+ <field name="id" type="string" indexed="true" stored="true"/>
183
+
184
+ <!-- catchall field, containing all other searchable text fields (implemented
185
+ via copyField further on in this schema -->
186
+ <field name="text" type="text" indexed="true" stored="false" multiValued="true"/>
187
+ <!-- catchall field, containing all other searchable text fields (implemented
188
+ via copyField further on in this schema -->
189
+ <field name="date" type="date" indexed="true" stored="false" multiValued="true"/>
190
+
191
+ <!-- Dynamic field definitions. If a field name is not found, dynamicFields
192
+ will be used if the name matches any of the patterns.
193
+ RESTRICTION: the glob-like pattern in the name attribute must have
194
+ a "*" only at the start or the end.
195
+ EXAMPLE: name="*_i" will match any field ending in _i (like myid_i, z_i)
196
+ Longer patterns will be matched first. if equal size patterns
197
+ both match, the first appearing in the schema will be used. -->
198
+ <dynamicField name="*_date" type="date" indexed="true" stored="true" multiValued="true"/>
199
+ <dynamicField name="*_name" type="date" indexed="true" stored="true" multiValued="true"/>
200
+ <dynamicField name="*_field" type="text" indexed="true" stored="true" multiValued="true"/>
201
+
202
+ <dynamicField name="*_facet" type="string" indexed="true" stored="true" multiValued="true"/>
203
+ <dynamicField name="*_zh_text" type="text_zh" indexed="true" stored="true" multiValued="true"/>
204
+ <dynamicField name="*_text" type="text" indexed="true" stored="true" multiValued="true"/>
205
+ <dynamicField name="*_display" type="text" indexed="false" stored="true" multiValued="true"/>
206
+ </fields>
207
+
208
+ <!-- field to use to determine and enforce document uniqueness. -->
209
+ <uniqueKey>id</uniqueKey>
210
+
211
+ <!-- field for the QueryParser to use when an explicit fieldname is absent -->
212
+ <defaultSearchField>text</defaultSearchField>
213
+
214
+ <!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
215
+ <solrQueryParser defaultOperator="AND"/>
216
+
217
+ <!-- copyField commands copy one field to another at the time a document
218
+ is added to the index. It's used either to index the same field differently,
219
+ or to add multiple fields to the same field for easier/faster searching. -->
220
+ <copyField source="*_date" dest="date"/>
221
+ <copyField source="*_text" dest="text"/>
222
+ <copyField source="*_facet" dest="text"/>
223
+
224
+ <!-- Similarity is the scoring routine for each document vs. a query.
225
+ A custom similarity may be specified here, but the default is fine
226
+ for most applications. -->
227
+ <!-- <similarity class="org.apache.lucene.search.DefaultSimilarity"/> -->
228
+
229
+ </schema>