X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.workbench%2Fsrc%2Forg%2Fsimantics%2Fworkbench%2Finternal%2Fcontributions%2Fsearch%2FSearchServiceImpl.java;fp=bundles%2Forg.simantics.workbench%2Fsrc%2Forg%2Fsimantics%2Fworkbench%2Finternal%2Fcontributions%2Fsearch%2FSearchServiceImpl.java;h=e0a96ed12c39d62c880743b945bd6a1fecea2738;hp=07a9af5daf3c9b34111791b64c7108467ae40607;hb=0ae2b770234dfc3cbb18bd38f324125cf0faca07;hpb=24e2b34260f219f0d1644ca7a138894980e25b14 diff --git a/bundles/org.simantics.workbench/src/org/simantics/workbench/internal/contributions/search/SearchServiceImpl.java b/bundles/org.simantics.workbench/src/org/simantics/workbench/internal/contributions/search/SearchServiceImpl.java index 07a9af5da..e0a96ed12 100644 --- a/bundles/org.simantics.workbench/src/org/simantics/workbench/internal/contributions/search/SearchServiceImpl.java +++ b/bundles/org.simantics.workbench/src/org/simantics/workbench/internal/contributions/search/SearchServiceImpl.java @@ -1,670 +1,670 @@ -package org.simantics.workbench.internal.contributions.search; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.SubMonitor; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.swt.SWT; -import org.eclipse.swt.browser.LocationEvent; -import org.eclipse.swt.browser.LocationListener; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.IViewPart; -import org.eclipse.ui.IViewReference; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.PlatformUI; -import org.simantics.ObjectIdentitySchedulingRule; -import org.simantics.Simantics; -import org.simantics.databoard.Bindings; -import org.simantics.db.ReadGraph; -import org.simantics.db.Resource; -import org.simantics.db.Session; -import org.simantics.db.exception.DatabaseException; -import org.simantics.db.layer0.adapter.Instances; -import org.simantics.db.layer0.request.ActiveModels; -import org.simantics.db.request.Read; -import org.simantics.db.service.SerialisationSupport; -import org.simantics.editors.Browser; -import org.simantics.editors.BrowserInput; -import org.simantics.layer0.Layer0; -import org.simantics.scl.runtime.function.Function; -import org.simantics.scl.runtime.function.Function5; -import org.simantics.ui.workbench.action.ChooseActionRequest; -import org.simantics.utils.FileUtils; -import org.simantics.utils.datastructures.MapList; -import org.simantics.utils.datastructures.Pair; -import org.simantics.utils.ui.ErrorLogger; -import org.simantics.utils.ui.ExceptionUtils; -import org.simantics.utils.ui.workbench.WorkbenchUtils; -import org.simantics.workbench.internal.Activator; -import org.simantics.workbench.ontology.WorkbenchResource; -import org.simantics.workbench.search.ISearchService; -import org.simantics.workbench.search.QueryResult; -import org.simantics.workbench.search.SearchEngine; -import org.simantics.workbench.search.SearchQuery; -import org.simantics.workbench.search.SearchResult; -import org.simantics.workbench.search.Searching; - -import freemarker.template.TemplateException; - -/** - * @author Tuukka Lehtonen - * @author Marko Luukkainen - */ -public class SearchServiceImpl implements ISearchService{ - - private static final String SEARCH_TEMP_FILE_SUFFIX = "search.html"; - private static final String SEARCH_TEMP_DIR = "search"; - private static final String BROWSER_VIEW = BrowserView.ID; - private static final Integer MAX_RESULTS = 1000; - - @Override - public void performQuery(SearchQuery query, ResultBrowser browserType, boolean activateResultBrowser) { - try { - Set searchEngines = getSearchEngines(); - - // if query does not define used engines, use all available engines. - boolean containsEngines = false; - for (SearchEngine e : searchEngines) { - if (query.getSearchFlags().containsKey(e.getId())) { - containsEngines = true; - break; - } - } - if (!containsEngines) { - for (SearchEngine e : searchEngines) - if (e.isEnabledByDefault()) - query.setSearchFlag(e.getId(), "on"); - } - } catch (DatabaseException e) { - ExceptionUtils.logError(e); - } - switch (browserType) { - case EDITOR: - performEditorQuery(query); - break; - case VIEW: - performViewQuery(query, activateResultBrowser); - break; - } - - } - - private void performViewQuery(SearchQuery query, boolean activateResultBrowser) { - try { - browserView = (BrowserView) showLocalView( - BROWSER_VIEW, - activateResultBrowser ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_CREATE); - if (browserView.getBrowser() != browserViewBrowser) { - browserViewBrowser = browserView.getBrowser(); - browserViewTemporaryFiles = new ArrayList(); - browserViewTemporaryFiles.add(getNewTemporaryFile()); - browserView.getBrowser().addLocationListener(browserViewLocationListener); - browserView.getBrowser().addDisposeListener(new DisposeListener() { - @Override - public void widgetDisposed(DisposeEvent e) { - browserViewBrowser = null; - } - }); - } - browserView.setPartProperty(BrowserView.LAST_QUERY_PROPERTY, query.getOriginalQuery()); - updateViewQuery(browserView, browserViewTemporaryFiles, query); - } catch (IOException e1) { - ErrorLogger.defaultLogError(e1); - } catch (PartInitException e1) { - ErrorLogger.defaultLogError(e1); - } - } - - private IViewPart showLocalView(String id, int mode) throws PartInitException { - final IWorkbenchWindow wb = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - if (wb == null) - return null; - - // Is view already created in the currently active workbench window? - IViewPart vp = findViewFromWindow(wb, id, false); - if (vp != null) { - if (mode == IWorkbenchPage.VIEW_CREATE) - return vp; - IWorkbenchPage page = vp.getViewSite().getPage(); - if (mode == IWorkbenchPage.VIEW_VISIBLE) { - page.bringToTop(vp); - } else if (mode == IWorkbenchPage.VIEW_ACTIVATE) { - page.activate(vp); - } - return vp; - } - - // Create the view on the active window's active page - IWorkbenchPage page = wb.getActivePage(); - if (page == null) { - IWorkbenchPage pages[] = wb.getPages(); - if (pages.length == 0) return null; - page = pages[0]; - } - return page.showView(id, null, mode); - } - - private static IViewPart findViewFromWindow(IWorkbenchWindow wb, String viewId, boolean restore) { - for (IWorkbenchPage page : wb.getPages()) { - IViewReference vr = page.findViewReference(viewId); - if (vr != null) { - IViewPart vp = vr.getView(restore); - if (vp != null) - return vp; - } - } - return null; - } - - /** - * There's always only one of these around. - */ - BrowserView browserView = null; - - org.eclipse.swt.browser.Browser browserViewBrowser = null; - - List browserViewTemporaryFiles = null; - - LocationListener browserViewLocationListener = new LocationListener() { - @Override - public void changing(LocationEvent event) { - //System.out.println("changing: " + event); - try { - URI newUri = new URI(event.location); - - // Handle resource links by opening an editor for them - if ("resource".equals(newUri.getScheme())) { - event.doit = false; - openResource(getShell(event), newUri.getSchemeSpecificPart()); - return; - } - } catch (URISyntaxException e) { - // This URI was not needed anyway, resource: URIs will always parse properly so let - // it go without logging. - } catch (DatabaseException e) { - ErrorLogger.defaultLogError(e); - } - } - - @Override - public void changed(LocationEvent event) { - // System.out.println("changed: " + event); - try { - URL newUrl = new URL(event.location); - String query = newUrl.getQuery(); - if (query != null) { - SearchQuery searchQuery = SearchQuery.decode(newUrl); - if (searchQuery.getOriginalQuery() != null) { - event.doit = updateViewQuery( - browserView, - browserViewTemporaryFiles, - searchQuery); - - } - } - } catch (IOException e) { - ErrorLogger.defaultLogError(e); - } - } - }; - - class ViewQueryJob extends Job { - - SearchQuery query; - - List temporaryFiles; - - BrowserView browserView; - - Display display; - - public ViewQueryJob(SearchQuery query, List temporaryFiles, BrowserView browserView) { - super("Search..."); - this.query = query; - this.temporaryFiles = temporaryFiles; - this.browserView = browserView; - this.display = browserView.getBrowser().getDisplay(); - } - - @Override - protected IStatus run(IProgressMonitor monitor) { - try { - SubMonitor mon = SubMonitor.convert(monitor); - monitor.beginTask("Performing search '" + query + "'", 10); - monitor.subTask("Querying index"); - final List result = createResultPage(mon.newChild(8), query, MAX_RESULTS); - if (result == null) { - return Status.CANCEL_STATUS; - } - monitor.worked(8); - monitor.subTask("Generating results"); - updatePages(result, temporaryFiles); - monitor.worked(2); - updateBrowser(browserView, result.get(0)); - return Status.OK_STATUS; - } catch (DatabaseException e1) { - return new Status(IStatus.ERROR, Activator.PLUGIN_ID, - "Unexpected database problems during search result processing, see exception.", e1); - } catch (IOException e1) { - return new Status(IStatus.ERROR, Activator.PLUGIN_ID, - "Unexpected I/O problems during search result processing, see exception.", e1); - } finally { - monitor.done(); - } - } - - void updateBrowser(final BrowserView browserView, final QueryResult result) { - if (display.isDisposed()) - return; - display.asyncExec(new Runnable() { - @Override - public void run() { - if (browserView.isDisposed()) - return; - - try { - long t1 = System.currentTimeMillis(); - browserView.setUrl(temporaryFiles.get(0).toURI().toURL()); - //browserView.setContentDescription("'" + query + "' - " + result.getHitCount() + " matches in active model"); - long t2 = System.currentTimeMillis(); - System.out.println("Updating UI " + (t2-t1) + " ms"); - } catch (MalformedURLException e) { - ErrorLogger.defaultLogError(e); - } - } - }); - } - } - - protected boolean updateViewQuery(BrowserView browserView, List temporaryFiles, SearchQuery query) { - ViewQueryJob job = new ViewQueryJob(query, temporaryFiles, browserView); - job.setRule(new ObjectIdentitySchedulingRule(browserView)); - job.schedule(); - return true; - } - - protected Shell getShell(LocationEvent event) { - if (event.widget instanceof Control) { - Control c = (Control) event.widget; - if (c.isDisposed()) - return null; - return c.getShell(); - } - return null; - } - - /** - * @param query - */ - protected void performEditorQuery(final SearchQuery query) { - try { - final List temporaryFiles = new ArrayList(); - temporaryFiles.add(getNewTemporaryFile()); - final BrowserInput input = createBrowserInput(temporaryFiles.get(0), query); - updateInput(input, temporaryFiles.get(0), query); - - final Browser browser = (Browser) WorkbenchUtils.openEditor("org.simantics.editors.browser", input); - - browser.getBrowser().addLocationListener(new LocationListener() { - @Override - public void changing(LocationEvent event) { - // System.out.println("changing: " + event); - try { - // TODO : is there a better way to escape location? - URI newUri = new URI(event.location.replaceAll(" ", "%20")); - // Handle resource links by opening an editor for them - if ("resource".equals(newUri.getScheme())) { - event.doit = false; - openResource(getShell(event), newUri.getSchemeSpecificPart()); - return; - } - } catch (URISyntaxException e) { - ErrorLogger.defaultLogError(e); - } catch (DatabaseException e) { - ErrorLogger.defaultLogError(e); - } - } - - @Override - public void changed(LocationEvent event) { - if (query != null) { - // OpenEditor does not seem to pass any parameters (anything after '?' character) - SearchQuery searchQuery = query; - if (searchQuery.getOriginalQuery() != null) { - event.doit = updateQuery(browser, input, temporaryFiles, searchQuery); - } - } - } - }); - } catch (IOException e1) { - ErrorLogger.defaultLogError(e1); - } catch (PartInitException e1) { - ErrorLogger.defaultLogError(e1); - } - } - - protected void openResource(Shell shell, String link) throws DatabaseException { - try { - Session session = Simantics.getSession(); - final long id = Long.parseLong(link); - - Resource resource = session.syncRequest(new Read() { - @Override - public Resource perform(ReadGraph graph) throws DatabaseException { - SerialisationSupport ss = graph.getService(SerialisationSupport.class); - return ss.getResource(id); - } - }); - - ISelection input = new StructuredSelection(resource); - String perspectiveId = WorkbenchUtils.getCurrentPerspectiveId(); - - // Try the doubleClick-extensions - session.asyncRequest(new ChooseActionRequest(shell, input, perspectiveId, false, true)); - } catch (NumberFormatException e) { - return; - } - } - - /** - * @param query - */ - protected static boolean updateQuery(Browser browser, BrowserInput input, List temporaryFiles, final SearchQuery query) { - try { - String url = browser.getBrowser().getUrl(); - String inputUrl = input.getUrl().toString(); - if (url.equals(inputUrl)) - return false; - - List result = createResultPage(new NullProgressMonitor(), query, MAX_RESULTS); - updateInput(input, temporaryFiles.get(0), query); - updatePages(result,temporaryFiles); - // browser.getBrowser().setUrl(inputUrl); - browser.getBrowser().refresh(); - // temporaryFile.delete(); - for (File temporaryFile : temporaryFiles) - temporaryFile.deleteOnExit(); - return true; - } catch (DatabaseException e1) { - ErrorLogger.defaultLogError(e1); - } catch (IOException e1) { - ErrorLogger.defaultLogError(e1); - } - return false; - } - - private static File ensureTemporaryDirectoryExists() throws IOException { - return Simantics.getTemporaryDirectory(SEARCH_TEMP_DIR); - } - - /** - * @param html - * @return - * @throws IOException - */ - private static File getNewTemporaryFile() throws IOException { - return Simantics.getTempfile(SEARCH_TEMP_DIR, SEARCH_TEMP_FILE_SUFFIX); - } - - /** - * @param query - * @param html - * @return - * @throws IOException - */ - private static BrowserInput createBrowserInput(File file, SearchQuery query) throws IOException { - // new BrowserInput(file.toURI().toURL() - return new BrowserInput(SearchQuery.encode(file, query), query.getOriginalQuery(), false, false, SWT.NONE); - } - - private static void updatePages(List result, List temporaryFiles) throws IOException{ - ensureTemporaryDirectoryExists(); - for (int i = temporaryFiles.size(); i < result.size(); i++) { - temporaryFiles.add(getNewTemporaryFile()); - } - if (result.size() > 1) { - createPageControls(result,temporaryFiles); - } - if (result.size() > 0) { - for (int i = 0; i < result.size(); i++) { - updatePage(temporaryFiles.get(i), result.get(i).getHtml()); - } - } else { - updatePage(temporaryFiles.get(0), ""); - } - } - - /** - * @param html - * @return - * @throws IOException - */ - private static void updatePage(File file, String html) throws IOException { - long t1 = System.currentTimeMillis(); - FileUtils.writeFile(file, html.getBytes("UTF-8")); - long t2 = System.currentTimeMillis(); - System.out.println("Writing html page took " + (t2-t1) + " ms"); - } - - private static void createPageControls(List result, List temporaryFiles) throws IOException{ - if (result.size() == 1) - return; - for (int i = 0; i < result.size(); i++) { - boolean first = i == 0; - boolean last = i == result.size() -1; - QueryResult current = result.get(i); - String pageContrtol = "
"; - pageContrtol += "Page " + (i+1) + " of "+ + result.size() + " "; - if (first) { - pageContrtol += "First"; - pageContrtol += " Previous"; - pageContrtol += " Next"; - pageContrtol += " Last"; - } else if (last) { - pageContrtol += "First"; - pageContrtol += " Previous"; - pageContrtol += " Next"; - pageContrtol += " Last"; - } else { - pageContrtol += "First"; - pageContrtol += " Previous"; - pageContrtol += " Next"; - pageContrtol += " Last"; - } - pageContrtol += "

"; - - result.set(i, new QueryResult(current.getHeader(), pageContrtol+current.getContent()+pageContrtol,current.getFooter(),current.getHitCount())); - } - - } - - - - /** - * @param html - * @return - * @throws IOException - */ - private static void updateInput(BrowserInput input, File file, SearchQuery query) throws IOException { - URL url = SearchQuery.encode(file, query); - input.setUrl(url); - input.setName(query.getOriginalQuery()); - // System.out.println("update input: " + url + " - " + query + " - " + input); - } - - private static Set getSearchEngines() throws DatabaseException{ - return Simantics.getSession().syncRequest(new Read>() { - @Override - public Set perform(ReadGraph graph) - throws DatabaseException { - Resource project = Simantics.peekProjectResource(); - if (project == null) - return Collections.emptySet(); - return getSearchEngines(graph, project); - } - }); - } - - private static Set getSearchEngines(ReadGraph graph, Resource project) throws DatabaseException{ - Set searchEngines = new HashSet(); - Map> result = getSearchEnginesByModel(graph, project); - for (Set set : result.values()) - searchEngines.addAll(set); - return searchEngines; - } - - - private static Map> getSearchEnginesByModel(ReadGraph graph, Resource project) throws DatabaseException{ - WorkbenchResource WB = WorkbenchResource.getInstance(graph); - Layer0 L0 = Layer0.getInstance(graph); - Map> result = new HashMap>(); - Collection activeModels = graph.syncRequest(new ActiveModels(project)); - for (Resource model : activeModels) { - Set searchEngines = new HashSet(); - - List searchFunctions = new ArrayList(); - searchFunctions.addAll(graph.getObjects(model, WB.HasWorkbenchSearchFunction)); - if (searchFunctions.size() == 0) { - searchEngines.addAll(findSearchContributions(graph, model)); - } - if (searchFunctions.isEmpty() && searchEngines.isEmpty()) { - // TODO : backwards compatibility, to be removed. - searchFunctions.addAll(graph.getObjects(project, WB.HasWorkbenchSearchFunction)); - } - if (searchFunctions.isEmpty() && searchEngines.isEmpty()) - searchFunctions.add( WB.DependenciesSearchFunction); - - for (Resource searchFunction : searchFunctions) { - @SuppressWarnings("rawtypes") - Function f = graph.adapt(searchFunction, Function.class); - String id = graph.getURI(searchFunction); - String name = graph.getPossibleRelatedValue2(searchFunction, L0.HasLabel); - @SuppressWarnings("unchecked") - SearchEngine engine = new SearchEngine(id,name,(Function5)f, true); - { - engine.addSupportedParam("Name"); - engine.addSupportedParam("Types"); - } - searchEngines.add(engine); - } - result.put(model, searchEngines); - } - return result; - } - - - /** - * @param subMonitor - * @param query - * @param maxResults - * @return - * @throws DatabaseException - */ - private static List createResultPage(final IProgressMonitor monitor, final SearchQuery query, final int maxResults) throws DatabaseException { - return Simantics.getSession().syncRequest(new Read>() { - @Override - public List perform(ReadGraph graph) throws DatabaseException { - Resource project = Simantics.peekProjectResource(); - if (project == null) - return null; - - Collection activeModels = graph.syncRequest(new ActiveModels(project)); - //if (activeModels.isEmpty()) - // return null; - long t1 = System.currentTimeMillis(); - Set searchEngines = new HashSet(); - MapList> searchResults = new MapList>(); - Map> searchEngineMap = getSearchEnginesByModel(graph, project); - - for (Resource model : activeModels) { - for (SearchEngine engine : searchEngineMap.get(model)) { - - if (query.getBooleanFlag(engine.getId())) { - SearchResult result = engine.getSearchFunction().apply(monitor, graph, model, query, maxResults); - if (!result.isEmpty()) { - searchResults.add(model, new Pair(engine, result)); - } - } - searchEngines.add(engine); - } -// searchResults.put(model, result); - } - long t2 = System.currentTimeMillis(); - System.out.println("Running search queries took " + (t2-t1) + " ms for query '" + query + "'"); - - try { - return Searching.generatePage(graph, searchEngines,query, maxResults, searchResults); - } catch (IOException e) { - Activator.logError("I/O problem while generating search result page.", e); - } catch (TemplateException e) { - Activator.logError("Template definition problem in search result page generation. Please inform the developers.", e); - } - return null; - } - }); - } - - private static Collection findSearchContributions(ReadGraph graph, Resource model) throws DatabaseException { - WorkbenchResource WB = WorkbenchResource.getInstance(graph); - Instances contributionFinder = graph.adapt(WB.SearchContribution, Instances.class); - Resource index = model; - List result = new ArrayList(); - for (Resource r : contributionFinder.find(graph, index)) { - SearchEngine engine = contributionToEngine(graph, r); - if (engine != null) - result.add(engine); - } - return result; - } - - private static SearchEngine contributionToEngine(ReadGraph graph, Resource searchContribution) throws DatabaseException { - Layer0 L0 = Layer0.getInstance(graph); - WorkbenchResource WB = WorkbenchResource.getInstance(graph); - - Resource searchFunction = graph.getPossibleObject(searchContribution, WB.hasSearchFunction); - if (searchFunction == null) - return null; - - @SuppressWarnings("rawtypes") - Function f = graph.adapt(searchFunction, Function.class); - String id = graph.getURI(searchFunction); - String name = graph.getPossibleRelatedValue2(searchFunction, L0.HasLabel); - - Boolean enabledByDefault = graph.getPossibleRelatedValue2(searchContribution, WB.SearchContribution_isEnabledByDefault, Bindings.BOOLEAN); - boolean enabled = !Boolean.FALSE.equals(enabledByDefault); - - @SuppressWarnings("unchecked") - SearchEngine engine = new SearchEngine(id,name,(Function5)f, enabled); - { - engine.addSupportedParam("Name"); - engine.addSupportedParam("Types"); - } - return engine; - } - -} +package org.simantics.workbench.internal.contributions.search; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.browser.LocationEvent; +import org.eclipse.swt.browser.LocationListener; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IViewReference; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.simantics.ObjectIdentitySchedulingRule; +import org.simantics.Simantics; +import org.simantics.databoard.Bindings; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.Session; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.adapter.Instances; +import org.simantics.db.layer0.request.ActiveModels; +import org.simantics.db.request.Read; +import org.simantics.db.service.SerialisationSupport; +import org.simantics.editors.Browser; +import org.simantics.editors.BrowserInput; +import org.simantics.layer0.Layer0; +import org.simantics.scl.runtime.function.Function; +import org.simantics.scl.runtime.function.Function5; +import org.simantics.ui.workbench.action.ChooseActionRequest; +import org.simantics.utils.FileUtils; +import org.simantics.utils.datastructures.MapList; +import org.simantics.utils.datastructures.Pair; +import org.simantics.utils.ui.ErrorLogger; +import org.simantics.utils.ui.ExceptionUtils; +import org.simantics.utils.ui.workbench.WorkbenchUtils; +import org.simantics.workbench.internal.Activator; +import org.simantics.workbench.ontology.WorkbenchResource; +import org.simantics.workbench.search.ISearchService; +import org.simantics.workbench.search.QueryResult; +import org.simantics.workbench.search.SearchEngine; +import org.simantics.workbench.search.SearchQuery; +import org.simantics.workbench.search.SearchResult; +import org.simantics.workbench.search.Searching; + +import freemarker.template.TemplateException; + +/** + * @author Tuukka Lehtonen + * @author Marko Luukkainen + */ +public class SearchServiceImpl implements ISearchService{ + + private static final String SEARCH_TEMP_FILE_SUFFIX = "search.html"; + private static final String SEARCH_TEMP_DIR = "search"; + private static final String BROWSER_VIEW = BrowserView.ID; + private static final Integer MAX_RESULTS = 1000; + + @Override + public void performQuery(SearchQuery query, ResultBrowser browserType, boolean activateResultBrowser) { + try { + Set searchEngines = getSearchEngines(); + + // if query does not define used engines, use all available engines. + boolean containsEngines = false; + for (SearchEngine e : searchEngines) { + if (query.getSearchFlags().containsKey(e.getId())) { + containsEngines = true; + break; + } + } + if (!containsEngines) { + for (SearchEngine e : searchEngines) + if (e.isEnabledByDefault()) + query.setSearchFlag(e.getId(), "on"); + } + } catch (DatabaseException e) { + ExceptionUtils.logError(e); + } + switch (browserType) { + case EDITOR: + performEditorQuery(query); + break; + case VIEW: + performViewQuery(query, activateResultBrowser); + break; + } + + } + + private void performViewQuery(SearchQuery query, boolean activateResultBrowser) { + try { + browserView = (BrowserView) showLocalView( + BROWSER_VIEW, + activateResultBrowser ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_CREATE); + if (browserView.getBrowser() != browserViewBrowser) { + browserViewBrowser = browserView.getBrowser(); + browserViewTemporaryFiles = new ArrayList(); + browserViewTemporaryFiles.add(getNewTemporaryFile()); + browserView.getBrowser().addLocationListener(browserViewLocationListener); + browserView.getBrowser().addDisposeListener(new DisposeListener() { + @Override + public void widgetDisposed(DisposeEvent e) { + browserViewBrowser = null; + } + }); + } + browserView.setPartProperty(BrowserView.LAST_QUERY_PROPERTY, query.getOriginalQuery()); + updateViewQuery(browserView, browserViewTemporaryFiles, query); + } catch (IOException e1) { + ErrorLogger.defaultLogError(e1); + } catch (PartInitException e1) { + ErrorLogger.defaultLogError(e1); + } + } + + private IViewPart showLocalView(String id, int mode) throws PartInitException { + final IWorkbenchWindow wb = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (wb == null) + return null; + + // Is view already created in the currently active workbench window? + IViewPart vp = findViewFromWindow(wb, id, false); + if (vp != null) { + if (mode == IWorkbenchPage.VIEW_CREATE) + return vp; + IWorkbenchPage page = vp.getViewSite().getPage(); + if (mode == IWorkbenchPage.VIEW_VISIBLE) { + page.bringToTop(vp); + } else if (mode == IWorkbenchPage.VIEW_ACTIVATE) { + page.activate(vp); + } + return vp; + } + + // Create the view on the active window's active page + IWorkbenchPage page = wb.getActivePage(); + if (page == null) { + IWorkbenchPage pages[] = wb.getPages(); + if (pages.length == 0) return null; + page = pages[0]; + } + return page.showView(id, null, mode); + } + + private static IViewPart findViewFromWindow(IWorkbenchWindow wb, String viewId, boolean restore) { + for (IWorkbenchPage page : wb.getPages()) { + IViewReference vr = page.findViewReference(viewId); + if (vr != null) { + IViewPart vp = vr.getView(restore); + if (vp != null) + return vp; + } + } + return null; + } + + /** + * There's always only one of these around. + */ + BrowserView browserView = null; + + org.eclipse.swt.browser.Browser browserViewBrowser = null; + + List browserViewTemporaryFiles = null; + + LocationListener browserViewLocationListener = new LocationListener() { + @Override + public void changing(LocationEvent event) { + //System.out.println("changing: " + event); + try { + URI newUri = new URI(event.location); + + // Handle resource links by opening an editor for them + if ("resource".equals(newUri.getScheme())) { + event.doit = false; + openResource(getShell(event), newUri.getSchemeSpecificPart()); + return; + } + } catch (URISyntaxException e) { + // This URI was not needed anyway, resource: URIs will always parse properly so let + // it go without logging. + } catch (DatabaseException e) { + ErrorLogger.defaultLogError(e); + } + } + + @Override + public void changed(LocationEvent event) { + // System.out.println("changed: " + event); + try { + URL newUrl = new URL(event.location); + String query = newUrl.getQuery(); + if (query != null) { + SearchQuery searchQuery = SearchQuery.decode(newUrl); + if (searchQuery.getOriginalQuery() != null) { + event.doit = updateViewQuery( + browserView, + browserViewTemporaryFiles, + searchQuery); + + } + } + } catch (IOException e) { + ErrorLogger.defaultLogError(e); + } + } + }; + + class ViewQueryJob extends Job { + + SearchQuery query; + + List temporaryFiles; + + BrowserView browserView; + + Display display; + + public ViewQueryJob(SearchQuery query, List temporaryFiles, BrowserView browserView) { + super("Search..."); + this.query = query; + this.temporaryFiles = temporaryFiles; + this.browserView = browserView; + this.display = browserView.getBrowser().getDisplay(); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + SubMonitor mon = SubMonitor.convert(monitor); + monitor.beginTask("Performing search '" + query + "'", 10); + monitor.subTask("Querying index"); + final List result = createResultPage(mon.newChild(8), query, MAX_RESULTS); + if (result == null) { + return Status.CANCEL_STATUS; + } + monitor.worked(8); + monitor.subTask("Generating results"); + updatePages(result, temporaryFiles); + monitor.worked(2); + updateBrowser(browserView, result.get(0)); + return Status.OK_STATUS; + } catch (DatabaseException e1) { + return new Status(IStatus.ERROR, Activator.PLUGIN_ID, + "Unexpected database problems during search result processing, see exception.", e1); + } catch (IOException e1) { + return new Status(IStatus.ERROR, Activator.PLUGIN_ID, + "Unexpected I/O problems during search result processing, see exception.", e1); + } finally { + monitor.done(); + } + } + + void updateBrowser(final BrowserView browserView, final QueryResult result) { + if (display.isDisposed()) + return; + display.asyncExec(new Runnable() { + @Override + public void run() { + if (browserView.isDisposed()) + return; + + try { + long t1 = System.currentTimeMillis(); + browserView.setUrl(temporaryFiles.get(0).toURI().toURL()); + //browserView.setContentDescription("'" + query + "' - " + result.getHitCount() + " matches in active model"); + long t2 = System.currentTimeMillis(); + System.out.println("Updating UI " + (t2-t1) + " ms"); + } catch (MalformedURLException e) { + ErrorLogger.defaultLogError(e); + } + } + }); + } + } + + protected boolean updateViewQuery(BrowserView browserView, List temporaryFiles, SearchQuery query) { + ViewQueryJob job = new ViewQueryJob(query, temporaryFiles, browserView); + job.setRule(new ObjectIdentitySchedulingRule(browserView)); + job.schedule(); + return true; + } + + protected Shell getShell(LocationEvent event) { + if (event.widget instanceof Control) { + Control c = (Control) event.widget; + if (c.isDisposed()) + return null; + return c.getShell(); + } + return null; + } + + /** + * @param query + */ + protected void performEditorQuery(final SearchQuery query) { + try { + final List temporaryFiles = new ArrayList(); + temporaryFiles.add(getNewTemporaryFile()); + final BrowserInput input = createBrowserInput(temporaryFiles.get(0), query); + updateInput(input, temporaryFiles.get(0), query); + + final Browser browser = (Browser) WorkbenchUtils.openEditor("org.simantics.editors.browser", input); + + browser.getBrowser().addLocationListener(new LocationListener() { + @Override + public void changing(LocationEvent event) { + // System.out.println("changing: " + event); + try { + // TODO : is there a better way to escape location? + URI newUri = new URI(event.location.replaceAll(" ", "%20")); + // Handle resource links by opening an editor for them + if ("resource".equals(newUri.getScheme())) { + event.doit = false; + openResource(getShell(event), newUri.getSchemeSpecificPart()); + return; + } + } catch (URISyntaxException e) { + ErrorLogger.defaultLogError(e); + } catch (DatabaseException e) { + ErrorLogger.defaultLogError(e); + } + } + + @Override + public void changed(LocationEvent event) { + if (query != null) { + // OpenEditor does not seem to pass any parameters (anything after '?' character) + SearchQuery searchQuery = query; + if (searchQuery.getOriginalQuery() != null) { + event.doit = updateQuery(browser, input, temporaryFiles, searchQuery); + } + } + } + }); + } catch (IOException e1) { + ErrorLogger.defaultLogError(e1); + } catch (PartInitException e1) { + ErrorLogger.defaultLogError(e1); + } + } + + protected void openResource(Shell shell, String link) throws DatabaseException { + try { + Session session = Simantics.getSession(); + final long id = Long.parseLong(link); + + Resource resource = session.syncRequest(new Read() { + @Override + public Resource perform(ReadGraph graph) throws DatabaseException { + SerialisationSupport ss = graph.getService(SerialisationSupport.class); + return ss.getResource(id); + } + }); + + ISelection input = new StructuredSelection(resource); + String perspectiveId = WorkbenchUtils.getCurrentPerspectiveId(); + + // Try the doubleClick-extensions + session.asyncRequest(new ChooseActionRequest(shell, input, perspectiveId, false, true)); + } catch (NumberFormatException e) { + return; + } + } + + /** + * @param query + */ + protected static boolean updateQuery(Browser browser, BrowserInput input, List temporaryFiles, final SearchQuery query) { + try { + String url = browser.getBrowser().getUrl(); + String inputUrl = input.getUrl().toString(); + if (url.equals(inputUrl)) + return false; + + List result = createResultPage(new NullProgressMonitor(), query, MAX_RESULTS); + updateInput(input, temporaryFiles.get(0), query); + updatePages(result,temporaryFiles); + // browser.getBrowser().setUrl(inputUrl); + browser.getBrowser().refresh(); + // temporaryFile.delete(); + for (File temporaryFile : temporaryFiles) + temporaryFile.deleteOnExit(); + return true; + } catch (DatabaseException e1) { + ErrorLogger.defaultLogError(e1); + } catch (IOException e1) { + ErrorLogger.defaultLogError(e1); + } + return false; + } + + private static File ensureTemporaryDirectoryExists() throws IOException { + return Simantics.getTemporaryDirectory(SEARCH_TEMP_DIR); + } + + /** + * @param html + * @return + * @throws IOException + */ + private static File getNewTemporaryFile() throws IOException { + return Simantics.getTempfile(SEARCH_TEMP_DIR, SEARCH_TEMP_FILE_SUFFIX); + } + + /** + * @param query + * @param html + * @return + * @throws IOException + */ + private static BrowserInput createBrowserInput(File file, SearchQuery query) throws IOException { + // new BrowserInput(file.toURI().toURL() + return new BrowserInput(SearchQuery.encode(file, query), query.getOriginalQuery(), false, false, SWT.NONE); + } + + private static void updatePages(List result, List temporaryFiles) throws IOException{ + ensureTemporaryDirectoryExists(); + for (int i = temporaryFiles.size(); i < result.size(); i++) { + temporaryFiles.add(getNewTemporaryFile()); + } + if (result.size() > 1) { + createPageControls(result,temporaryFiles); + } + if (result.size() > 0) { + for (int i = 0; i < result.size(); i++) { + updatePage(temporaryFiles.get(i), result.get(i).getHtml()); + } + } else { + updatePage(temporaryFiles.get(0), ""); + } + } + + /** + * @param html + * @return + * @throws IOException + */ + private static void updatePage(File file, String html) throws IOException { + long t1 = System.currentTimeMillis(); + FileUtils.writeFile(file, html.getBytes("UTF-8")); + long t2 = System.currentTimeMillis(); + System.out.println("Writing html page took " + (t2-t1) + " ms"); + } + + private static void createPageControls(List result, List temporaryFiles) throws IOException{ + if (result.size() == 1) + return; + for (int i = 0; i < result.size(); i++) { + boolean first = i == 0; + boolean last = i == result.size() -1; + QueryResult current = result.get(i); + String pageContrtol = "
"; + pageContrtol += "Page " + (i+1) + " of "+ + result.size() + " "; + if (first) { + pageContrtol += "First"; + pageContrtol += " Previous"; + pageContrtol += " Next"; + pageContrtol += " Last"; + } else if (last) { + pageContrtol += "First"; + pageContrtol += " Previous"; + pageContrtol += " Next"; + pageContrtol += " Last"; + } else { + pageContrtol += "First"; + pageContrtol += " Previous"; + pageContrtol += " Next"; + pageContrtol += " Last"; + } + pageContrtol += "

"; + + result.set(i, new QueryResult(current.getHeader(), pageContrtol+current.getContent()+pageContrtol,current.getFooter(),current.getHitCount())); + } + + } + + + + /** + * @param html + * @return + * @throws IOException + */ + private static void updateInput(BrowserInput input, File file, SearchQuery query) throws IOException { + URL url = SearchQuery.encode(file, query); + input.setUrl(url); + input.setName(query.getOriginalQuery()); + // System.out.println("update input: " + url + " - " + query + " - " + input); + } + + private static Set getSearchEngines() throws DatabaseException{ + return Simantics.getSession().syncRequest(new Read>() { + @Override + public Set perform(ReadGraph graph) + throws DatabaseException { + Resource project = Simantics.peekProjectResource(); + if (project == null) + return Collections.emptySet(); + return getSearchEngines(graph, project); + } + }); + } + + private static Set getSearchEngines(ReadGraph graph, Resource project) throws DatabaseException{ + Set searchEngines = new HashSet(); + Map> result = getSearchEnginesByModel(graph, project); + for (Set set : result.values()) + searchEngines.addAll(set); + return searchEngines; + } + + + private static Map> getSearchEnginesByModel(ReadGraph graph, Resource project) throws DatabaseException{ + WorkbenchResource WB = WorkbenchResource.getInstance(graph); + Layer0 L0 = Layer0.getInstance(graph); + Map> result = new HashMap>(); + Collection activeModels = graph.syncRequest(new ActiveModels(project)); + for (Resource model : activeModels) { + Set searchEngines = new HashSet(); + + List searchFunctions = new ArrayList(); + searchFunctions.addAll(graph.getObjects(model, WB.HasWorkbenchSearchFunction)); + if (searchFunctions.size() == 0) { + searchEngines.addAll(findSearchContributions(graph, model)); + } + if (searchFunctions.isEmpty() && searchEngines.isEmpty()) { + // TODO : backwards compatibility, to be removed. + searchFunctions.addAll(graph.getObjects(project, WB.HasWorkbenchSearchFunction)); + } + if (searchFunctions.isEmpty() && searchEngines.isEmpty()) + searchFunctions.add( WB.DependenciesSearchFunction); + + for (Resource searchFunction : searchFunctions) { + @SuppressWarnings("rawtypes") + Function f = graph.adapt(searchFunction, Function.class); + String id = graph.getURI(searchFunction); + String name = graph.getPossibleRelatedValue2(searchFunction, L0.HasLabel); + @SuppressWarnings("unchecked") + SearchEngine engine = new SearchEngine(id,name,(Function5)f, true); + { + engine.addSupportedParam("Name"); + engine.addSupportedParam("Types"); + } + searchEngines.add(engine); + } + result.put(model, searchEngines); + } + return result; + } + + + /** + * @param subMonitor + * @param query + * @param maxResults + * @return + * @throws DatabaseException + */ + private static List createResultPage(final IProgressMonitor monitor, final SearchQuery query, final int maxResults) throws DatabaseException { + return Simantics.getSession().syncRequest(new Read>() { + @Override + public List perform(ReadGraph graph) throws DatabaseException { + Resource project = Simantics.peekProjectResource(); + if (project == null) + return null; + + Collection activeModels = graph.syncRequest(new ActiveModels(project)); + //if (activeModels.isEmpty()) + // return null; + long t1 = System.currentTimeMillis(); + Set searchEngines = new HashSet(); + MapList> searchResults = new MapList>(); + Map> searchEngineMap = getSearchEnginesByModel(graph, project); + + for (Resource model : activeModels) { + for (SearchEngine engine : searchEngineMap.get(model)) { + + if (query.getBooleanFlag(engine.getId())) { + SearchResult result = engine.getSearchFunction().apply(monitor, graph, model, query, maxResults); + if (!result.isEmpty()) { + searchResults.add(model, new Pair(engine, result)); + } + } + searchEngines.add(engine); + } +// searchResults.put(model, result); + } + long t2 = System.currentTimeMillis(); + System.out.println("Running search queries took " + (t2-t1) + " ms for query '" + query + "'"); + + try { + return Searching.generatePage(graph, searchEngines,query, maxResults, searchResults); + } catch (IOException e) { + Activator.logError("I/O problem while generating search result page.", e); + } catch (TemplateException e) { + Activator.logError("Template definition problem in search result page generation. Please inform the developers.", e); + } + return null; + } + }); + } + + private static Collection findSearchContributions(ReadGraph graph, Resource model) throws DatabaseException { + WorkbenchResource WB = WorkbenchResource.getInstance(graph); + Instances contributionFinder = graph.adapt(WB.SearchContribution, Instances.class); + Resource index = model; + List result = new ArrayList(); + for (Resource r : contributionFinder.find(graph, index)) { + SearchEngine engine = contributionToEngine(graph, r); + if (engine != null) + result.add(engine); + } + return result; + } + + private static SearchEngine contributionToEngine(ReadGraph graph, Resource searchContribution) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + WorkbenchResource WB = WorkbenchResource.getInstance(graph); + + Resource searchFunction = graph.getPossibleObject(searchContribution, WB.hasSearchFunction); + if (searchFunction == null) + return null; + + @SuppressWarnings("rawtypes") + Function f = graph.adapt(searchFunction, Function.class); + String id = graph.getURI(searchFunction); + String name = graph.getPossibleRelatedValue2(searchFunction, L0.HasLabel); + + Boolean enabledByDefault = graph.getPossibleRelatedValue2(searchContribution, WB.SearchContribution_isEnabledByDefault, Bindings.BOOLEAN); + boolean enabled = !Boolean.FALSE.equals(enabledByDefault); + + @SuppressWarnings("unchecked") + SearchEngine engine = new SearchEngine(id,name,(Function5)f, enabled); + { + engine.addSupportedParam("Name"); + engine.addSupportedParam("Types"); + } + return engine; + } + +}