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.genericrelation.Dependencies;
49 import org.simantics.db.layer0.request.ActiveModels;
50 import org.simantics.db.request.Read;
51 import org.simantics.db.service.SerialisationSupport;
52 import org.simantics.editors.Browser;
53 import org.simantics.editors.BrowserInput;
54 import org.simantics.layer0.Layer0;
55 import org.simantics.scl.runtime.function.Function;
56 import org.simantics.scl.runtime.function.Function5;
57 import org.simantics.ui.workbench.action.ChooseActionRequest;
58 import org.simantics.utils.FileUtils;
59 import org.simantics.utils.datastructures.MapList;
60 import org.simantics.utils.datastructures.Pair;
61 import org.simantics.utils.ui.ErrorLogger;
62 import org.simantics.utils.ui.ExceptionUtils;
63 import org.simantics.utils.ui.workbench.WorkbenchUtils;
64 import org.simantics.workbench.internal.Activator;
65 import org.simantics.workbench.ontology.WorkbenchResource;
66 import org.simantics.workbench.search.ISearchService;
67 import org.simantics.workbench.search.QueryResult;
68 import org.simantics.workbench.search.SearchEngine;
69 import org.simantics.workbench.search.SearchQuery;
70 import org.simantics.workbench.search.SearchResult;
71 import org.simantics.workbench.search.Searching;
73 import freemarker.template.TemplateException;
76 * @author Tuukka Lehtonen
77 * @author Marko Luukkainen
79 public class SearchServiceImpl implements ISearchService{
81 private static final String SEARCH_TEMP_FILE_SUFFIX = "search.html";
82 private static final String SEARCH_TEMP_DIR = "search";
83 private static final String BROWSER_VIEW = BrowserView.ID;
84 private static final Integer MAX_RESULTS = 1000;
87 public void performQuery(SearchQuery query, ResultBrowser browserType, boolean activateResultBrowser) {
89 Set<SearchEngine> searchEngines = getSearchEngines();
91 // if query does not define used engines, use all available engines.
92 boolean containsEngines = false;
93 for (SearchEngine e : searchEngines) {
94 if (query.getSearchFlags().containsKey(e.getId())) {
95 containsEngines = true;
99 if (!containsEngines) {
100 for (SearchEngine e : searchEngines)
101 if (e.isEnabledByDefault())
102 query.setSearchFlag(e.getId(), "on");
104 } catch (DatabaseException e) {
105 ExceptionUtils.logError(e);
107 switch (browserType) {
109 performEditorQuery(query);
112 performViewQuery(query, activateResultBrowser);
118 private void performViewQuery(SearchQuery query, boolean activateResultBrowser) {
120 browserView = (BrowserView) showLocalView(
122 activateResultBrowser ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_CREATE);
123 if (browserView.getBrowser() != browserViewBrowser) {
124 browserViewBrowser = browserView.getBrowser();
125 browserViewTemporaryFiles = new ArrayList<File>();
126 browserViewTemporaryFiles.add(getNewTemporaryFile());
127 browserView.getBrowser().addLocationListener(browserViewLocationListener);
128 browserView.getBrowser().addDisposeListener(new DisposeListener() {
130 public void widgetDisposed(DisposeEvent e) {
131 browserViewBrowser = null;
135 browserView.setPartProperty(BrowserView.LAST_QUERY_PROPERTY, query.getOriginalQuery());
136 updateViewQuery(browserView, browserViewTemporaryFiles, query);
137 } catch (IOException e1) {
138 ErrorLogger.defaultLogError(e1);
139 } catch (PartInitException e1) {
140 ErrorLogger.defaultLogError(e1);
144 private IViewPart showLocalView(String id, int mode) throws PartInitException {
145 final IWorkbenchWindow wb = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
149 // Is view already created in the currently active workbench window?
150 IViewPart vp = findViewFromWindow(wb, id, false);
152 if (mode == IWorkbenchPage.VIEW_CREATE)
154 IWorkbenchPage page = vp.getViewSite().getPage();
155 if (mode == IWorkbenchPage.VIEW_VISIBLE) {
157 } else if (mode == IWorkbenchPage.VIEW_ACTIVATE) {
163 // Create the view on the active window's active page
164 IWorkbenchPage page = wb.getActivePage();
166 IWorkbenchPage pages[] = wb.getPages();
167 if (pages.length == 0) return null;
170 return page.showView(id, null, mode);
173 private static IViewPart findViewFromWindow(IWorkbenchWindow wb, String viewId, boolean restore) {
174 for (IWorkbenchPage page : wb.getPages()) {
175 IViewReference vr = page.findViewReference(viewId);
177 IViewPart vp = vr.getView(restore);
186 * There's always only one of these around.
188 BrowserView browserView = null;
190 org.eclipse.swt.browser.Browser browserViewBrowser = null;
192 List<File> browserViewTemporaryFiles = null;
194 LocationListener browserViewLocationListener = new LocationListener() {
196 public void changing(LocationEvent event) {
197 //System.out.println("changing: " + event);
199 URI newUri = new URI(event.location);
201 // Handle resource links by opening an editor for them
202 if ("resource".equals(newUri.getScheme())) {
204 openResource(getShell(event), newUri.getSchemeSpecificPart());
207 } catch (URISyntaxException e) {
208 // This URI was not needed anyway, resource: URIs will always parse properly so let
209 // it go without logging.
210 } catch (DatabaseException e) {
211 ErrorLogger.defaultLogError(e);
216 public void changed(LocationEvent event) {
217 // System.out.println("changed: " + event);
219 URL newUrl = new URL(event.location);
220 String query = newUrl.getQuery();
222 SearchQuery searchQuery = SearchQuery.decode(newUrl);
223 if (searchQuery.getOriginalQuery() != null) {
224 event.doit = updateViewQuery(
226 browserViewTemporaryFiles,
231 } catch (IOException e) {
232 ErrorLogger.defaultLogError(e);
237 class ViewQueryJob extends Job {
241 List<File> temporaryFiles;
243 BrowserView browserView;
247 public ViewQueryJob(SearchQuery query, List<File> temporaryFiles, BrowserView browserView) {
250 this.temporaryFiles = temporaryFiles;
251 this.browserView = browserView;
252 this.display = browserView.getBrowser().getDisplay();
256 protected IStatus run(IProgressMonitor monitor) {
258 SubMonitor mon = SubMonitor.convert(monitor);
259 monitor.beginTask("Performing search '" + query + "'", 10);
260 monitor.subTask("Querying index");
261 final List<QueryResult> result = createResultPage(mon.newChild(8), query, MAX_RESULTS);
262 if (result == null) {
263 return Status.CANCEL_STATUS;
266 monitor.subTask("Generating results");
267 updatePages(result, temporaryFiles);
269 updateBrowser(browserView, result.get(0));
270 return Status.OK_STATUS;
271 } catch (DatabaseException e1) {
272 return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
273 "Unexpected database problems during search result processing, see exception.", e1);
274 } catch (IOException e1) {
275 return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
276 "Unexpected I/O problems during search result processing, see exception.", e1);
282 void updateBrowser(final BrowserView browserView, final QueryResult result) {
283 if (display.isDisposed())
285 display.asyncExec(new Runnable() {
288 if (browserView.isDisposed())
292 long t1 = System.currentTimeMillis();
293 browserView.setUrl(temporaryFiles.get(0).toURI().toURL());
294 //browserView.setContentDescription("'" + query + "' - " + result.getHitCount() + " matches in active model");
295 long t2 = System.currentTimeMillis();
296 System.out.println("Updating UI " + (t2-t1) + " ms");
297 } catch (MalformedURLException e) {
298 ErrorLogger.defaultLogError(e);
305 protected boolean updateViewQuery(BrowserView browserView, List<File> temporaryFiles, SearchQuery query) {
306 ViewQueryJob job = new ViewQueryJob(query, temporaryFiles, browserView);
307 job.setRule(new ObjectIdentitySchedulingRule(browserView));
312 protected Shell getShell(LocationEvent event) {
313 if (event.widget instanceof Control) {
314 Control c = (Control) event.widget;
325 protected void performEditorQuery(final SearchQuery query) {
327 final List<File> temporaryFiles = new ArrayList<File>();
328 temporaryFiles.add(getNewTemporaryFile());
329 final BrowserInput input = createBrowserInput(temporaryFiles.get(0), query);
330 updateInput(input, temporaryFiles.get(0), query);
332 final Browser browser = (Browser) WorkbenchUtils.openEditor("org.simantics.editors.browser", input);
334 browser.getBrowser().addLocationListener(new LocationListener() {
336 public void changing(LocationEvent event) {
337 // System.out.println("changing: " + event);
339 // TODO : is there a better way to escape location?
340 URI newUri = new URI(event.location.replaceAll(" ", "%20"));
341 // Handle resource links by opening an editor for them
342 if ("resource".equals(newUri.getScheme())) {
344 openResource(getShell(event), newUri.getSchemeSpecificPart());
347 } catch (URISyntaxException e) {
348 ErrorLogger.defaultLogError(e);
349 } catch (DatabaseException e) {
350 ErrorLogger.defaultLogError(e);
355 public void changed(LocationEvent event) {
357 // OpenEditor does not seem to pass any parameters (anything after '?' character)
358 SearchQuery searchQuery = query;
359 if (searchQuery.getOriginalQuery() != null) {
360 event.doit = updateQuery(browser, input, temporaryFiles, searchQuery);
365 } catch (IOException e1) {
366 ErrorLogger.defaultLogError(e1);
367 } catch (PartInitException e1) {
368 ErrorLogger.defaultLogError(e1);
372 protected void openResource(Shell shell, String link) throws DatabaseException {
374 Session session = Simantics.getSession();
375 final long id = Long.parseLong(link);
377 Resource resource = session.syncRequest(new Read<Resource>() {
379 public Resource perform(ReadGraph graph) throws DatabaseException {
380 SerialisationSupport ss = graph.getService(SerialisationSupport.class);
381 return ss.getResource(id);
385 ISelection input = new StructuredSelection(resource);
386 String perspectiveId = WorkbenchUtils.getCurrentPerspectiveId();
388 // Try the doubleClick-extensions
389 session.asyncRequest(new ChooseActionRequest(shell, input, perspectiveId, false, true));
390 } catch (NumberFormatException e) {
398 protected static boolean updateQuery(Browser browser, BrowserInput input, List<File> temporaryFiles, final SearchQuery query) {
400 String url = browser.getBrowser().getUrl();
401 String inputUrl = input.getUrl().toString();
402 if (url.equals(inputUrl))
405 List<QueryResult> result = createResultPage(new NullProgressMonitor(), query, MAX_RESULTS);
406 updateInput(input, temporaryFiles.get(0), query);
407 updatePages(result,temporaryFiles);
408 // browser.getBrowser().setUrl(inputUrl);
409 browser.getBrowser().refresh();
410 // temporaryFile.delete();
411 for (File temporaryFile : temporaryFiles)
412 temporaryFile.deleteOnExit();
414 } catch (DatabaseException e1) {
415 ErrorLogger.defaultLogError(e1);
416 } catch (IOException e1) {
417 ErrorLogger.defaultLogError(e1);
422 private static File ensureTemporaryDirectoryExists() throws IOException {
423 return Simantics.getTemporaryDirectory(SEARCH_TEMP_DIR);
429 * @throws IOException
431 private static File getNewTemporaryFile() throws IOException {
432 return Simantics.getTempfile(SEARCH_TEMP_DIR, SEARCH_TEMP_FILE_SUFFIX);
439 * @throws IOException
441 private static BrowserInput createBrowserInput(File file, SearchQuery query) throws IOException {
442 // new BrowserInput(file.toURI().toURL()
443 return new BrowserInput(SearchQuery.encode(file, query), query.getOriginalQuery(), false, false, SWT.NONE);
446 private static void updatePages(List<QueryResult> result, List<File> temporaryFiles) throws IOException{
447 ensureTemporaryDirectoryExists();
448 for (int i = temporaryFiles.size(); i < result.size(); i++) {
449 temporaryFiles.add(getNewTemporaryFile());
451 if (result.size() > 1) {
452 createPageControls(result,temporaryFiles);
454 if (result.size() > 0) {
455 for (int i = 0; i < result.size(); i++) {
456 updatePage(temporaryFiles.get(i), result.get(i).getHtml());
459 updatePage(temporaryFiles.get(0), "");
466 * @throws IOException
468 private static void updatePage(File file, String html) throws IOException {
469 long t1 = System.currentTimeMillis();
470 FileUtils.writeFile(file, html.getBytes("UTF-8"));
471 long t2 = System.currentTimeMillis();
472 System.out.println("Writing html page took " + (t2-t1) + " ms");
475 private static void createPageControls(List<QueryResult> result, List<File> temporaryFiles) throws IOException{
476 if (result.size() == 1)
478 for (int i = 0; i < result.size(); i++) {
479 boolean first = i == 0;
480 boolean last = i == result.size() -1;
481 QueryResult current = result.get(i);
482 String pageContrtol = "<div class=\"resultInfo\">";
483 pageContrtol += "Page " + (i+1) + " of "+ + result.size() + " ";
485 pageContrtol += "First";
486 pageContrtol += " Previous";
487 pageContrtol += " <a href=\"" + temporaryFiles.get(i+1).toURI().toURL().toString() + "\">Next</a>";
488 pageContrtol += " <a href=\"" + temporaryFiles.get(result.size()-1).toURI().toURL().toString() + "\">Last</a>";
490 pageContrtol += "<a href=\"" + temporaryFiles.get(0).toURI().toURL().toString() + "\">First</a>";
491 pageContrtol += " <a href=\"" + temporaryFiles.get(i-1).toURI().toURL().toString() + "\">Previous</a>";
492 pageContrtol += " Next";
493 pageContrtol += " Last";
495 pageContrtol += "<a href=\"" + temporaryFiles.get(0).toURI().toURL().toString() + "\">First</a>";
496 pageContrtol += " <a href=\"" + temporaryFiles.get(i-1).toURI().toURL().toString() + "\">Previous</a>";
497 pageContrtol += " <a href=\"" + temporaryFiles.get(i+1).toURI().toURL().toString() + "\">Next</a>";
498 pageContrtol += " <a href=\"" + temporaryFiles.get(result.size()-1).toURI().toURL().toString() + "\">Last</a>";
500 pageContrtol += "</div><br>";
502 result.set(i, new QueryResult(current.getHeader(), pageContrtol+current.getContent()+pageContrtol,current.getFooter(),current.getHitCount()));
512 * @throws IOException
514 private static void updateInput(BrowserInput input, File file, SearchQuery query) throws IOException {
515 URL url = SearchQuery.encode(file, query);
517 input.setName(query.getOriginalQuery());
518 // System.out.println("update input: " + url + " - " + query + " - " + input);
521 private static Set<SearchEngine> getSearchEngines() throws DatabaseException{
522 return Simantics.getSession().syncRequest(new Read<Set<SearchEngine>>() {
524 public Set<SearchEngine> perform(ReadGraph graph)
525 throws DatabaseException {
526 Resource project = Simantics.peekProjectResource();
528 return Collections.emptySet();
529 return getSearchEngines(graph, project);
534 private static Set<SearchEngine> getSearchEngines(ReadGraph graph, Resource project) throws DatabaseException{
535 Set<SearchEngine> searchEngines = new HashSet<SearchEngine>();
536 Map<Resource, Set<SearchEngine>> result = getSearchEnginesByModel(graph, project);
537 for (Set<SearchEngine> set : result.values())
538 searchEngines.addAll(set);
539 return searchEngines;
543 private static Map<Resource,Set<SearchEngine>> getSearchEnginesByModel(ReadGraph graph, Resource project) throws DatabaseException{
544 WorkbenchResource WB = WorkbenchResource.getInstance(graph);
545 Layer0 L0 = Layer0.getInstance(graph);
546 Map<Resource, Set<SearchEngine>> result = new HashMap<Resource, Set<SearchEngine>>();
547 Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));
548 for (Resource model : activeModels) {
549 Set<SearchEngine> searchEngines = new HashSet<SearchEngine>();
551 List<Resource> searchFunctions = new ArrayList<Resource>();
552 searchFunctions.addAll(graph.getObjects(model, WB.HasWorkbenchSearchFunction));
553 if (searchFunctions.size() == 0) {
554 searchEngines.addAll(findSearchContributions(graph, model));
556 if (searchFunctions.isEmpty() && searchEngines.isEmpty()) {
557 // TODO : backwards compatibility, to be removed.
558 searchFunctions.addAll(graph.getObjects(project, WB.HasWorkbenchSearchFunction));
560 if (searchFunctions.isEmpty() && searchEngines.isEmpty())
561 searchFunctions.add( WB.DependenciesSearchFunction);
563 for (Resource searchFunction : searchFunctions) {
564 @SuppressWarnings("rawtypes")
565 Function f = graph.adapt(searchFunction, Function.class);
566 String id = graph.getURI(searchFunction);
567 String name = graph.getPossibleRelatedValue2(searchFunction, L0.HasLabel);
568 @SuppressWarnings("unchecked")
569 SearchEngine engine = new SearchEngine(id,name,(Function5<IProgressMonitor, ReadGraph, Resource, SearchQuery, Integer, SearchResult>)f, true);
571 engine.addSupportedParam(Dependencies.FIELD_NAME_SEARCH, "Name");
572 engine.addSupportedParam(Dependencies.FIELD_TYPES_SEARCH, "Types");
574 searchEngines.add(engine);
576 result.put(model, searchEngines);
587 * @throws DatabaseException
589 private static List<QueryResult> createResultPage(final IProgressMonitor monitor, final SearchQuery query, final int maxResults) throws DatabaseException {
590 return Simantics.getSession().syncRequest(new Read<List<QueryResult>>() {
592 public List<QueryResult> perform(ReadGraph graph) throws DatabaseException {
593 Resource project = Simantics.peekProjectResource();
597 Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));
598 //if (activeModels.isEmpty())
600 long t1 = System.currentTimeMillis();
601 Set<SearchEngine> searchEngines = new HashSet<SearchEngine>();
602 MapList<Resource, Pair<SearchEngine,SearchResult>> searchResults = new MapList<Resource, Pair<SearchEngine,SearchResult>>();
603 Map<Resource,Set<SearchEngine>> searchEngineMap = getSearchEnginesByModel(graph, project);
605 for (Resource model : activeModels) {
606 for (SearchEngine engine : searchEngineMap.get(model)) {
608 if (query.getBooleanFlag(engine.getId())) {
609 SearchResult result = engine.getSearchFunction().apply(monitor, graph, model, query, maxResults);
610 if (!result.isEmpty()) {
611 searchResults.add(model, new Pair<SearchEngine,SearchResult>(engine, result));
614 searchEngines.add(engine);
616 // searchResults.put(model, result);
618 long t2 = System.currentTimeMillis();
619 System.out.println("Running search queries took " + (t2-t1) + " ms for query '" + query + "'");
622 return Searching.generatePage(graph, searchEngines,query, maxResults, searchResults);
623 } catch (IOException e) {
624 Activator.logError("I/O problem while generating search result page.", e);
625 } catch (TemplateException e) {
626 Activator.logError("Template definition problem in search result page generation. Please inform the developers.", e);
633 private static Collection<SearchEngine> findSearchContributions(ReadGraph graph, Resource model) throws DatabaseException {
634 WorkbenchResource WB = WorkbenchResource.getInstance(graph);
635 Instances contributionFinder = graph.adapt(WB.SearchContribution, Instances.class);
636 Resource index = model;
637 List<SearchEngine> result = new ArrayList<SearchEngine>();
638 for (Resource r : contributionFinder.find(graph, index)) {
639 SearchEngine engine = contributionToEngine(graph, r);
646 private static SearchEngine contributionToEngine(ReadGraph graph, Resource searchContribution) throws DatabaseException {
647 Layer0 L0 = Layer0.getInstance(graph);
648 WorkbenchResource WB = WorkbenchResource.getInstance(graph);
650 Resource searchFunction = graph.getPossibleObject(searchContribution, WB.hasSearchFunction);
651 if (searchFunction == null)
654 @SuppressWarnings("rawtypes")
655 Function f = graph.adapt(searchFunction, Function.class);
656 String id = graph.getURI(searchFunction);
657 String name = graph.getPossibleRelatedValue2(searchFunction, L0.HasLabel);
659 Boolean enabledByDefault = graph.getPossibleRelatedValue2(searchContribution, WB.SearchContribution_isEnabledByDefault, Bindings.BOOLEAN);
660 boolean enabled = !Boolean.FALSE.equals(enabledByDefault);
662 @SuppressWarnings("unchecked")
663 SearchEngine engine = new SearchEngine(id,name,(Function5<IProgressMonitor, ReadGraph, Resource, SearchQuery, Integer, SearchResult>)f, enabled);
665 engine.addSupportedParam(Dependencies.FIELD_NAME_SEARCH, "Name");
666 engine.addSupportedParam(Dependencies.FIELD_TYPES_SEARCH, "Types");