/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.core.search; import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.skalli.commons.ThreadPool; import org.eclipse.skalli.model.Project; import org.eclipse.skalli.model.Taggable; import org.eclipse.skalli.services.entity.EventEntityUpdate; import org.eclipse.skalli.services.event.EventListener; import org.eclipse.skalli.services.event.EventService; import org.eclipse.skalli.services.extension.ExtensionService; import org.eclipse.skalli.services.extension.ExtensionServices; import org.eclipse.skalli.services.extension.Indexer; import org.eclipse.skalli.services.project.ProjectService; import org.eclipse.skalli.services.search.PagingInfo; import org.eclipse.skalli.services.search.QueryParseException; import org.eclipse.skalli.services.search.SearchHit; import org.eclipse.skalli.services.search.SearchResult; import org.eclipse.skalli.services.search.SearchService; import org.osgi.service.component.ComponentConstants; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LuceneSearchComponent implements SearchService, EventListener<EventEntityUpdate> { private static final Logger LOG = LoggerFactory.getLogger(LuceneSearchComponent.class); private LuceneIndex<Project> luceneIndex; protected void activate(ComponentContext context) { LOG.info(MessageFormat.format("[SearchService][Lucene] {0} : activated", (String) context.getProperties().get(ComponentConstants.COMPONENT_NAME))); } protected void deactivate(ComponentContext context) { LOG.info(MessageFormat.format("[SearchService][Lucene] {0} : deactivated", (String) context.getProperties().get(ComponentConstants.COMPONENT_NAME))); } protected void bindProjectService(ProjectService projectService) { LOG.info(MessageFormat.format("bindProjectService({0})", projectService)); //$NON-NLS-1$ luceneIndex = new LuceneIndex<Project>(projectService); // perform re-indexing asynchronously: do not block service binding! ThreadPool.submit(new Runnable() { @Override public void run() { reindexAll(); } }); } protected void bindEventService(EventService eventService) { LOG.info(MessageFormat.format("bindEventService({0})", eventService)); //$NON-NLS-1$ eventService.registerListener(EventEntityUpdate.class, this); } protected void unbindEventService(EventService eventService) { LOG.info(MessageFormat.format("unbindEventService({0})", eventService)); //$NON-NLS-1$ } protected void unbindProjectService(ProjectService projectService) { LOG.info(MessageFormat.format("unbindProjectService({0})", projectService)); //$NON-NLS-1$ luceneIndex = null; } @Override public void reindex(Collection<Project> projects) { if (projects == null || projects.isEmpty()) { return; } try { long time = System.currentTimeMillis(); LOG.info(MessageFormat.format("Re-indexing {0} projects...", projects.size())); luceneIndex.reindex(projects); LOG.info(MessageFormat.format("Re-indexing {0} projects finished in {1} milliseconds", projects.size(), Long.toString(System.currentTimeMillis() - time))); } catch (RuntimeException e) { LOG.warn("Failed to re-index projects", e); } } @Override public void reindexAll() { try { long time = System.currentTimeMillis(); LOG.info("Re-indexing all projects..."); luceneIndex.reindexAll(); LOG.info(MessageFormat.format("Re-indexing all projects finished in {0} milliseconds", Long.toString(System.currentTimeMillis() - time))); } catch (RuntimeException e) { LOG.warn("Failed to re-index all projects", e); } } @Override public void update(Project project) { update(Collections.singleton(project)); } @Override public void update(Collection<Project> projects) { luceneIndex.update(projects); } @Override public SearchResult<Project> findProjectsByQuery(String queryString, PagingInfo pagingInfo) throws QueryParseException { Set<String> fieldSet = new HashSet<String>(); for (ExtensionService<?> ext : ExtensionServices.getAll()) { Indexer<?> indexer = ext.getIndexer(); if (indexer != null) { Set<String> fields = indexer.getDefaultSearchFields(); if (fields != null) { fieldSet.addAll(fields); } } } return luceneIndex.search(fieldSet.toArray(new String[fieldSet.size()]), queryString, pagingInfo); } @Override public SearchResult<Project> findProjectsByUser(String userId, PagingInfo pagingInfo) throws QueryParseException { String[] fields = new String[] { "allMembers" }; //$NON-NLS-1$ return luceneIndex.searchPhrase(fields, userId, pagingInfo); } @Override public SearchResult<Project> findProjectsByTag(String tag, PagingInfo pagingInfo) throws QueryParseException { String[] fields = new String[] { Taggable.PROPERTY_TAGS }; return luceneIndex.searchPhrase(fields, tag, pagingInfo); } @Override public SearchResult<Project> getRelatedProjects(Project project, int count) { String[] fields = new String[] { Project.PROPERTY_NAME, Project.PROPERTY_DESCRIPTION, Taggable.PROPERTY_TAGS }; return luceneIndex.moreLikeThis(project, fields, count); } @Override public List<SearchHit<Project>> asSearchHits(Collection<Project> projects) { return luceneIndex.entitiesToHit(projects); } @Override public void onEvent(EventEntityUpdate event) { if (event.getEntityClass().equals(Project.class)) { update((Project)event.getEntity()); } } }