1 package org.simantics.workbench.internal.contributions.search;
4 import java.io.IOException;
5 import java.net.MalformedURLException;
7 import java.net.URISyntaxException;
9 import java.util.ArrayList;
10 import java.util.Collection;
11 import java.util.Collections;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.List;
18 import org.eclipse.core.runtime.IProgressMonitor;
19 import org.eclipse.core.runtime.IStatus;
20 import org.eclipse.core.runtime.NullProgressMonitor;
21 import org.eclipse.core.runtime.Status;
22 import org.eclipse.core.runtime.SubMonitor;
23 import org.eclipse.core.runtime.jobs.Job;
24 import org.eclipse.jface.viewers.ISelection;
25 import org.eclipse.jface.viewers.StructuredSelection;
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.browser.LocationEvent;
28 import org.eclipse.swt.browser.LocationListener;
29 import org.eclipse.swt.events.DisposeEvent;
30 import org.eclipse.swt.events.DisposeListener;
31 import org.eclipse.swt.widgets.Control;
32 import org.eclipse.swt.widgets.Display;
33 import org.eclipse.swt.widgets.Shell;
34 import org.eclipse.ui.IViewPart;
35 import org.eclipse.ui.IViewReference;
36 import org.eclipse.ui.IWorkbenchPage;
37 import org.eclipse.ui.IWorkbenchWindow;
38 import org.eclipse.ui.PartInitException;
39 import org.eclipse.ui.PlatformUI;
40 import org.simantics.ObjectIdentitySchedulingRule;
41 import org.simantics.Simantics;
42 import org.simantics.databoard.Bindings;
43 import org.simantics.db.ReadGraph;
44 import org.simantics.db.Resource;
45 import org.simantics.db.Session;
46 import org.simantics.db.exception.DatabaseException;
47 import org.simantics.db.layer0.adapter.Instances;
48 import org.simantics.db.layer0.request.ActiveModels;
49 import org.simantics.db.request.Read;
50 import org.simantics.db.service.SerialisationSupport;
51 import org.simantics.editors.Browser;
52 import org.simantics.editors.BrowserInput;
53 import org.simantics.layer0.Layer0;
54 import org.simantics.scl.runtime.function.Function;
55 import org.simantics.scl.runtime.function.Function5;
56 import org.simantics.ui.workbench.action.ChooseActionRequest;
57 import org.simantics.utils.FileUtils;
58 import org.simantics.utils.datastructures.MapList;
59 import org.simantics.utils.datastructures.Pair;
60 import org.simantics.utils.ui.ErrorLogger;
61 import org.simantics.utils.ui.ExceptionUtils;
62 import org.simantics.utils.ui.workbench.WorkbenchUtils;
63 import org.simantics.workbench.internal.Activator;
64 import org.simantics.workbench.ontology.WorkbenchResource;
65 import org.simantics.workbench.search.ISearchService;
66 import org.simantics.workbench.search.QueryResult;
67 import org.simantics.workbench.search.SearchEngine;
68 import org.simantics.workbench.search.SearchQuery;
69 import org.simantics.workbench.search.SearchResult;
70 import org.simantics.workbench.search.Searching;
72 import freemarker.template.TemplateException;
75 * @author Tuukka Lehtonen
76 * @author Marko Luukkainen
78 public class SearchServiceImpl implements ISearchService{
80 private static final String SEARCH_TEMP_FILE_SUFFIX = "search.html";
81 private static final String SEARCH_TEMP_DIR = "search";
82 private static final String BROWSER_VIEW = BrowserView.ID;
83 private static final Integer MAX_RESULTS = 1000;
86 public void performQuery(SearchQuery query, ResultBrowser browserType, boolean activateResultBrowser) {
88 Set<SearchEngine> searchEngines = getSearchEngines();
90 // if query does not define used engines, use all available engines.
91 boolean containsEngines = false;
92 for (SearchEngine e : searchEngines) {
93 if (query.getSearchFlags().containsKey(e.getId())) {
94 containsEngines = true;
98 if (!containsEngines) {
99 for (SearchEngine e : searchEngines)
100 if (e.isEnabledByDefault())
101 query.setSearchFlag(e.getId(), "on");
103 } catch (DatabaseException e) {
104 ExceptionUtils.logError(e);
106 switch (browserType) {
108 performEditorQuery(query);
111 performViewQuery(query, activateResultBrowser);
117 private void performViewQuery(SearchQuery query, boolean activateResultBrowser) {
119 browserView = (BrowserView) showLocalView(
121 activateResultBrowser ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_CREATE);
122 if (browserView.getBrowser() != browserViewBrowser) {
123 browserViewBrowser = browserView.getBrowser();
124 browserViewTemporaryFiles = new ArrayList<File>();
125 browserViewTemporaryFiles.add(getNewTemporaryFile());
126 browserView.getBrowser().addLocationListener(browserViewLocationListener);
127 browserView.getBrowser().addDisposeListener(new DisposeListener() {
129 public void widgetDisposed(DisposeEvent e) {
130 browserViewBrowser = null;
134 browserView.setPartProperty(BrowserView.LAST_QUERY_PROPERTY, query.getOriginalQuery());
135 updateViewQuery(browserView, browserViewTemporaryFiles, query);
136 } catch (IOException e1) {
137 ErrorLogger.defaultLogError(e1);
138 } catch (PartInitException e1) {
139 ErrorLogger.defaultLogError(e1);
143 private IViewPart showLocalView(String id, int mode) throws PartInitException {
144 final IWorkbenchWindow wb = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
148 // Is view already created in the currently active workbench window?
149 IViewPart vp = findViewFromWindow(wb, id, false);
151 if (mode == IWorkbenchPage.VIEW_CREATE)
153 IWorkbenchPage page = vp.getViewSite().getPage();
154 if (mode == IWorkbenchPage.VIEW_VISIBLE) {
156 } else if (mode == IWorkbenchPage.VIEW_ACTIVATE) {
162 // Create the view on the active window's active page
163 IWorkbenchPage page = wb.getActivePage();
165 IWorkbenchPage pages[] = wb.getPages();
166 if (pages.length == 0) return null;
169 return page.showView(id, null, mode);
172 private static IViewPart findViewFromWindow(IWorkbenchWindow wb, String viewId, boolean restore) {
173 for (IWorkbenchPage page : wb.getPages()) {
174 IViewReference vr = page.findViewReference(viewId);
176 IViewPart vp = vr.getView(restore);
185 * There's always only one of these around.
187 BrowserView browserView = null;
189 org.eclipse.swt.browser.Browser browserViewBrowser = null;
191 List<File> browserViewTemporaryFiles = null;
193 LocationListener browserViewLocationListener = new LocationListener() {
195 public void changing(LocationEvent event) {
196 //System.out.println("changing: " + event);
198 URI newUri = new URI(event.location);
200 // Handle resource links by opening an editor for them
201 if ("resource".equals(newUri.getScheme())) {
203 openResource(getShell(event), newUri.getSchemeSpecificPart());
206 } catch (URISyntaxException e) {
207 // This URI was not needed anyway, resource: URIs will always parse properly so let
208 // it go without logging.
209 } catch (DatabaseException e) {
210 ErrorLogger.defaultLogError(e);
215 public void changed(LocationEvent event) {
216 // System.out.println("changed: " + event);
218 URL newUrl = new URL(event.location);
219 String query = newUrl.getQuery();
221 SearchQuery searchQuery = SearchQuery.decode(newUrl);
222 if (searchQuery.getOriginalQuery() != null) {
223 event.doit = updateViewQuery(
225 browserViewTemporaryFiles,
230 } catch (IOException e) {
231 ErrorLogger.defaultLogError(e);
236 class ViewQueryJob extends Job {
240 List<File> temporaryFiles;
242 BrowserView browserView;
246 public ViewQueryJob(SearchQuery query, List<File> temporaryFiles, BrowserView browserView) {
249 this.temporaryFiles = temporaryFiles;
250 this.browserView = browserView;
251 this.display = browserView.getBrowser().getDisplay();
255 protected IStatus run(IProgressMonitor monitor) {
257 SubMonitor mon = SubMonitor.convert(monitor);
258 monitor.beginTask("Performing search '" + query + "'", 10);
259 monitor.subTask("Querying index");
260 final List<QueryResult> result = createResultPage(mon.newChild(8), query, MAX_RESULTS);
261 if (result == null) {
262 return Status.CANCEL_STATUS;
265 monitor.subTask("Generating results");
266 updatePages(result, temporaryFiles);
268 updateBrowser(browserView, result.get(0));
269 return Status.OK_STATUS;
270 } catch (DatabaseException e1) {
271 return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
272 "Unexpected database problems during search result processing, see exception.", e1);
273 } catch (IOException e1) {
274 return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
275 "Unexpected I/O problems during search result processing, see exception.", e1);
281 void updateBrowser(final BrowserView browserView, final QueryResult result) {
282 if (display.isDisposed())
284 display.asyncExec(new Runnable() {
287 if (browserView.isDisposed())
291 long t1 = System.currentTimeMillis();
292 browserView.setUrl(temporaryFiles.get(0).toURI().toURL());
293 //browserView.setContentDescription("'" + query + "' - " + result.getHitCount() + " matches in active model");
294 long t2 = System.currentTimeMillis();
295 System.out.println("Updating UI " + (t2-t1) + " ms");
296 } catch (MalformedURLException e) {
297 ErrorLogger.defaultLogError(e);
304 protected boolean updateViewQuery(BrowserView browserView, List<File> temporaryFiles, SearchQuery query) {
305 ViewQueryJob job = new ViewQueryJob(query, temporaryFiles, browserView);
306 job.setRule(new ObjectIdentitySchedulingRule(browserView));
311 protected Shell getShell(LocationEvent event) {
312 if (event.widget instanceof Control) {
313 Control c = (Control) event.widget;
324 protected void performEditorQuery(final SearchQuery query) {
326 final List<File> temporaryFiles = new ArrayList<File>();
327 temporaryFiles.add(getNewTemporaryFile());
328 final BrowserInput input = createBrowserInput(temporaryFiles.get(0), query);
329 updateInput(input, temporaryFiles.get(0), query);
331 final Browser browser = (Browser) WorkbenchUtils.openEditor("org.simantics.editors.browser", input);
333 browser.getBrowser().addLocationListener(new LocationListener() {
335 public void changing(LocationEvent event) {
336 // System.out.println("changing: " + event);
338 // TODO : is there a better way to escape location?
339 URI newUri = new URI(event.location.replaceAll(" ", "%20"));
340 // Handle resource links by opening an editor for them
341 if ("resource".equals(newUri.getScheme())) {
343 openResource(getShell(event), newUri.getSchemeSpecificPart());
346 } catch (URISyntaxException e) {
347 ErrorLogger.defaultLogError(e);
348 } catch (DatabaseException e) {
349 ErrorLogger.defaultLogError(e);
354 public void changed(LocationEvent event) {
356 // OpenEditor does not seem to pass any parameters (anything after '?' character)
357 SearchQuery searchQuery = query;
358 if (searchQuery.getOriginalQuery() != null) {
359 event.doit = updateQuery(browser, input, temporaryFiles, searchQuery);
364 } catch (IOException e1) {
365 ErrorLogger.defaultLogError(e1);
366 } catch (PartInitException e1) {
367 ErrorLogger.defaultLogError(e1);
371 protected void openResource(Shell shell, String link) throws DatabaseException {
373 Session session = Simantics.getSession();
374 final long id = Long.parseLong(link);
376 Resource resource = session.syncRequest(new Read<Resource>() {
378 public Resource perform(ReadGraph graph) throws DatabaseException {
379 SerialisationSupport ss = graph.getService(SerialisationSupport.class);
380 return ss.getResource(id);
384 ISelection input = new StructuredSelection(resource);
385 String perspectiveId = WorkbenchUtils.getCurrentPerspectiveId();
387 // Try the doubleClick-extensions
388 session.asyncRequest(new ChooseActionRequest(shell, input, perspectiveId, false, true));
389 } catch (NumberFormatException e) {
397 protected static boolean updateQuery(Browser browser, BrowserInput input, List<File> temporaryFiles, final SearchQuery query) {
399 String url = browser.getBrowser().getUrl();
400 String inputUrl = input.getUrl().toString();
401 if (url.equals(inputUrl))
404 List<QueryResult> result = createResultPage(new NullProgressMonitor(), query, MAX_RESULTS);
405 updateInput(input, temporaryFiles.get(0), query);
406 updatePages(result,temporaryFiles);
407 // browser.getBrowser().setUrl(inputUrl);
408 browser.getBrowser().refresh();
409 // temporaryFile.delete();
410 for (File temporaryFile : temporaryFiles)
411 temporaryFile.deleteOnExit();
413 } catch (DatabaseException e1) {
414 ErrorLogger.defaultLogError(e1);
415 } catch (IOException e1) {
416 ErrorLogger.defaultLogError(e1);
421 private static File ensureTemporaryDirectoryExists() throws IOException {
422 return Simantics.getTemporaryDirectory(SEARCH_TEMP_DIR);
428 * @throws IOException
430 private static File getNewTemporaryFile() throws IOException {
431 return Simantics.getTempfile(SEARCH_TEMP_DIR, SEARCH_TEMP_FILE_SUFFIX);
438 * @throws IOException
440 private static BrowserInput createBrowserInput(File file, SearchQuery query) throws IOException {
441 // new BrowserInput(file.toURI().toURL()
442 return new BrowserInput(SearchQuery.encode(file, query), query.getOriginalQuery(), false, false, SWT.NONE);
445 private static void updatePages(List<QueryResult> result, List<File> temporaryFiles) throws IOException{
446 ensureTemporaryDirectoryExists();
447 for (int i = temporaryFiles.size(); i < result.size(); i++) {
448 temporaryFiles.add(getNewTemporaryFile());
450 if (result.size() > 1) {
451 createPageControls(result,temporaryFiles);
453 if (result.size() > 0) {
454 for (int i = 0; i < result.size(); i++) {
455 updatePage(temporaryFiles.get(i), result.get(i).getHtml());
458 updatePage(temporaryFiles.get(0), "");
465 * @throws IOException
467 private static void updatePage(File file, String html) throws IOException {
468 long t1 = System.currentTimeMillis();
469 FileUtils.writeFile(file, html.getBytes("UTF-8"));
470 long t2 = System.currentTimeMillis();
471 System.out.println("Writing html page took " + (t2-t1) + " ms");
474 private static void createPageControls(List<QueryResult> result, List<File> temporaryFiles) throws IOException{
475 if (result.size() == 1)
477 for (int i = 0; i < result.size(); i++) {
478 boolean first = i == 0;
479 boolean last = i == result.size() -1;
480 QueryResult current = result.get(i);
481 String pageContrtol = "<div class=\"resultInfo\">";
482 pageContrtol += "Page " + (i+1) + " of "+ + result.size() + " ";
484 pageContrtol += "First";
485 pageContrtol += " Previous";
486 pageContrtol += " <a href=\"" + temporaryFiles.get(i+1).toURI().toURL().toString() + "\">Next</a>";
487 pageContrtol += " <a href=\"" + temporaryFiles.get(result.size()-1).toURI().toURL().toString() + "\">Last</a>";
489 pageContrtol += "<a href=\"" + temporaryFiles.get(0).toURI().toURL().toString() + "\">First</a>";
490 pageContrtol += " <a href=\"" + temporaryFiles.get(i-1).toURI().toURL().toString() + "\">Previous</a>";
491 pageContrtol += " Next";
492 pageContrtol += " Last";
494 pageContrtol += "<a href=\"" + temporaryFiles.get(0).toURI().toURL().toString() + "\">First</a>";
495 pageContrtol += " <a href=\"" + temporaryFiles.get(i-1).toURI().toURL().toString() + "\">Previous</a>";
496 pageContrtol += " <a href=\"" + temporaryFiles.get(i+1).toURI().toURL().toString() + "\">Next</a>";
497 pageContrtol += " <a href=\"" + temporaryFiles.get(result.size()-1).toURI().toURL().toString() + "\">Last</a>";
499 pageContrtol += "</div><br>";
501 result.set(i, new QueryResult(current.getHeader(), pageContrtol+current.getContent()+pageContrtol,current.getFooter(),current.getHitCount()));
511 * @throws IOException
513 private static void updateInput(BrowserInput input, File file, SearchQuery query) throws IOException {
514 URL url = SearchQuery.encode(file, query);
516 input.setName(query.getOriginalQuery());
517 // System.out.println("update input: " + url + " - " + query + " - " + input);
520 private static Set<SearchEngine> getSearchEngines() throws DatabaseException{
521 return Simantics.getSession().syncRequest(new Read<Set<SearchEngine>>() {
523 public Set<SearchEngine> perform(ReadGraph graph)
524 throws DatabaseException {
525 Resource project = Simantics.peekProjectResource();
527 return Collections.emptySet();
528 return getSearchEngines(graph, project);
533 private static Set<SearchEngine> getSearchEngines(ReadGraph graph, Resource project) throws DatabaseException{
534 Set<SearchEngine> searchEngines = new HashSet<SearchEngine>();
535 Map<Resource, Set<SearchEngine>> result = getSearchEnginesByModel(graph, project);
536 for (Set<SearchEngine> set : result.values())
537 searchEngines.addAll(set);
538 return searchEngines;
542 private static Map<Resource,Set<SearchEngine>> getSearchEnginesByModel(ReadGraph graph, Resource project) throws DatabaseException{
543 WorkbenchResource WB = WorkbenchResource.getInstance(graph);
544 Layer0 L0 = Layer0.getInstance(graph);
545 Map<Resource, Set<SearchEngine>> result = new HashMap<Resource, Set<SearchEngine>>();
546 Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));
547 for (Resource model : activeModels) {
548 Set<SearchEngine> searchEngines = new HashSet<SearchEngine>();
550 List<Resource> searchFunctions = new ArrayList<Resource>();
551 searchFunctions.addAll(graph.getObjects(model, WB.HasWorkbenchSearchFunction));
552 if (searchFunctions.size() == 0) {
553 searchEngines.addAll(findSearchContributions(graph, model));
555 if (searchFunctions.isEmpty() && searchEngines.isEmpty()) {
556 // TODO : backwards compatibility, to be removed.
557 searchFunctions.addAll(graph.getObjects(project, WB.HasWorkbenchSearchFunction));
559 if (searchFunctions.isEmpty() && searchEngines.isEmpty())
560 searchFunctions.add( WB.DependenciesSearchFunction);
562 for (Resource searchFunction : searchFunctions) {
563 @SuppressWarnings("rawtypes")
564 Function f = graph.adapt(searchFunction, Function.class);
565 String id = graph.getURI(searchFunction);
566 String name = graph.getPossibleRelatedValue2(searchFunction, L0.HasLabel);
567 @SuppressWarnings("unchecked")
568 SearchEngine engine = new SearchEngine(id,name,(Function5<IProgressMonitor, ReadGraph, Resource, SearchQuery, Integer, SearchResult>)f, true);
570 engine.addSupportedParam("Name");
571 engine.addSupportedParam("Types");
573 searchEngines.add(engine);
575 result.put(model, searchEngines);
586 * @throws DatabaseException
588 private static List<QueryResult> createResultPage(final IProgressMonitor monitor, final SearchQuery query, final int maxResults) throws DatabaseException {
589 return Simantics.getSession().syncRequest(new Read<List<QueryResult>>() {
591 public List<QueryResult> perform(ReadGraph graph) throws DatabaseException {
592 Resource project = Simantics.peekProjectResource();
596 Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));
597 //if (activeModels.isEmpty())
599 long t1 = System.currentTimeMillis();
600 Set<SearchEngine> searchEngines = new HashSet<SearchEngine>();
601 MapList<Resource, Pair<SearchEngine,SearchResult>> searchResults = new MapList<Resource, Pair<SearchEngine,SearchResult>>();
602 Map<Resource,Set<SearchEngine>> searchEngineMap = getSearchEnginesByModel(graph, project);
604 for (Resource model : activeModels) {
605 for (SearchEngine engine : searchEngineMap.get(model)) {
607 if (query.getBooleanFlag(engine.getId())) {
608 SearchResult result = engine.getSearchFunction().apply(monitor, graph, model, query, maxResults);
609 if (!result.isEmpty()) {
610 searchResults.add(model, new Pair<SearchEngine,SearchResult>(engine, result));
613 searchEngines.add(engine);
615 // searchResults.put(model, result);
617 long t2 = System.currentTimeMillis();
618 System.out.println("Running search queries took " + (t2-t1) + " ms for query '" + query + "'");
621 return Searching.generatePage(graph, searchEngines,query, maxResults, searchResults);
622 } catch (IOException e) {
623 Activator.logError("I/O problem while generating search result page.", e);
624 } catch (TemplateException e) {
625 Activator.logError("Template definition problem in search result page generation. Please inform the developers.", e);
632 private static Collection<SearchEngine> findSearchContributions(ReadGraph graph, Resource model) throws DatabaseException {
633 WorkbenchResource WB = WorkbenchResource.getInstance(graph);
634 Instances contributionFinder = graph.adapt(WB.SearchContribution, Instances.class);
635 Resource index = model;
636 List<SearchEngine> result = new ArrayList<SearchEngine>();
637 for (Resource r : contributionFinder.find(graph, index)) {
638 SearchEngine engine = contributionToEngine(graph, r);
645 private static SearchEngine contributionToEngine(ReadGraph graph, Resource searchContribution) throws DatabaseException {
646 Layer0 L0 = Layer0.getInstance(graph);
647 WorkbenchResource WB = WorkbenchResource.getInstance(graph);
649 Resource searchFunction = graph.getPossibleObject(searchContribution, WB.hasSearchFunction);
650 if (searchFunction == null)
653 @SuppressWarnings("rawtypes")
654 Function f = graph.adapt(searchFunction, Function.class);
655 String id = graph.getURI(searchFunction);
656 String name = graph.getPossibleRelatedValue2(searchFunction, L0.HasLabel);
658 Boolean enabledByDefault = graph.getPossibleRelatedValue2(searchContribution, WB.SearchContribution_isEnabledByDefault, Bindings.BOOLEAN);
659 boolean enabled = !Boolean.FALSE.equals(enabledByDefault);
661 @SuppressWarnings("unchecked")
662 SearchEngine engine = new SearchEngine(id,name,(Function5<IProgressMonitor, ReadGraph, Resource, SearchQuery, Integer, SearchResult>)f, enabled);
664 engine.addSupportedParam("Name");
665 engine.addSupportedParam("Types");