/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/search/trunk/search-impl/impl/src/java/org/sakaiproject/search/component/service/impl/SearchIndexBuilderImpl.java $ * $Id: SearchIndexBuilderImpl.java 105078 2012-02-24 23:00:38Z ottenhoff@longsight.com $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.sakaiproject.search.component.service.impl; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.event.api.Event; import org.sakaiproject.event.api.Notification; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.exception.TypeException; import org.sakaiproject.search.api.EntityContentProducer; import org.sakaiproject.search.api.SearchIndexBuilder; import org.sakaiproject.search.dao.SearchBuilderItemDao; import org.sakaiproject.search.indexer.api.IndexQueueListener; import org.sakaiproject.search.model.SearchBuilderItem; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.ToolConfiguration; import org.sakaiproject.site.cover.SiteService; /** * Search index builder is expected to be registered in spring as * org.sakaiproject.search.api.SearchIndexBuilder as a singleton. It receives * resources which it adds to its list of pending documents to be indexed. A * Separate thread then runs through the list of entities to be indexed, * updating the index. Each time the index is updates an event is posted to * force the Search components that are using the index to reload. Incremental * updates to the Lucene index require that the searchers reload the index once * the index writer has been built. * * @author ieb */ public class SearchIndexBuilderImpl implements SearchIndexBuilder { private static Log log = LogFactory.getLog(SearchIndexBuilderImpl.class); /** * dependency */ private SearchBuilderItemDao searchBuilderItemDao = null; private List<EntityContentProducer> producers = new ArrayList<EntityContentProducer>(); private boolean onlyIndexSearchToolSites = false; private List<IndexQueueListener> indexQueueListeners = new ArrayList<IndexQueueListener>(); private boolean excludeUserSites = true; public void init() { } /** * register an entity content producer to provide content to the search * engine {@inheritDoc} */ public void registerEntityContentProducer(EntityContentProducer ecp) { log.debug("register " + ecp); producers.add(ecp); } /** * Add a resource to the indexing queue {@inheritDoc} */ public void addResource(Notification notification, Event event) { log.debug("Add resource " + notification + "::" + event); String resourceName = event.getResource(); if (resourceName == null) { // default if null resourceName = ""; } if (resourceName.length() > 255) { log .warn("Entity Reference is longer than 255 characters, not indexing. Reference=" + resourceName); return; } EntityContentProducer ecp = newEntityContentProducer(event); if ( ecp == null || ecp.getSiteId(resourceName) == null) { log.debug("Not indexing " + resourceName + " as it has no context"); return; } if (onlyIndexSearchToolSites) { try { String siteId = ecp.getSiteId(resourceName); Site s = SiteService.getSite(siteId); ToolConfiguration t = s.getToolForCommonId("sakai.search"); if (t == null) { log.debug("Not indexing " + resourceName + " as it has no search tool"); return; } } catch (Exception ex) { log.debug("Not indexing " + resourceName + " as it has no site", ex); return; } } Integer action = ecp.getAction(event); try { SearchBuilderItem sb = searchBuilderItemDao.findByName(resourceName); if (sb == null) { // new sb = searchBuilderItemDao.create(); sb.setSearchaction(action); sb.setName(resourceName); String siteId = ecp.getSiteId(resourceName); if (siteId == null || siteId.length() == 0) { // default if null should neve happen siteId = "none"; } sb.setContext(siteId); sb.setSearchstate(SearchBuilderItem.STATE_PENDING); sb.setItemscope(SearchBuilderItem.ITEM); } else { sb.setSearchaction(action); String siteId = ecp.getSiteId(resourceName); if (siteId == null || siteId.length() == 0) { // default if null, should never happen siteId = "none"; } sb.setContext(siteId); sb.setName(resourceName); sb.setSearchstate(SearchBuilderItem.STATE_PENDING); sb.setItemscope(SearchBuilderItem.ITEM); } searchBuilderItemDao.update(sb); log.debug("SEARCHBUILDER: Added Resource " + action + " " + sb.getName()); fireResourceAdded(resourceName); } catch (Throwable t) { log.debug("In trying to register resource " + resourceName + " in search engine this resource will" + " not be indexed untill it is modified"); } } protected void fireResourceAdded(String name) { for (Iterator<IndexQueueListener> itl = indexQueueListeners.iterator(); itl .hasNext();) { IndexQueueListener tl = itl.next(); tl.added(name); } } public void addIndexQueueListener(IndexQueueListener indexQueueListener) { List<IndexQueueListener> tl = new ArrayList<IndexQueueListener>(); tl.addAll(indexQueueListeners); tl.add(indexQueueListener); indexQueueListeners = tl; } public void removeIndexQueueListener(IndexQueueListener indexQueueListener) { List<IndexQueueListener> tl = new ArrayList<IndexQueueListener>(); tl.addAll(indexQueueListeners); tl.remove(indexQueueListener); indexQueueListeners = tl; } /** * refresh the index from the current stored state {@inheritDoc} */ public void refreshIndex() { SearchBuilderItem sb = searchBuilderItemDao .findByName(SearchBuilderItem.GLOBAL_MASTER); if (sb == null) { log.debug("Created NEW " + SearchBuilderItem.GLOBAL_MASTER); sb = searchBuilderItemDao.create(); } if ((SearchBuilderItem.STATE_COMPLETED.equals(sb.getSearchstate())) || (!SearchBuilderItem.ACTION_REBUILD.equals(sb.getSearchaction()) && !SearchBuilderItem.STATE_PENDING.equals(sb.getSearchstate()) && !SearchBuilderItem.STATE_PENDING_2 .equals(sb.getSearchstate()))) { sb.setSearchaction(SearchBuilderItem.ACTION_REFRESH); sb.setName(SearchBuilderItem.GLOBAL_MASTER); sb.setContext(SearchBuilderItem.GLOBAL_CONTEXT); sb.setSearchstate(SearchBuilderItem.STATE_PENDING); sb.setItemscope(SearchBuilderItem.ITEM_GLOBAL_MASTER); searchBuilderItemDao.update(sb); log.debug("SEARCHBUILDER: REFRESH ALL " + sb.getSearchaction() + " " + sb.getName()); fireResourceAdded(String.valueOf(SearchBuilderItem.GLOBAL_MASTER)); } else { log.debug("SEARCHBUILDER: REFRESH ALL IN PROGRESS " + sb.getSearchaction() + " " + sb.getName()); } } public void destroy() { } /* * List l = searchBuilderItemDao.getAll(); for (Iterator i = l.iterator(); * i.hasNext();) { SearchBuilderItemImpl sbi = (SearchBuilderItemImpl) * i.next(); sbi.setSearchstate(SearchBuilderItem.STATE_PENDING); * sbi.setSearchaction(SearchBuilderItem.ACTION_ADD); try { log.info(" * Updating " + sbi.getName()); searchBuilderItemDao.update(sbi); } catch * (Exception ex) { try { sbi = (SearchBuilderItemImpl) searchBuilderItemDao * .findByName(sbi.getName()); if (sbi != null) { * sbi.setSearchstate(SearchBuilderItem.STATE_PENDING); * sbi.setSearchaction(SearchBuilderItem.ACTION_ADD); * searchBuilderItemDao.update(sbi); } } catch (Exception e) { * log.warn("Failed to update on second attempt " + e.getMessage()); } } } * restartBuilder(); } */ /** * Rebuild the index from the entities own stored state {@inheritDoc} */ public void rebuildIndex() { try { SearchBuilderItem sb = searchBuilderItemDao .findByName(SearchBuilderItem.GLOBAL_MASTER); if (sb == null) { sb = searchBuilderItemDao.create(); } sb.setSearchaction(SearchBuilderItem.ACTION_REBUILD); sb.setName(SearchBuilderItem.GLOBAL_MASTER); sb.setContext(SearchBuilderItem.GLOBAL_CONTEXT); sb.setSearchstate(SearchBuilderItem.STATE_PENDING); sb.setItemscope(SearchBuilderItem.ITEM_GLOBAL_MASTER); searchBuilderItemDao.update(sb); log.debug("SEARCHBUILDER: REBUILD ALL " + sb.getSearchaction() + " " + sb.getName()); fireResourceAdded(String.valueOf(SearchBuilderItem.GLOBAL_MASTER)); } catch (Exception ex) { log.warn(" rebuild index encountered a problme " + ex.getMessage()); } } /* * for (Iterator i = producers.iterator(); i.hasNext();) { * EntityContentProducer ecp = (EntityContentProducer) i.next(); List * contentList = ecp.getAllContent(); for (Iterator ci = * contentList.iterator(); ci.hasNext();) { String resourceName = (String) * ci.next(); } } } */ /** * Generates a SearchableEntityProducer * * @param ref * @return * @throws PermissionException * @throws IdUnusedException * @throws TypeException */ public EntityContentProducer newEntityContentProducer(String ref) { log.debug(" new entitycontent producer"); for (Iterator<EntityContentProducer> i = producers.iterator(); i.hasNext();) { EntityContentProducer ecp = (EntityContentProducer) i.next(); if (ecp.matches(ref)) { return ecp; } } return null; } /** * get hold of an entity content producer using the event * * @param event * @return */ public EntityContentProducer newEntityContentProducer(Event event) { log.debug(" new entitycontent producer"); for (Iterator<EntityContentProducer> i = producers.iterator(); i.hasNext();) { EntityContentProducer ecp = (EntityContentProducer) i.next(); if (ecp.matches(event)) { log.debug(" Matched Entity Content Producer for event " + event + " with " + ecp); return ecp; } else { log.debug("Skipped ECP " + ecp); } } log.debug("Failed to match any Entity Content Producer for event " + event); return null; } /** * @return Returns the searchBuilderItemDao. */ public SearchBuilderItemDao getSearchBuilderItemDao() { return searchBuilderItemDao; } /** * @param searchBuilderItemDao * The searchBuilderItemDao to set. */ public void setSearchBuilderItemDao(SearchBuilderItemDao searchBuilderItemDao) { this.searchBuilderItemDao = searchBuilderItemDao; } /** * return true if the queue is empty * * @{inheritDoc} */ public boolean isBuildQueueEmpty() { int n = searchBuilderItemDao.countPending(); log.debug("Queue has " + n); return (n == 0); } /** * get all the producers registerd, as a clone to avoid concurrent * modification exceptions * * @return */ public List<EntityContentProducer> getContentProducers() { return new ArrayList<EntityContentProducer>(producers); } public int getPendingDocuments() { return searchBuilderItemDao.countPending(); } /** * Rebuild the index from the entities own stored state {@inheritDoc}, just * the supplied siteId */ public void rebuildIndex(String currentSiteId) { try { if (currentSiteId == null || currentSiteId.length() == 0) { currentSiteId = "none"; } String siteMaster = MessageFormat.format( SearchBuilderItem.SITE_MASTER_FORMAT, new Object[] { currentSiteId }); SearchBuilderItem sb = searchBuilderItemDao.findByName(siteMaster); if (sb == null) { sb = searchBuilderItemDao.create(); } sb.setSearchaction(SearchBuilderItem.ACTION_REBUILD); sb.setName(siteMaster); sb.setContext(currentSiteId); sb.setSearchstate(SearchBuilderItem.STATE_PENDING); sb.setItemscope(SearchBuilderItem.ITEM_SITE_MASTER); searchBuilderItemDao.update(sb); log.debug("SEARCHBUILDER: REBUILD CONTEXT " + sb.getSearchaction() + " " + sb.getName()); fireResourceAdded(sb.getName()); } catch (Exception ex) { log.warn(" rebuild index encountered a problme " + ex.getMessage()); } } /** * Refresh the index fo the supplied site. */ public void refreshIndex(String currentSiteId) { if (currentSiteId == null || currentSiteId.length() == 0) { currentSiteId = "none"; } String siteMaster = MessageFormat.format(SearchBuilderItem.SITE_MASTER_FORMAT, new Object[] { currentSiteId }); SearchBuilderItem sb = searchBuilderItemDao.findByName(siteMaster); if (sb == null) { log.debug("Created NEW " + siteMaster); sb = searchBuilderItemDao.create(); sb.setContext(currentSiteId); sb.setName(siteMaster); sb.setSearchstate(SearchBuilderItem.STATE_COMPLETED); sb.setSearchaction(SearchBuilderItem.ACTION_REFRESH); sb.setItemscope(SearchBuilderItem.ITEM_SITE_MASTER); } if ((SearchBuilderItem.STATE_COMPLETED.equals(sb.getSearchstate())) || (!SearchBuilderItem.ACTION_REBUILD.equals(sb.getSearchaction()) && !SearchBuilderItem.STATE_PENDING.equals(sb.getSearchstate()) && !SearchBuilderItem.STATE_PENDING_2 .equals(sb.getSearchstate()))) { sb.setSearchaction(SearchBuilderItem.ACTION_REFRESH); sb.setName(siteMaster); sb.setContext(currentSiteId); sb.setSearchstate(SearchBuilderItem.STATE_PENDING); sb.setItemscope(SearchBuilderItem.ITEM_SITE_MASTER); searchBuilderItemDao.update(sb); log.debug("SEARCHBUILDER: REFRESH CONTEXT " + sb.getSearchaction() + " " + sb.getName()); fireResourceAdded(sb.getName()); } else { log.debug("SEARCHBUILDER: REFRESH CONTEXT IN PROGRESS " + sb.getSearchaction() + " " + sb.getName()); } } public List<SearchBuilderItem> getAllSearchItems() { return searchBuilderItemDao.getAll(); } public List<SearchBuilderItem> getGlobalMasterSearchItems() { return searchBuilderItemDao.getGlobalMasters(); } public List<SearchBuilderItem> getSiteMasterSearchItems() { return searchBuilderItemDao.getSiteMasters(); } /** * @return the onlyIndexSearchToolSites */ public boolean isOnlyIndexSearchToolSites() { return onlyIndexSearchToolSites; } /** * @param onlyIndexSearchToolSites * the onlyIndexSearchToolSites to set */ public void setOnlyIndexSearchToolSites(boolean onlyIndexSearchToolSites) { this.onlyIndexSearchToolSites = onlyIndexSearchToolSites; } public void setExcludeUserSites(boolean excludeUserSites) { this.excludeUserSites = excludeUserSites; } public boolean isExcludeUserSites() { // TODO Auto-generated method stub return excludeUserSites; } }