Indexing And Searching Models

The index framework supports the indexing of instance models from arbitrary Ecore models and supports queries to retrieve information from these models. Queries can provide information as EObjects that reference a particular EObject, EObjects that are of a certain EClass among many other types of queries. This indexing part of the framework provides an extension point for clients to contribute indexable information to support indexing of their artifacts. By default, the framework indexes the type (EClass of any EObject) and all reference information (containment and non-containment reference information) for every EObject. The infrastructure can optionally index attributes for an EObject (EAttributes). The goal of this section is to enable clients to index their artifacts and query for information from these files.

The code samples indicated in this section are demonstrated in the example Index Example.

Indexing Files

The indexing framework defines the com.ibm.xtools.emf.index.configurationEntries extension point for clients to configure entries for the indexing platform. To start with the framework will process only files associated with the content types declared against this extension point.

Registering the content types

The following example registers the special content type associated with files (.extlibrary) that are produced with the "Extended Library" metamodel. The plugins for the example metamodel are installed in the workspace as part of installing the index example.

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

Creating index entries

The class attribute defined in the extension above specifies the IIndexProviderFactory instance used to create the IIndexProvider instances. The index providers are used for creating index entries for files associated with these content type ids. The framework provides a factory class to handle xmi serialized files. This class is a SAX handler for parsing and creating index entries for files created using the org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl class with ids.

Index Example Wizard

The index entries for a given resource should include a ResourceEntry for the resource level information and every eObject in the resource should have a corresponding EObjectEntry. For creating index entries for XMI resources refer to IndexSAXXMIHandler for usage of these classes.

	//Create a resource entry:
	ResourceEntry resourceEntry = new ResourceEntry(resourceURI);
	
	//Add imports to the entry:
	resourceEntry.addImport(importingResourceURI);
	                 
	//Create an EObject entry:
	EObjectEntry eObjectEntry = new EObjectEntry(containerURI, eClassOfEObject);
	                 
	//Add reference value to the entry:
	eObjectEntry.addEReferenceValue(eReference, referencingEObjectURI);
	                 
	//Add attribute value to the entry:
	eObjectEntry.addEAttributeValue(eAttribute, value);
		

Optionally Indexing EAttributes

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 EAttribute values unless specified in the index extension. Note that the framework can only index information that is persisted. EAttribute features that are derived and/or transient cannot be indexed. The following example registers some of the EAttributes "Extended Library" metamodel.

     <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>
     	

Constructing a Simple Query

As a start we will construct a simple query using the IIndexSearchManager instance (IIndexSearchManager). The scope for this query will be the entire workspace. We are querying for any books that have a specific title.

	IndexContext context = IndexContext.createWorkspaceContext(myResourceSet);
	
	try {
		Collection objects = IIndexSearchManager.INSTANCE.findEObjects(indexContext,
		"War and Peace", false, EXTLibraryPackage.eINSTANCE.getBook_Title(), EXTLibraryPackage.eINSTANCE.getBook(), monitor);
	} catch (IndexException e) {
         // handle exception
	}     
		
	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 required resources will be loaded into the resource set.

	IndexContext context = IndexContext.createWorkspaceContext(myResourceSet);
	context.getOptions().put(IndexContext.RESOLVE_PROXIES, Boolean.TRUE);
	
	try {
		Collection objects = IIndexSearchManager.INSTANCE.findEObjects(indexContext,
		"War and Peace", false, EXTLibraryPackage.eINSTANCE.getBook_Title(), EXTLibraryPackage.eINSTANCE.getBook(), monitor);
	} catch (IndexException e) {
		// handle exception
	}	
	
	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);
				}
			}
			
		}
	}
		

It is important to note that we are only able to query 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 section. When we queried for the author of the book we get this information automatically because it is an EReference.

Retrieving proxy information

One of the many useful facets of the indexing framework is that we could try to get more information about a book proxy without having to load its resource.

Formulating new queries for retrieving proxy information

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.

	IndexContext context = IndexContext.createWorkspaceContext(myResourceSet);
	
	try {
		Collection objects = IIndexSearchManager.INSTANCE.findEObjects(indexContext,
		"War and Peace", false, EXTLibraryPackage.eINSTANCE.getBook_Title(), EXTLibraryPackage.eINSTANCE.getBook(), monitor);
	} catch (IndexException e) {
		// handle exception
	}
		
	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(), EXTLibraryPackage.eINSTANCE.getWriter(),
					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);
				}
			}
		}
	}		
		

Formulating queries using the "PROVIDE_INDEXED_DATA_FOR_PROXIES_AND_PARENTS" option

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 use the "PROVIDE_INDEXED_DATA_FOR_PROXIES_AND_PARENTS" option. Setting this option will populate the IProxyData cache within the context that contains attribute and reference information on proxies and their parents returned as part of the result set.

	IndexContext context = IndexContext.createWorkspaceContext(myResourceSet);
	context.getOptions().put(
					IndexContext.PROVIDE_INDEXED_DATA_FOR_PROXIES_AND_PARENTS,
					Boolean.TRUE);	
					
	try {
		Collection objects = IIndexSearchManager.INSTANCE.findEObjects(indexContext,
		"War and Peace", false, EXTLibraryPackage.eINSTANCE.getBook_Title(), EXTLibraryPackage.eINSTANCE.getBook(), monitor);
	} catch (IndexException e) {
		// handle exception
	}
		
	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();
		
		// Get the proxy data class
		IProxyData proxyData = context.getProxyData();
		
		while (iter.hasNext()) {
			Book book = (Book)iter.next();
			Writer author = null;
			if (book.eIsProxy()) {
				// Get the URI for the book
				URI uriBook = EcoreUtil.getURI(book);
				
				// Get the author of the book
				URI uriWriter = (URI) proxyData.getValue(uriBook,
						EXTLibraryPackage.eINSTANCE.getBook_Author());
				
				if (uriWriter != null) {
					// get the names of the author
					String lastName = (String) proxyData.getValue(uriWriter,
						EXTLibraryPackage.eINSTANCE.getAuthor_LastName());
					String firstName = (String) proxyData.getValue(uriWriter,
						EXTLibraryPackage.eINSTANCE.getAuthor_FirstName());	
				
					if (lastName != null && firstName != null) {
						print(lastName+", "+firstName);
					}
				}
			} else {
				author = book.getAuthor();
				String lastName = author.getLastName();
				String firstName = author.getFirstName();
				
				if (lastName != null && firstName != null) {
					print(lastName+", "+firstName);
				}				
			}
			
		}
	}		
		

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.

For example, whenever deleting a resource from the workspace it would be useful to know what other resources are importing this resource. The following example demonstrates formulating a query to find the exports.

	IndexContext context = IndexContext.createWorkspaceContext(myResourceSet);
	Collection exports = IIndexSearchManager.INSTANCE.findExports(indexContext,
		resourceToDelete, monitor);
	if (exports.size() == 0) {
		// no exports
	} else {
		Iterator iter = exports.iterator();
		while (iter.hasNext()) {
			Resource resource = (Resource)iter.next();
			print("The importing resource is " + resource.getURI().toString());
		}
	}
		

Running Queries as Jobs

Queries may sometimes take some time to complete their search through the index. In these cases the clients 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.

    //Create the job:
    QueryJob job = new QueryJob("Example Query") {
         protected Collection doRun(IProgressMonitor monitor) {
            // create the index context
            IndexContext context = IndexContext.createDefaultContext(new ResourceSetImpl());
                   
            // run the query
            Collection result = Collections.EMPTY_LIST;
            result = IIndexSearchManager.INSTANCE.findEObjects(context, EXTLibraryPackage.eINSTANCE.
              .getBookOnTape(), monitor);
                    
            return result;
        };
    };
     
    // Schedule the job:
    job.schedule();
       
    // Get the results:
    job.getResults();   
		

Wildcard support in queries

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 workspace. Note that the patterns used are not true regular expressions.

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

Legal notices