Tutorial: Searching the EMF Index

Contents


Overview

The EMF index framework indexes files in the workspace that were created using EMF as well as information contained within a given ResourceSet. From all of this indexing information a client can obtain information such as EObjects that reference a particular EObject, EObjects that are of a certain EClass among many other types of queries. The advantage of using the index framework is that queries are possible without having to load all of the files in the workspace into your resource set and having to iterate over the entire resource set in order to retrieve the results. The savings can be in terms of both memory usage and time to execute.

[back to top]

References

This tutorial assumes that the reader has knowledge of EMF and the eclipse PDE development environment. It is essential that the reader understands the basic reflective mechanisms of EMF. Concepts such as IFile, ISchedulingRule and content types are used throughout the tutorial.

[back to top]

Introduction

The goal of this tutorial to prepare the EMF indexing framework for us to use it to query for certain information stored in our EMF files. We will construct index queries and execute them on the index search manager.

[back to top]

Indexing Framework Preparations

The indexing framework does not index every file it finds in the workspace. It can't assume that every file with be an EMF file and it cannot assume that it will be capable of parsing any given file. Only files that have certain content types that are declared against the extension point are indexed. All other files are completely ignored.

In our case, we have a special content type for our files produced with our special "extended library" metamodel. We will add the following extension to our plugin.

<extension
	point="com.ibm.xtools.emf.index.configurationEntries">
		<contentType
			typeIds="org.eclipse.emf.examples.library.extendedLibrary"/>
</extension>
		

When the framework indexes a file it will record all of the EReferences, and the EClass of an EObject. Every resource will have its imports/exports recorded. The framework will not index an EAttribute values unless we add an extension to our plugin. Note that the framework can only index information that is persisted. Structural features that are derived and/or transient cannot be indexed.

<extension
	point="com.ibm.xtools.emf.index.configurationEntries">
	<structuralFeature
		features="Book.title,Writer.firstName,Writer.lastName,BookOnTape.title,Library.name"
		nsURI="http:///org/eclipse/emf/examples/library/extlibrary.ecore/1.0.0"/>
</extension>
     	

After preparing the indexing framework we can now make queries against certain EAttributes such as the title of a book and the results can be gathered from any "extended library" file in the workspace.

[back to top]

Constructing a Simple Query

As a start we will construct a simple query that we will run on the index search manager. The scope for this query will be the entire workspace. We are querying for any books that have a specific title.

context = IndexContext.createWorkspaceContext(myResourceSet);
objects = IIndexSearchManager.INSTANCE.findEObjects(indexContext,
	"War and Peace", false, EXTLibraryPackage.eINSTANCE.getBook_Title(), null, monitor);
	
if (objects.isEmpty()) {
	print("No books with that name."); //$NON-NLS-1$
} else {
	print("Books are:"); //$NON-NLS-1$
	Iterator iter = objects.iterator();
	while (iter.hasNext()) {
		EObject eObject = (EObject) iter.next();
		print(eObject);
	}
}
		

Note that the returned objects could be proxies because our scope includes resources that are not loaded but are located in the workspace. It is for this reason that we do not attempt to access a book's information without first calling eIsProxy() on the eObject.

We could rewrite our query so that all of the returned objects will not be proxies so that we can print out the authors of the books. The side-effect will be that all of the necessary resources will be loaded into the resource set.

context = IndexContext.createWorkspaceContext(myResourceSet);
context.getOptions().put(IndexContext.RESOLVE_PROXIES, Boolean.TRUE);
objects = IIndexSearchManager.INSTANCE.findEObjects(indexContext,
	"War and Peace", false, EXTLibraryPackage.eINSTANCE.getBook_Title(), null, monitor);
	
if (objects.isEmpty()) {
	print("No books with that name."); //$NON-NLS-1$
} else {
	print("War and Peace authors are:"); //$NON-NLS-1$
	Iterator iter = objects.iterator();
	while (iter.hasNext()) {
		Book book = (Book) iter.next();
		Writer author = book.getAuthor();
		
		if (author != null && !author.eIsProxy()) {
			String lastName = null, firstName = null;
			lastName = author.getLastName();
			firstName = author.getFirstName();
			
			if (lastName != null && firstName != null) {
				print(lastName+", "+firstName);
			}
		}
		
	}
}
		

One of the many useful facets the indexing framework is that we could try to get more information about a book proxy without having to load its resource. Let's try rewriting the last section of the above code to get the names of the authors of each book even if the book object is a proxy and remove our use of the "RESOLVE_PROXIES" option.

context = IndexContext.createWorkspaceContext(myResourceSet);
objects = IIndexSearchManager.INSTANCE.findEObjects(indexContext,
	"War and Peace", false, EXTLibraryPackage.eINSTANCE.getBook_Title(), null, monitor);
	
if (objects.isEmpty()) {
	print("No books with that name."); //$NON-NLS-1$
} else {
	print("War and Peace authors are:"); //$NON-NLS-1$
	Iterator iter = objects.iterator();
	
	while (iter.hasNext()) {
		Book book = (Book)iter.next();
		Writer author = null;
		if (book.eIsProxy()) {
			// Check the index for the author of this book proxy
			Collection results = IIndexSearchManager.INSTANCE.findReferencedObjects(
				context, book, EXTLibraryPackage.eINSTANCE.getBook_Author(), null,
				monitor);
			
			// The result size will be one or zero because the author EReference
			//  has a multiplicity of 0..1
			if (results.size() == 1) {
				author = (Writer)results.iterator().next();
			}
		} else {
			author = book.getAuthor();
		}
		
		if (author != null) {
			String lastName = null, firstName = null;
			if (author.eIsProxy()) {
				// The author could be another proxy. We will use the
				//  index search manager to find its first and last name
				//  without loading any resources.
				lastName = (String) IIndexSearchManager.INSTANCE.findValue(
					context, author,
					EXTLibraryPackage.eINSTANCE.getAuthor_LastName(), monitor);
				firstName = (String) IIndexSearchManager.INSTANCE.findValue(
					context, author,
					EXTLibraryPackage.eINSTANCE.getAuthor_FirstName(), monitor);
			} else {
				lastName = author.getLastName();
				firstName = author.getFirstName();
			}
			
			if (lastName != null && firstName != null) {
				print(lastName+", "+firstName);
			}
		}
	}
}		
		

It is important to note that we are only able to query the index search manager for information based on the book's title and the writer's first and last name because we had included those EAttributes in our plugin's extension listed at the beginning of this tutorial. When we queried for the author of the book we get this information automatically because it is an EReference.

[back to top]

Finding Resource Exports

Some queries can be constructed to discover information regarding an entire resource, namely imports and exports. Imports occur whenever an EObject within a resource makes a reference to an EObject in another resource. An export occurs when a resource contains an EObject that is referenced by an EObject from another resource.

Whenever deleting a resource from the workspace it would be useful to know what other resources are importing this resource. Perhaps a warning should be displayed to the user to inform them that by deleting the resource, they will be affecting other resources.

context = IndexContext.createWorkspaceContext(myResourceSet);
exports = IIndexSearchManager.INSTANCE.findExports(indexContext,
	resourceToDelete, monitor);
if (exports.size() == 0) {
	// delete this resource from the workspace, it should be safe
} else {
	print("This resource should not be deleted because it is referenced by
			other resources in the workspace.");
}
		

[back to top]

Running Queries as Jobs

Queries can sometimes take some time to complete their search through the EMF index. In these cases the UI could execute the query as a job so that it can be run in the background while the user works on other things. To facilitate these workflows the index framework has provided a special QueryJob class.

QueryJob job = new QueryJob("My Index Query") { //$NON-NLS-1$
	protected Collection doRun(IProgressMonitor monitor) {
		try {
		IndexContext context = IndexContext.createWorkspaceContext(myResourceSet);
		return IIndexSearchManager.INSTANCE.findReferencingObjects();
		} catch (IndexException e) {
			// Log or otherwise handle the exception.
		}
		
		return null;
	};
};

job.schedule();

...
// When the job has finished we grab the results
Collection results = job.getResults();
		

Note that the job will have a workspace scheduling rule so it will conflict with any other jobs that require any scheduling rules for any files in the workspace.

[back to top]

Searching for Strings by Pattern

The indexing framework supports wildcard matches for string EAttribute values. For example, we could search for books that start with the word "War" in the loaded resources in our resource set. Note that the patterns used are not true regular expressions.

context = IndexContext.createDefaultContext(myResourceSet);
boolean ignoreCase = false;
warBooks = IIndexSearchManager.INSTANCE.findEObjects(indexContext,
			"War*", ignoreCase, EXTLibraryPackage.eINSTANCE.getBook_Title(), null, monitor);
		

[back to top]


Summary

In this tutorial, we did the following:

  1. Prepared the EMF index framework for the types of queries that we expect to be execute
  2. Constructed a simple query that found results from the workspace as well as loaded resources in our resource set
  3. Queried the EMF index for additional information about EObjects that have not been loaded into memory
  4. Discovered other resources in the workspace that reference a resource that the user was about to delete
  5. Ran a query as an eclipse job so that it could run asynchronously
  6. Found EObjects based on a wildcard pattern for the value of a particular string EAttribute

[back to top]


Legal notices