PageRenderTime 34ms CodeModel.GetById 2ms app.highlight 26ms RepoModel.GetById 1ms app.codeStats 0ms

/Raven.Database/Indexing/AnonymousObjectToLuceneDocumentConverter.cs

Relevant Search: With Applications for Solr and Elasticsearch

'Chapter 4. Taming tokens'. If you want to know how to extract ideas rather than words this book is for you. Learn concepts of precision and recall, making trade-offs between them and controlling the specificity of matches. Amazon Affiliate Link
https://github.com/nwendel/ravendb
C# | 601 lines | 508 code | 71 blank | 22 comment | 172 complexity | a748c845f6118435816670b4cf30132a MD5 | raw file
  1//-----------------------------------------------------------------------
  2// <copyright file="AnonymousObjectToLuceneDocumentConverter.cs" company="Hibernating Rhinos LTD">
  3//     Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4// </copyright>
  5//-----------------------------------------------------------------------
  6using System;
  7using System.Collections;
  8using System.Collections.Generic;
  9using System.ComponentModel;
 10using System.Globalization;
 11using System.IO;
 12using System.Linq;
 13using Lucene.Net.Documents;
 14using Lucene.Net.Search;
 15
 16using Raven.Abstractions.Extensions;
 17using Raven.Abstractions.Logging;
 18using Raven.Database.Linq;
 19using Raven.Imports.Newtonsoft.Json;
 20using Raven.Imports.Newtonsoft.Json.Linq;
 21using Raven.Abstractions;
 22using Raven.Abstractions.Data;
 23using Raven.Abstractions.Indexing;
 24using Raven.Abstractions.Linq;
 25using Raven.Database.Extensions;
 26using Raven.Json.Linq;
 27
 28namespace Raven.Database.Indexing
 29{
 30	public class AnonymousObjectToLuceneDocumentConverter
 31	{
 32		private readonly AbstractViewGenerator viewGenerator;
 33
 34		private readonly ILog log;
 35
 36		private readonly DocumentDatabase database;
 37		private readonly IndexDefinition indexDefinition;
 38		private readonly List<int> multipleItemsSameFieldCount = new List<int>();
 39		private readonly Dictionary<FieldCacheKey, Field> fieldsCache = new Dictionary<FieldCacheKey, Field>();
 40		private readonly Dictionary<FieldCacheKey, NumericField> numericFieldsCache = new Dictionary<FieldCacheKey, NumericField>();
 41
 42		public AnonymousObjectToLuceneDocumentConverter(DocumentDatabase database, IndexDefinition indexDefinition, AbstractViewGenerator viewGenerator, ILog log)
 43		{
 44			this.database = database;
 45			this.indexDefinition = indexDefinition;
 46			this.viewGenerator = viewGenerator;
 47			this.log = log;
 48		}
 49
 50		public IEnumerable<AbstractField> Index(object val, PropertyDescriptorCollection properties, Field.Store defaultStorage)
 51		{
 52			return from property in properties.Cast<PropertyDescriptor>()
 53				   where property.Name != Constants.DocumentIdFieldName
 54				   from field in CreateFields(property.Name, property.GetValue(val), defaultStorage)
 55				   select field;
 56		}
 57
 58		public IEnumerable<AbstractField> Index(RavenJObject document, Field.Store defaultStorage)
 59		{
 60			return from property in document
 61				   where property.Key != Constants.DocumentIdFieldName
 62				   from field in CreateFields(property.Key, GetPropertyValue(property.Value), defaultStorage)
 63				   select field;
 64		}
 65
 66		private static object GetPropertyValue(RavenJToken property)
 67		{
 68			switch (property.Type)
 69			{
 70				case JTokenType.Array:
 71				case JTokenType.Object:
 72					return property.ToString(Formatting.None);
 73				default:
 74					return property.Value<object>();
 75			}
 76		}
 77
 78		/// <summary>
 79		/// This method generate the fields for indexing documents in lucene from the values.
 80		/// Given a name and a value, it has the following behavior:
 81		/// * If the value is enumerable, index all the items in the enumerable under the same field name
 82		/// * If the value is null, create a single field with the supplied name with the unanalyzed value 'NULL_VALUE'
 83		/// * If the value is string or was set to not analyzed, create a single field with the supplied name
 84		/// * If the value is date, create a single field with millisecond precision with the supplied name
 85		/// * If the value is numeric (int, long, double, decimal, or float) will create two fields:
 86		///		1. with the supplied name, containing the numeric value as an unanalyzed string - useful for direct queries
 87		///		2. with the name: name +'_Range', containing the numeric value in a form that allows range queries
 88		/// </summary>
 89        public IEnumerable<AbstractField> CreateFields(string name, object value, Field.Store defaultStorage, bool nestedArray = false, Field.TermVector defaultTermVector = Field.TermVector.NO, Field.Index? analyzed = null)
 90		{
 91			if (string.IsNullOrWhiteSpace(name))
 92				throw new ArgumentException("Field must be not null, not empty and cannot contain whitespace", "name");
 93
 94			if (char.IsLetter(name[0]) == false && name[0] != '_')
 95			{
 96				name = "_" + name;
 97			}
 98
 99			if (viewGenerator.IsSpatialField(name))
100				return viewGenerator.GetSpatialField(name).CreateIndexableFields(value);
101
102            return CreateRegularFields(name, value, defaultStorage, nestedArray, defaultTermVector, analyzed);
103		}
104
105		private IEnumerable<AbstractField> CreateRegularFields(string name, object value, Field.Store defaultStorage, bool nestedArray = false, Field.TermVector defaultTermVector = Field.TermVector.NO, Field.Index? analyzed = null)
106		{
107            var fieldIndexingOptions = analyzed ?? indexDefinition.GetIndex(name, null);
108			var storage = indexDefinition.GetStorage(name, defaultStorage);
109			var termVector = indexDefinition.GetTermVector(name, defaultTermVector);
110
111			if (fieldIndexingOptions == Field.Index.NO && storage == Field.Store.NO && termVector == Field.TermVector.NO)
112			{
113				yield break;
114			}
115
116			if (fieldIndexingOptions == Field.Index.NO && storage == Field.Store.NO)
117			{
118				fieldIndexingOptions = Field.Index.ANALYZED; // we have some sort of term vector, forcing index to be analyzed, then.
119			}
120
121			if (value == null)
122			{
123				yield return CreateFieldWithCaching(name, Constants.NullValue, storage,
124								 Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
125				yield break;
126			}
127
128			CheckIfSortOptionsAndInputTypeMatch(name, value);
129
130			var attachmentFoIndexing = value as AttachmentForIndexing;
131			if (attachmentFoIndexing != null)
132			{
133				if (database == null)
134					throw new InvalidOperationException(
135						"Cannot use attachment for indexing if the database parameter is null. This is probably a RavenDB bug");
136
137				var attachment = database.Attachments.GetStatic(attachmentFoIndexing.Key);
138				if (attachment == null)
139				{
140					yield break;
141				}
142
143				var fieldWithCaching = CreateFieldWithCaching(name, string.Empty, Field.Store.NO, fieldIndexingOptions, termVector);
144				
145				if (database.TransactionalStorage.IsAlreadyInBatch)
146				{
147					var streamReader = new StreamReader(attachment.Data());
148					fieldWithCaching.SetValue(streamReader);
149				}
150				else
151				{
152					// we are not in batch operation so we have to create it be able to read attachment's data
153					database.TransactionalStorage.Batch(accessor =>
154					{
155						var streamReader = new StreamReader(attachment.Data());
156						// we have to read it into memory because we after exiting the batch an attachment's data stream will be closed
157						fieldWithCaching.SetValue(streamReader.ReadToEnd()); 
158					});
159				}
160
161				yield return fieldWithCaching;
162				yield break;
163			}
164			if (Equals(value, string.Empty))
165			{
166				yield return CreateFieldWithCaching(name, Constants.EmptyString, storage,
167							 Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
168				yield break;
169			}
170			var dynamicNullObject = value as DynamicNullObject;
171			if (ReferenceEquals(dynamicNullObject, null) == false)
172			{
173				if (dynamicNullObject.IsExplicitNull)
174				{
175					var sortOptions = indexDefinition.GetSortOption(name, query: null);
176					if (sortOptions == null ||
177					    sortOptions.Value == SortOptions.String ||
178					    sortOptions.Value == SortOptions.None ||
179					    sortOptions.Value == SortOptions.StringVal ||
180					    sortOptions.Value == SortOptions.Custom)
181
182					{
183					yield return CreateFieldWithCaching(name, Constants.NullValue, storage,
184														Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
185				    }
186
187				    foreach (var field in CreateNumericFieldWithCaching(name, GetNullValueForSorting(sortOptions), storage, termVector))
188                        yield return field;
189                   
190                 }
191				yield break;
192			}
193			var boostedValue = value as BoostedValue;
194			if (boostedValue != null)
195			{
196				foreach (var field in CreateFields(name, boostedValue.Value, storage, false, termVector))
197				{
198					field.Boost = boostedValue.Boost;
199					field.OmitNorms = false;
200					yield return field;
201				}
202				yield break;
203			}
204
205
206			var abstractField = value as AbstractField;
207			if (abstractField != null)
208			{
209				yield return abstractField;
210				yield break;
211			}
212			var bytes = value as byte[];
213			if (bytes != null)
214			{
215				yield return CreateBinaryFieldWithCaching(name, bytes, storage, fieldIndexingOptions, termVector);
216				yield break;
217			}
218
219			var itemsToIndex = value as IEnumerable;
220			if (itemsToIndex != null && ShouldTreatAsEnumerable(itemsToIndex))
221			{
222                int count = 1;
223
224                if (nestedArray == false)
225                    yield return new Field(name + "_IsArray", "true", storage, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
226
227				foreach (var itemToIndex in itemsToIndex)
228				{
229				    if (!CanCreateFieldsForNestedArray(itemToIndex, fieldIndexingOptions))
230				        continue;
231
232				    multipleItemsSameFieldCount.Add(count++);
233				    foreach (var field in CreateFields(name, itemToIndex, storage, nestedArray: true, defaultTermVector: defaultTermVector, analyzed: analyzed))
234				        yield return field;
235
236				    multipleItemsSameFieldCount.RemoveAt(multipleItemsSameFieldCount.Count - 1);
237				}
238
239			    yield break;
240			}
241
242			if (Equals(fieldIndexingOptions, Field.Index.NOT_ANALYZED) ||
243				Equals(fieldIndexingOptions, Field.Index.NOT_ANALYZED_NO_NORMS))// explicitly not analyzed
244			{
245				// date time, time span and date time offset have the same structure fo analyzed and not analyzed.
246				if (!(value is DateTime) && !(value is DateTimeOffset) && !(value is TimeSpan))
247				{
248					yield return CreateFieldWithCaching(name, value.ToString(), storage,
249														indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
250					yield break;
251				}
252			}
253			if (value is string)
254			{
255				var index = indexDefinition.GetIndex(name, Field.Index.ANALYZED);
256				yield return CreateFieldWithCaching(name, value.ToString(), storage, index, termVector);
257				yield break;
258			}
259
260			if (value is TimeSpan)
261			{
262				var val = (TimeSpan)value;
263				yield return CreateFieldWithCaching(name, val.ToString("c", CultureInfo.InvariantCulture), storage,
264						   indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
265			}
266			else if (value is DateTime)
267			{
268				var val = (DateTime)value;
269				var dateAsString = val.ToString(Default.DateTimeFormatsToWrite, CultureInfo.InvariantCulture);
270				if (val.Kind == DateTimeKind.Utc)
271					dateAsString += "Z";
272				yield return CreateFieldWithCaching(name, dateAsString, storage,
273						   indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
274			}
275			else if (value is DateTimeOffset)
276			{
277				var val = (DateTimeOffset)value;
278
279				string dtoStr;
280				if (Equals(fieldIndexingOptions, Field.Index.NOT_ANALYZED) || Equals(fieldIndexingOptions, Field.Index.NOT_ANALYZED_NO_NORMS))
281				{
282					dtoStr = val.ToString(Default.DateTimeOffsetFormatsToWrite, CultureInfo.InvariantCulture);
283				}
284				else
285				{
286					dtoStr = val.UtcDateTime.ToString(Default.DateTimeFormatsToWrite, CultureInfo.InvariantCulture) + "Z";
287				}
288				yield return CreateFieldWithCaching(name, dtoStr, storage,
289						   indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
290			}
291			else if (value is bool)
292			{
293				yield return new Field(name, ((bool)value) ? "true" : "false", storage,
294							  indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
295
296			}
297			else if (value is double)
298			{
299				var d = (double)value;
300				yield return CreateFieldWithCaching(name, d.ToString("r", CultureInfo.InvariantCulture), storage,
301							   indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
302			}
303			else if (value is decimal)
304			{
305				var d = (decimal)value;
306				var s = d.ToString(CultureInfo.InvariantCulture);
307				if (s.Contains('.'))
308				{
309					s = s.TrimEnd('0');
310					if (s.EndsWith("."))
311						s = s.Substring(0, s.Length - 1);
312				}
313				yield return CreateFieldWithCaching(name, s, storage,
314									   indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
315			}
316			else if (value is Enum)
317			{
318				yield return CreateFieldWithCaching(name, value.ToString(), storage,
319									   indexDefinition.GetIndex(name, Field.Index.ANALYZED_NO_NORMS), termVector);
320			}
321			else if (value is IConvertible) // we need this to store numbers in invariant format, so JSON could read them
322			{
323				var convert = ((IConvertible)value);
324				yield return CreateFieldWithCaching(name, convert.ToString(CultureInfo.InvariantCulture), storage,
325									   indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
326			}
327			else if (value is IDynamicJsonObject)
328			{
329				var inner = ((IDynamicJsonObject)value).Inner;
330				yield return CreateFieldWithCaching(name + "_ConvertToJson", "true", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
331				yield return CreateFieldWithCaching(name, inner.ToString(Formatting.None), storage,
332									   indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
333			}
334			else
335			{
336				var jsonVal = RavenJToken.FromObject(value).ToString(Formatting.None);
337				if (jsonVal.StartsWith("{") || jsonVal.StartsWith("["))
338					yield return CreateFieldWithCaching(name + "_ConvertToJson", "true", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
339				else if (jsonVal.StartsWith("\"") && jsonVal.EndsWith("\"") && jsonVal.Length > 1)
340					jsonVal = jsonVal.Substring(1, jsonVal.Length - 2);
341				yield return CreateFieldWithCaching(name, jsonVal, storage,
342									   indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
343			}
344
345
346			foreach (var numericField in CreateNumericFieldWithCaching(name, value, storage, termVector))
347				yield return numericField;
348		}
349
350		private void CheckIfSortOptionsAndInputTypeMatch(string name, object value)
351		{
352			if (log == null)
353				return;
354
355			if (value == null)
356				return;
357
358			var sortOption = indexDefinition.GetSortOption(name, null);
359			if (sortOption.HasValue == false)
360				return;
361
362			switch (sortOption.Value)
363			{
364				case SortOptions.Double:
365				case SortOptions.Float:
366				case SortOptions.Int:
367				case SortOptions.Long:
368				case SortOptions.Short:
369					if (value is int || value is short || value is double || value is long || value is float || value is decimal)
370						return;
371
372					log.Warn(string.Format("Field '{1}' in index '{0}' has numerical sorting enabled, but input value '{2}' was a '{3}'.", indexDefinition.Name, name, value, value.GetType()));
373					break;
374				default:
375					return;
376			}
377		}
378
379	    private static object GetNullValueForSorting(SortOptions? sortOptions)
380	    {
381	        switch (sortOptions)
382	        {
383	            case SortOptions.Short:
384	            case SortOptions.Int:
385	                return int.MinValue;
386	            case SortOptions.Double:
387	                return double.MinValue;
388	            case SortOptions.Float:
389	                return float.MinValue;
390
391// ReSharper disable RedundantCaseLabel
392	            case SortOptions.Long:
393
394	            // to be able to sort on timestamps
395	            case SortOptions.String:
396	            case SortOptions.StringVal:
397	            case SortOptions.None:
398	            case SortOptions.Custom:
399// ReSharper restore RedundantCaseLabel
400	            default:
401	                return long.MinValue;
402	        }
403	    }
404
405	    private IEnumerable<AbstractField> CreateNumericFieldWithCaching(string name, object value,
406			Field.Store defaultStorage, Field.TermVector termVector)
407		{
408
409			var fieldName = name + "_Range";
410			var storage = indexDefinition.GetStorage(name, defaultStorage);
411			var cacheKey = new FieldCacheKey(name, null, storage, termVector, multipleItemsSameFieldCount.ToArray());
412			NumericField numericField;
413			if (numericFieldsCache.TryGetValue(cacheKey, out numericField) == false)
414			{
415				numericFieldsCache[cacheKey] = numericField = new NumericField(fieldName, storage, true);
416			}
417
418			if (value is TimeSpan)
419			{
420				yield return numericField.SetLongValue(((TimeSpan)value).Ticks);
421			}
422			else if (value is int)
423			{
424                if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Long)
425					yield return numericField.SetLongValue((int)value);
426                else if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Float)
427                    yield return numericField.SetFloatValue((int)value);
428                else if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Double)
429					yield return numericField.SetDoubleValue((int)value);
430				else
431					yield return numericField.SetIntValue((int)value);
432			}
433			else if (value is long)
434			{
435				if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Double)
436					yield return numericField.SetDoubleValue((long)value);
437				else if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Float)
438                    yield return numericField.SetFloatValue((long)value);
439				else if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Int)
440                    yield return numericField.SetIntValue(Convert.ToInt32((long)value));
441                else
442					yield return numericField.SetLongValue((long)value);
443			}
444			else if (value is decimal)
445			{
446				if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Float)
447                    yield return numericField.SetFloatValue(Convert.ToSingle((decimal)value));
448				else if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Int)
449                    yield return numericField.SetIntValue(Convert.ToInt32((decimal)value));
450				else if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Long)
451                    yield return numericField.SetLongValue(Convert.ToInt64((decimal) value));
452                else
453                    yield return numericField.SetDoubleValue((double)(decimal)value);
454			}
455			else if (value is float)
456			{
457				if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Double)
458					yield return numericField.SetDoubleValue((float)value);
459				else if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Int)
460                    yield return numericField.SetIntValue(Convert.ToInt32((float)value));
461				else if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Long)
462                    yield return numericField.SetLongValue(Convert.ToInt64((float)value));
463                else
464					yield return numericField.SetFloatValue((float)value);
465			}
466			else if (value is double)
467			{
468				if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Float)
469                    yield return numericField.SetFloatValue(Convert.ToSingle((double)value));
470				else if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Int)
471                    yield return numericField.SetIntValue(Convert.ToInt32((double)value));
472				else if (indexDefinition.GetSortOption(name, query: null) == SortOptions.Long)
473                    yield return numericField.SetLongValue(Convert.ToInt64((double)value));
474                else
475    			    yield return numericField.SetDoubleValue((double)value);
476			}
477		}
478
479		public static bool ShouldTreatAsEnumerable(object itemsToIndex)
480		{
481			if (itemsToIndex == null)
482				return false;
483
484			if (itemsToIndex is DynamicJsonObject)
485				return false;
486
487			if (itemsToIndex is string)
488				return false;
489
490			if (itemsToIndex is RavenJObject)
491				return false;
492
493			if (itemsToIndex is IDictionary)
494				return false;
495
496			return true;
497		}
498
499		private Field CreateBinaryFieldWithCaching(string name, byte[] value, Field.Store store, Field.Index index, Field.TermVector termVector)
500		{
501			if (value.Length > 1024)
502				throw new ArgumentException("Binary values must be smaller than 1Kb");
503
504			var cacheKey = new FieldCacheKey(name, null, store, termVector, multipleItemsSameFieldCount.ToArray());
505			Field field;
506			var stringWriter = new StringWriter();
507			JsonExtensions.CreateDefaultJsonSerializer().Serialize(stringWriter, value);
508			var sb = stringWriter.GetStringBuilder();
509			sb.Remove(0, 1); // remove prefix "
510			sb.Remove(sb.Length - 1, 1); // remove postfix "
511			var val = sb.ToString();
512
513			if (fieldsCache.TryGetValue(cacheKey, out field) == false)
514			{
515				fieldsCache[cacheKey] = field = new Field(name, val, store, index, termVector);
516			}
517			field.SetValue(val);
518			field.Boost = 1;
519			field.OmitNorms = true;
520			return field;
521		}
522
523		public class FieldCacheKey
524		{
525			private readonly string name;
526			private readonly Field.Index? index;
527			private readonly Field.Store store;
528			private readonly Field.TermVector termVector;
529			private readonly int[] multipleItemsSameField;
530
531			public FieldCacheKey(string name, Field.Index? index, Field.Store store, Field.TermVector termVector, int[] multipleItemsSameField)
532			{
533				this.name = name;
534				this.index = index;
535				this.store = store;
536				this.termVector = termVector;
537				this.multipleItemsSameField = multipleItemsSameField;
538			}
539
540
541			protected bool Equals(FieldCacheKey other)
542			{
543				return string.Equals(name, other.name) &&
544					Equals(index, other.index) &&
545					Equals(store, other.store) &&
546					Equals(termVector, other.termVector) &&
547					multipleItemsSameField.SequenceEqual(other.multipleItemsSameField);
548			}
549
550			public override bool Equals(object obj)
551			{
552				if (ReferenceEquals(null, obj)) return false;
553				if (ReferenceEquals(this, obj)) return true;
554				if (obj.GetType() != typeof(FieldCacheKey)) return false;
555				return Equals((FieldCacheKey)obj);
556			}
557
558			public override int GetHashCode()
559			{
560				unchecked
561				{
562					int hashCode = (name != null ? name.GetHashCode() : 0);
563					hashCode = (hashCode * 397) ^ (index != null ? index.GetHashCode() : 0);
564					hashCode = (hashCode * 397) ^ store.GetHashCode();
565					hashCode = (hashCode * 397) ^ termVector.GetHashCode();
566					hashCode = multipleItemsSameField.Aggregate(hashCode, (h, x) => h * 397 ^ x);
567					return hashCode;
568				}
569			}
570		}
571
572		private Field CreateFieldWithCaching(string name, string value, Field.Store store, Field.Index index, Field.TermVector termVector)
573		{
574			var cacheKey = new FieldCacheKey(name, index, store, termVector, multipleItemsSameFieldCount.ToArray());
575			Field field;
576
577			if (fieldsCache.TryGetValue(cacheKey, out field) == false)
578				fieldsCache[cacheKey] = field = new Field(name, value, store, index, termVector);
579
580			field.SetValue(value);
581			field.Boost = 1;
582			field.OmitNorms = true;
583			return field;
584		}
585
586		private bool CanCreateFieldsForNestedArray(object value, Field.Index fieldIndexingOptions)
587		{
588			if (!fieldIndexingOptions.IsAnalyzed())
589			{
590				return true;
591			}
592
593			if (value == null || value is DynamicNullObject)
594			{
595				return false;
596			}
597
598			return true;
599		}
600	}
601}