/******************************************************************************* * Copyright (c) 2006-2012 * Software Technology Group, Dresden University of Technology * DevBoost GmbH, Berlin, Amtsgericht Charlottenburg, HRB 140026 * * 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: * Software Technology Group - TU Dresden, Germany; * DevBoost GmbH - Berlin, Germany * - initial API and implementation ******************************************************************************/ package org.reuseware.sokan.index.util; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.ResourceSet; import org.reuseware.sokan.FacetedRequest; import org.reuseware.sokan.FacetedResponse; import org.reuseware.sokan.ID; import org.reuseware.sokan.IndexRow; import org.reuseware.sokan.index.CommitCache; import org.reuseware.sokan.index.IndexCache; import org.reuseware.sokan.index.SokanIndexPlugin; import org.reuseware.sokan.index.emodeler.EModelerManager; import org.reuseware.sokan.index.indexer.IndexerConfiguration; import org.reuseware.sokan.index.indexer.IndexerManager; import org.reuseware.sokan.index.notify.IndexListener; import org.reuseware.sokan.index.notify.IndexNotifier; import org.reuseware.sokan.index.persister.PersistencyManager; /** * This class provides a number of methods to manipulate and query the index * maintained by Sokan. <br> * <br> * <b>Important Note:</b><br> * If you implement your own {@link Indexer} make sure, you don't run into an * infinite loop by invoking methods like {@link #getIndex()} or * {@link #getFacetedResponse(FacetedRequest)}. <br> * See {@link Indexer} for more information on this topic. * */ public final class IndexUtil { /** * The singleton instance. */ public static final IndexUtil INSTANCE = new IndexUtil(); /** * Problem marker ID for problem markers produced directly by Sokan. */ public static final String ID_PROBLEM_MARKER = SokanIndexPlugin.PLUGIN_ID + ".idProblem"; private IndexerManager indexerManager; private PersistencyManager persistencyManager; private IndexNotifier notifier; private CommitCache comCache; private IndexCache indexCache; private IndexUtil() { persistencyManager = new PersistencyManager(); indexerManager = new IndexerManager(persistencyManager); notifier = new IndexNotifier(); comCache = new CommitCache(); indexCache = new IndexCache(); } /** * Registers the artifact with the given URI in the index. * * @param uri the URI * @param instantUpdate true if the artifact should be instantly indexed */ public void add(URI uri, boolean instantUpdate) { if (uri == null) { return; } if (isArtifact(uri)) { if (isArtifact(uri) && !uri.equals(ResourceUtil.uriFrom(ResourceUtil.idFrom(uri)))) { return; } } if (!StoreUtil.INSTANCE.isInStore(uri)) { return; } if (instantUpdate) { CommitCache cache = new CommitCache(); cache.getNewResources().add(uri); commitIndex(cache, new NullProgressMonitor()); } else { comCache.getNewResources().add(uri); } } /** * Registers the artifact with the given URI in the index. * * @param uri the URI */ public void addArtifact(URI uri) { add(uri, false); } /** * Registers the artifact represented by the given Eclipse resource in the index. * * @param resource the Eclipse resource */ public void addArtifact(IResource resource) { try { resource.deleteMarkers(ID_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); } catch (CoreException e) { SokanIndexPlugin.logError("", e); } if (isArtifact(resource) && !ResourceUtil.uriFrom(resource).equals(ResourceUtil.uriFrom(ResourceUtil.idFrom(resource)))) { reportIDAlreadyTakenError(resource); return; } add(ResourceUtil.uriFrom(resource), false); } /** * Retrieves <code>true</code> if each of the given <code>ID</code>s * represent an artifact in the index. * * @param resourceIDs * A list of artifact identifiers build from resources. * @return <code>true</code> if all resourceIDs are artifactIDs. */ public boolean areArtifactsID(List<ID> resourceIDs) { if (resourceIDs == null) { return false; } return persistencyManager.areArtifact(resourceIDs); } /** * Retrieves <code>true</code> if each of the given <code>IResource</code>s * is located in a Store and indexed. * * @param resources * A list of resources. * @return <code>true</code> if all resources are in a Store and indexed. */ public boolean areArtifactsRes(List<? extends IResource> resources) { if (resources == null || resources.isEmpty()) { return false; } List<ID> resIDs = new ArrayList<ID>(resources.size()); for (IResource res : resources) { if (!StoreUtil.INSTANCE.isInStore(res)) { return false; } resIDs.add(ResourceUtil.idFrom(res)); } return areArtifactsID(resIDs); } /** * Retrieves the complete index. * * @return A list of all index rows. */ public List<IndexRow> getIndex() { List<IndexRow> rows; String lastCommit = indexerManager.getTimeStamp(); rows = indexCache.getAll(lastCommit); if (rows == null) { rows = persistencyManager.query(); indexCache.putAll(lastCommit, rows); } return rows; } /** * Retrieves the part of the index that fulfills the {@link FacetedRequest}. * * @param request * A faceted request to filter the index. * @return A list of all clean index rows that are filtered by the faceted * request. */ public List<IndexRow> getIndex(FacetedRequest request) { if (request == null) { new LinkedList<IndexRow>(); } FacetedResponse response = getFacetedResponse(request); if (response == null) { return new LinkedList<IndexRow>(); } return response.getContent(); } /** * Retrieves the clean index row of this artifact. * * @param artifactID * The artifact's identifier. * @return The artifact's clean index row. * @queryIndex */ public IndexRow getIndex(ID artifactID) { IndexRow row; String lastCommit = indexerManager.getTimeStamp(); row = indexCache.getIndex(lastCommit, artifactID); if (row == null) { row = persistencyManager.query(artifactID); indexCache.putIndex(lastCommit, artifactID, row); } return row; } /** * Retrieves the clean index rows of the given artifacts. * * @param artifactIDs * The artifacts' IDs. * @return The artifact's clean index row. * @queryIndex */ public List<IndexRow> getIndex(Collection<ID> artifactIDs) { List<IndexRow> rows; String lastCommit = indexerManager.getTimeStamp(); rows = indexCache.getIndex(lastCommit, artifactIDs); if (rows == null) { rows = persistencyManager.query(artifactIDs); indexCache.putIndex(lastCommit, artifactIDs, rows); } return rows; } /** * Check if indexing is currently in progress. * * @return <code>true</code>if indexing is currently in progress. */ public boolean isIndexing() { return indexerManager.isIndexing(); } /** * Retrieves a list of <code>EObject</code>s of the given * <code>EClass</code> or <code>null</code> if no adequate {@link EModeler} * is registered. * * @see EModeler#createMetaDataModel(IndexRow) * * @param artifactID * The artifact's identifier. * @param eClass * The <code>EClass</code> <code>EObject</code>s are needed for. * @param resourceSet the resource set to use * @return the created model */ public List<EObject> getEModel(ID artifactID, EClass eClass, ResourceSet resourceSet) { IndexRow row = getIndex(artifactID); if (row == null) { return null; } return EModelerManager.createModel(eClass, row, resourceSet); } /** * Retrieves a list of <code>EObject</code>s of the given * <code>EClass</code> or <code>null</code> if no adequate {@link EModeler} * is registered. * * @see EModeler#createMetaDataModel(IndexRow) * * @param row * The index row containing data to create the new * <code>EObjects</code>. * @param eClass * The <code>EClass</code> <code>EObject</code>s are needed for. * @param resourceSet the resource set to use * @return the created model */ public List<EObject> getEModel(IndexRow row, EClass eClass, ResourceSet resourceSet) { if (row == null || eClass == null) { return null; } return EModelerManager.createModel(eClass, row, resourceSet); } /** * Retrieves a clean {@link FacetedResponse} as a result of a * {@link FacetedRequest}. This response contains the request along with * other information needed by faceted browsers. * * @param request * A faceted request to filter the index. * @return A faceted response. If clean is <code>true</code> the response is * up-to-date, else some of the index rows might be out-of-date * making the whole response not representing the current state. */ public FacetedResponse getFacetedResponse(FacetedRequest request) { if (request == null) { return null; } FacetedResponse rsp; String lastCommit = indexerManager.getTimeStamp(); rsp = indexCache.getResponse(lastCommit, request); if (rsp == null) { rsp = persistencyManager.query(request); indexCache.putResponse(lastCommit, request, rsp); } return rsp; } /** * Removes the artifact with the given URI from the index. * * @param uri the URI * @param instantUpdate true if the artifact should be instantly removed */ public void remove(URI uri, boolean instantUpdate) { if (uri == null) { return; } // was this artifact indexed and not another one with the expected id if (!uri.equals(ResourceUtil.uriFrom(ResourceUtil.idFrom(uri)))) { return; } if (instantUpdate) { CommitCache cache = new CommitCache(); cache.getDeletedResources().add(uri); commitIndex(cache, new NullProgressMonitor()); } else { comCache.getDeletedResources().add(uri); } } /** * Removes the artifact with the given URI from the index. * * @param uri the URI */ public void removeArtifact(URI uri) { remove(uri, false); } /** * Removes the artifact represented by the given Eclipse resource from the index. * * @param resource the Eclipse resource */ public void removeArtifact(IResource resource) { remove(ResourceUtil.uriFrom(resource), false); } /** * Removes the artifact represented by the given index row from the index. * * @param row the index row */ public void removeArtifact(IndexRow row) { if (row == null) { return; } URI uri = ResourceUtil.uriFrom(row.getPhyURI()); if (uri == null) { return; } comCache.getDeletedResources().add(uri); } /** * Shutdown the index system. */ public void shutdownIndexServer() { persistencyManager.shutdownServer(); } /** * Updates the artifact with the given URI in the index. * * @param uri the URI * @param instantUpdate true if the artifact should be instantly re-indexed */ public void update(URI uri, boolean instantUpdate) { if (uri == null) { return; } if (instantUpdate) { CommitCache cache = new CommitCache(); cache.getUpdatedResources().add(uri); commitIndex(cache, new NullProgressMonitor()); } else { comCache.getUpdatedResources().add(uri); } } /** * Updates the artifact with the given URI in the index. * * @param uri the URI */ public void updateArtifact(URI uri) { update(uri, false); } /** * Updates the artifact represented by the given Eclipse resource in the index. * * @param resource the Eclipse resource */ public void updateArtifact(IResource resource) { if (IndexUtil.INSTANCE.isGenerated(resource)) { return; } update(ResourceUtil.uriFrom(resource), false); } /** * Updates the index of the artifact with the given ID using the given indexers. * * @param id the artifact's ID * @param indexerIDs the IDs of the idexers to call */ public void updateArtifact(ID id, List<String> indexerIDs) { comCache.getUpdateMap().put(id, indexerManager.getIndexers(indexerIDs, true)); } /** * Commit the complete index. */ public void commitIndex() { if (commitIndex(comCache, new NullProgressMonitor())) { comCache = new CommitCache(); } } /** * Commit the complete index. * * @param monitor a monitor to record the indexing progress */ public void commitIndex(IProgressMonitor monitor) { if (commitIndex(comCache, monitor)) { comCache = new CommitCache(); } } /** * Retrieves the current number of rows in the index. * * @return The index's size. */ public int getSize() { return persistencyManager.getIndexSize(); } /** * Retrieves <code>true</code> if the given <code>ID</code> represents an * artifact in the index. * * @param resourceID * An artifact identifier build from a resource. * @return <code>true</code> if the resourceID is an artifactID. */ public boolean isArtifact(ID resourceID) { if (resourceID == null) { return false; } Boolean isArt = null; String lastCommit = indexerManager.getTimeStamp(); isArt = indexCache.isArtifact(lastCommit, resourceID); if (isArt == null) { isArt = persistencyManager.isArtifact(resourceID); indexCache.putIsArtifact(lastCommit, resourceID, isArt); } return isArt; } /** * Retrieves <code>true</code> if the given <code>IResource</code> represents an * artifact in the index. * * @param resource the Eclipse resource * @return <code>true</code> if the resource is an artifact */ public boolean isArtifact(IResource resource) { if (resource == null) { return false; } if (!StoreUtil.INSTANCE.isInStore(resource)) { return false; } ID resID = ResourceUtil.idFrom(resource); return isArtifact(resID); } /** * @param uri an URI * @return true if the artifact with the given URI is indexed */ public boolean isArtifact(URI uri) { if (uri == null) { return false; } ID resID = ResourceUtil.idFrom(uri); return isArtifact(resID); } /** * @param resource an Eclipse resource * @return true if the resource was generated by Sokan */ public boolean isGenerated(IResource resource) { if (resource == null || !(resource instanceof IFile)) { return false; } if (!StoreUtil.INSTANCE.isInStore(resource)) { return false; } ID resID = ResourceUtil.idFrom(resource); if (resID == null) { return false; } IndexRow row = getIndex(resID); if (row == null) { return false; } return row.isGenerated(); } /** * Commit the given index cache. * * @param cache the cache * @param monitor a monitor to record the commit's progress * @return true if successful */ public boolean commitIndex(CommitCache cache, IProgressMonitor monitor) { Set<ID> delta = indexerManager.performCommit(cache, monitor); if (delta == null) { return false; } notifier.notifyListeners(delta); return true; } /** * Registers the given index listener. * * @param listener the index listener */ public void addListener(IndexListener listener) { notifier.add(listener); } /** * Unregisters the given index listener. * * @param listener the index listener */ public void removeListener(IndexListener listener) { notifier.remove(listener); } /** * Report an error that the ID that would be assigned to the artifact * represented by the given Eclipse resource is already in use. * * @param resource the resource */ public void reportIDAlreadyTakenError(IResource resource) { ID id = ResourceUtil.idFrom(resource); URI indexedArtifactURI = ResourceUtil.uriFrom(id); String msg = "Cannot index: ID " + id.getSegments() + " already taken by artifact '" + indexedArtifactURI + "'"; try { IMarker marker = resource.createMarker(ID_PROBLEM_MARKER); marker.setAttribute(IMarker.MESSAGE, msg); marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); } catch (CoreException e) { SokanIndexPlugin.logError(resource + ": " + msg, e); } } /** * Report an error that the ID that would be assigned to the artifact * with the given URI is already in use. * * @param uri the artifact's URI */ public void reportIDAlreadyTakenError(URI uri) { ID id = ResourceUtil.idFrom(uri); URI indexedArtifactURI = ResourceUtil.uriFrom(id); String msg = "Cannot index: ID " + id.getSegments() + " already taken by artifact '" + indexedArtifactURI + "'"; SokanIndexPlugin.logError(uri + ": " + msg, null); } /** * Registers a new load option. * * @param key the option's key * @param value the option's value */ public void addLoadOption(String key, Object value) { indexerManager.addLoadOption(key, value); } /** * Initializes a new resource set with the registered * load options. * * @return the new resource set */ public ResourceSet createNewResourceSet() { return indexerManager.createNewResourceSet(); } /** * Registers a new indexer. * * @param indexerConfiguration the index configuration that contains * the indexer with its dependencies */ public void addIndexer(IndexerConfiguration indexerConfiguration) { indexerManager.addIndexer(indexerConfiguration); } /** * @return all registered indexers */ public List<IndexerConfiguration> getIndexerConfigurations() { return indexerManager.getIndexerConfigurations(); } /** * Use the specified persistency manager. * * @param persistencyManager the persistency manager */ public void setPersistencyManager(PersistencyManager persistencyManager) { this.persistencyManager = persistencyManager; this.indexerManager = new IndexerManager(persistencyManager); } }