/** * */ package de.hannesniederhausen.storynotes.ui.internal.index; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.PostConstruct; import javax.inject.Inject; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.StaleReaderException; import org.apache.lucene.index.Term; import org.apache.lucene.queryParser.MultiFieldQueryParser; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.Directory; import org.apache.lucene.store.LockObtainFailedException; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.util.Version; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import de.hannesniederhausen.storynotes.model.Category; import de.hannesniederhausen.storynotes.model.File; import de.hannesniederhausen.storynotes.model.FileElement; import de.hannesniederhausen.storynotes.model.Note; import de.hannesniederhausen.storynotes.model.Project; import de.hannesniederhausen.storynotes.model.StorynotesPackage; import de.hannesniederhausen.storynotes.model.annotations.IAnnotationConstants; import de.hannesniederhausen.storynotes.model.service.IModelProviderService; /** * The {@link ModelIndexer} creates a lucene index from the current model. It * listenes to the command stack and adds model changes to the index. * * @author Hannes Niederhausen * */ public class ModelIndexer { private static final String NOTE = "note"; private static final String CATEGORY = "category"; private static final String NAME = "name"; private static final String TYPE = "type"; private static final String ID = "id"; private static final String DESCRIPTION = "description"; private static final String PROJECT = "project"; private static final String PARENT = "parent"; private Set<String> fieldNames; @Inject private IModelProviderService modelProviderService; private Directory index; private IndexAdapter indexAdapter; private IndexWriter writer; private StandardAnalyzer analyzer; @PostConstruct public void init() { try { java.io.File fsFile = new java.io.File("/tmp/index"); if (fsFile.exists()) { deleteDir(fsFile); } fieldNames = new HashSet<String>(); fieldNames.add(DESCRIPTION); indexAdapter = new IndexAdapter(); index = new RAMDirectory(); analyzer = new StandardAnalyzer(Version.LUCENE_29); writer = new IndexWriter(index, analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED); File file = modelProviderService.getFile(); for (Project p : file.getProjects()) { addProject(p, writer, true); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } public void dispose() { try { fieldNames.clear(); writer.close(); index.close(); indexAdapter.dispose(); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } } public List<Document> query(String queryString) { IndexSearcher searcher = null; try { searcher = new IndexSearcher(writer.getReader()); if (queryString.charAt(queryString.length() - 1) != ' ') { queryString += "*"; } int size = fieldNames.size(); String[] fields = fieldNames.toArray(new String[size]); BooleanClause.Occur[] flags = new BooleanClause.Occur[size]; for (int i = 0; i < size; i++) { flags[i] = BooleanClause.Occur.SHOULD; } Query query = MultiFieldQueryParser.parse(Version.LUCENE_29, queryString, fields, flags, analyzer); List<Document> resultList = new ArrayList<Document>(10); TopDocs td = searcher.search(query, null, 10); for (ScoreDoc sd : td.scoreDocs) { resultList.add(searcher.doc(sd.doc)); } return resultList; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally { try { if (searcher != null) searcher.close(); } catch (IOException e) { } } } /** * @param writer * @param file * @throws IOException * @throws CorruptIndexException */ private void addProject(Project p, IndexWriter writer, boolean handleChildren) throws CorruptIndexException, IOException { Document doc = createDocument(PROJECT, p.getName(), p); indexAdapter.addTo(p); String description = p.getDescription(); if (description != null) { doc.add(new Field(DESCRIPTION, description, Field.Store.YES, Field.Index.ANALYZED)); } for (EStructuralFeature f : p.eClass().getEAllStructuralFeatures()) { Object value = p.eGet(f); if (value != null) { if (f.getEAnnotation(IAnnotationConstants.MODEL_LABEL) != null) { doc.add(new Field("field_label", value.toString(), Field.Store.YES, Field.Index.NO)); } } } writer.addDocument(doc); if (handleChildren) { for (Category cat : p.getCategories()) { addCategory(cat, writer, handleChildren); } } } /** * @param p * @param doc * @param typeName * @param name * @param fe */ private Document createDocument(String typeName, String name, FileElement fe) { Document doc = new Document(); doc.add(new Field(ID, Long.toString(fe.getId()), Field.Store.YES, Field.Index.NO)); doc.add(new Field(TYPE, typeName, Field.Store.YES, Field.Index.NO)); if (name != null) doc.add(new Field(NAME, name, Field.Store.YES, Field.Index.ANALYZED)); EObject parent = fe.eContainer(); while (parent instanceof FileElement) { doc.add(new Field(PARENT, Long.toString(((FileElement) parent) .getId()), Field.Store.YES, Field.Index.NO)); parent = parent.eContainer(); } return doc; } /** * @param cat * @param writer * @throws IOException * @throws CorruptIndexException */ private void addCategory(Category cat, IndexWriter writer, boolean handleChildren) throws CorruptIndexException, IOException { Document doc = createDocument(CATEGORY, cat.getName(), cat); indexAdapter.addTo(cat); for (EStructuralFeature f : cat.eClass().getEAllStructuralFeatures()) { Object value = cat.eGet(f); if (value != null) { if (f.getEAnnotation(IAnnotationConstants.MODEL_LABEL) != null) { doc.add(new Field("field_label", value.toString(), Field.Store.YES, Field.Index.NO)); } } } if (handleChildren) { for (Note n : cat.getNotes()) { addNote(n, writer); } } writer.addDocument(doc); } /** * @param n * @param writer * @throws IOException * @throws CorruptIndexException */ private void addNote(Note n, IndexWriter writer) throws CorruptIndexException, IOException { Document doc = createDocument(NOTE, null, n); indexAdapter.addTo(n); for (EStructuralFeature f : n.eClass().getEStructuralFeatures()) { Object value = n.eGet(f); if (value != null) { if (f.getEAnnotation(IAnnotationConstants.MODEL_LABEL) != null) { doc.add(new Field("field_label", value.toString(), Field.Store.YES, Field.Index.NO)); } if (f.getEAnnotation(IAnnotationConstants.NO_INDEX_LABEL)==null) { doc.add(new Field(f.getName(), value.toString(), Field.Store.NO, Field.Index.ANALYZED)); fieldNames.add(f.getName()); } } } writer.addDocument(doc); } /** * Deletes * * @param dir */ private void deleteDir(java.io.File dir) { if (!dir.isDirectory()) { throw new IllegalArgumentException("dir is not a directory"); } for (java.io.File f : dir.listFiles()) { if (f.isDirectory()) { deleteDir(f); } else if (f.isFile()) { f.delete(); } } dir.delete(); } private void removeElement(FileElement element, boolean removeChildren) throws CorruptIndexException, IOException { removeElements(removeChildren, element); } private void removeElements(boolean removeChildren, FileElement... elements) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { for (FileElement e : elements) { Term term = new Term(ID, Long.toString(e.getId())); writer.deleteDocuments(term); if (removeChildren) { Term childTerm = new Term(PARENT, Long.toString(e.getId())); writer.deleteDocuments(childTerm); } } } private class IndexAdapter extends AdapterImpl { private Set<EObject> listenedObjects = new HashSet<EObject>(); @Override public void notifyChanged(Notification msg) { Object notifier = msg.getNotifier(); if (notifier instanceof File) { handleFileChanges(msg); return; } if (notifier instanceof Project) { handleProjectChanges(msg); return; } if (notifier instanceof Category) { handleCategoryChanges(msg); return; } if (notifier instanceof Note) { handleNoteChanges(msg); return; } } public void dispose() { for (EObject o : listenedObjects) { o.eAdapters().remove(this); } listenedObjects.clear(); } /** * @param msg */ private void handleNoteChanges(Notification msg) { try { Note note = (Note) msg.getNotifier(); removeElement(note, false); addNote(note, writer); } catch (Exception e) { throw new RuntimeException(e); } } /** * @param msg */ private void handleCategoryChanges(Notification msg) { try { switch (msg.getEventType()) { case Notification.ADD: { Note note = (Note) msg.getNewValue(); addNote(note, writer); break; } case Notification.ADD_MANY: { @SuppressWarnings("unchecked") List<Note> notes = (List<Note>) msg.getNewValue(); for (Note n : notes) { addNote(n, writer); } break; } case Notification.REMOVE: { Note note = (Note) msg.getOldValue(); removeElement(note, true); break; } case Notification.REMOVE_MANY: { @SuppressWarnings("unchecked") List<Note> notes = (List<Note>) msg.getOldValue(); removeElements(true, notes.toArray(new FileElement[notes.size()])); break; } case Notification.SET: { Category cat = (Category) msg.getNotifier(); removeElement(cat, false); addCategory(cat, writer, false); break; } } // end of switch } catch (Exception e) { throw new RuntimeException(e); } } /** * @param msg */ private void handleProjectChanges(Notification msg) { try { switch (msg.getEventType()) { case Notification.ADD: { Category cat = (Category) msg.getNewValue(); addCategory(cat, writer, true); break; } case Notification.ADD_MANY: { @SuppressWarnings("unchecked") List<Category> categories = (List<Category>) msg .getNewValue(); for (Category p : categories) { addCategory(p, writer, true); } break; } case Notification.REMOVE: { Category cat = (Category) msg.getOldValue(); removeElement(cat, true); break; } case Notification.REMOVE_MANY: { @SuppressWarnings("unchecked") List<Category> categories = (List<Category>) msg .getOldValue(); removeElements(true, categories.toArray(new FileElement[categories .size()])); break; } case Notification.SET: { Project project = (Project) msg.getNotifier(); removeElement(project, false); addProject(project, writer, false); break; } } // end of switch } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * @param msg */ private void handleFileChanges(Notification msg) { if (msg.getFeatureID(File.class) != StorynotesPackage.FILE__PROJECTS) return; try { switch (msg.getEventType()) { case Notification.ADD: { Project p = (Project) msg.getNewValue(); addProject(p, writer, true); break; } case Notification.ADD_MANY: { @SuppressWarnings("unchecked") List<Project> projects = (List<Project>) msg.getNewValue(); for (Project p : projects) { addProject(p, writer, true); } break; } case Notification.REMOVE: { Project p = (Project) msg.getNewValue(); removeElement(p, true); break; } case Notification.REMOVE_MANY: { @SuppressWarnings("unchecked") List<Project> projects = (List<Project>) msg.getNewValue(); removeElements(true, projects.toArray(new FileElement[projects.size()])); break; } } // end of switch } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } private void addTo(EObject element) { if (listenedObjects.contains(element)) return; element.eAdapters().add(this); listenedObjects.add(element); } } }