active-fedora 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>