All pastes #79030 Raw Edit

AqD

public text v1 · immutable
·published 2006-07-04 20:27 UTC
rendered paste body
using AqDWeb.Attributes;
using AqDWeb.Exceptions;
using AqDWeb.Services;
using Lucene.Net.Documents;
using NHibernate.Type;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using YaDiary.Base;

namespace YaDiary.Domain
{
  /// <summary>A generic service for <see cref="IndexedArticle"/>. It hooks
  /// into the NHibernate interceptor to update DotLucene indexes incrementally.
  /// </summary>
  [GenericService(typeof(IndexedArticle))]
  public class IndexedArticleService<T>: VersionedEntityService<T>
  where T: IndexedArticle
  {
    public delegate string Translate(string text);

    public override bool OnSave(object entity, object id,
                                IDictionary<string, object> namedState,
                                IDictionary<string, IType> namedTypes)
    {
      T e = entity as T;
      // Skip historical objects.
      if (e.EntityTimestamp == DateTime.MinValue
          && IsSearchableEntity(namedState))
      {
        // Set EntityID manually here because it's going to be used when saving
        // index. The change will be overrided when NHibernate refreshs the
        // entity from state (base.OnSave sets the same EntityID in state).
        e.EntityID = (Guid) id;
        SaveIndexes(new IndexedArticle[]{e});
      }

      return base.OnSave(entity, id, namedState, namedTypes);
    }

    public override bool OnFlushDirty(object entity, object id,
                                      IDictionary<string, object> namedCurrentState,
                                      IDictionary<string, object> namedPreviousState,
                                      IDictionary<string, IType> namedTypes)
    {
      T e = entity as T;
      bool currentlySearchable
        = IsSearchableEntity(namedCurrentState);
      bool previouslySearchable
        = IsSearchableEntity(namedPreviousState);

      if (currentlySearchable && previouslySearchable && e.IsEntityDeleted)
        DeleteIndexes(new IndexedArticle[]{e});
      else if (currentlySearchable && !previouslySearchable)
        SaveIndexes(new IndexedArticle[]{e});
      else if (currentlySearchable)
        UpdateIndexes(new IndexedArticle[]{e});
      else if (!currentlySearchable && previouslySearchable)
        DeleteIndexes(new IndexedArticle[]{e});

      return base.OnFlushDirty(entity, id, namedCurrentState,
                               namedPreviousState, namedTypes);
    }

    public virtual void ClearAllIndexes()
    {
      YaDiarySearchModule.Instance.ClearIndexes();
    }

    public virtual void OptimizeAllIndexes()
    {
      YaDiarySearchModule.Instance.OptimizeIndexes();
    }

    public virtual void SaveIndexes(IList<IndexedArticle> entities)
    {
      UpdateIndexes(entities, true);
    }

    public virtual void UpdateIndexes(IList<IndexedArticle> entities)
    {
      UpdateIndexes(entities, false);
    }

    public virtual void UpdateIndexes(IList<IndexedArticle> entities, bool saveOnly)
    {
      YaDiarySearchModule.Instance.BatchUpdateIndexes(
        entities,
        delegate(IndexedArticle entity, YaDiarySearchModule.CreateDocument createDocument)
        {
          Document zhTwDoc = createDocument();
          zhTwDoc.Add(Field.Keyword("language", "zh-TW"));
          SetupIndexDocument(
            entity, zhTwDoc,
            delegate (string text)
            {
              return text;
            }
          );

          Document zhCnDoc = createDocument();
          zhCnDoc.Add(Field.Keyword("language", "zh-CN"));
          ChineseConversionModule translator
            = ChineseConversionModule.Instance;

          SetupIndexDocument(
            entity, zhCnDoc,
            delegate (string text)
            {
              return translator.TranslateTraditionalToSimple(text);
            }
          );

          return new Document[] {zhTwDoc, zhCnDoc};
        },
        saveOnly
      );
    }

    public virtual void DeleteIndexes(IList<IndexedArticle> entities)
    {
      YaDiarySearchModule.Instance.BatchDeleteIndexes(entities);
    }

    protected virtual void SetupIndexDocument(IndexedArticle entity,
                                              Document doc,
                                              Translate translate)
    {
      char splitChar = (entity.Keywords.Contains(",") ? ',': ' ');
      foreach (string keyword in entity.Keywords.Split(splitChar))
      {
        Field kf = Field.Text("keyword", translate(keyword.Trim()));
        kf.SetBoost(3.0f); doc.Add(kf);
      }

      Field sf = Field.Text("subject", translate(entity.Subject));
      sf.SetBoost(2.0f); doc.Add(sf);

      Field af = Field.Text("author", translate(entity.Author.Name));
      af.SetBoost(1.5f); doc.Add(af);

      doc.Add(Field.Keyword("postdate", entity.PostDate));

      doc.Add(
        Field.Text(
          "text",
          new StringReader(translate(ExtractHtmlBodyText(entity.Text)))
        )
      );

      if (entity is DiscussionAnswer)
      {
        doc.Add(
          Field.Keyword(
            "threadID",
            (entity as DiscussionAnswer).Question.ID.ToString())
        );
      }
    }

    protected virtual string ExtractHtmlBodyText(string html)
    {
      // Strip all tags and convert back html entity chars, this should be
      // sufficient for simple HTML body without javascript
      return HttpUtility.HtmlDecode(Regex.Replace(html, "<[^>]*>", ""));
    }

    protected virtual bool IsSearchableEntity(IDictionary<string, object> namedState)

    {
      if (typeof(Discussion).IsAssignableFrom(typeof(T)))
      {
        try
        {
          return (bool) namedState["IsPublic"];
        }
        catch (KeyNotFoundException ex)
        {
          throw new InvalidUserCodeException(
            typeof(T).FullName + " contains no \"IsPublic\" property?!", ex);
        }
      }
      else
        return true;
    }
  }
}