View Javadoc
1   /*
2    * Copyright (C) 2007-2012 Argeo GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.argeo.slc.client.ui.dist.editors;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import javax.jcr.Node;
24  import javax.jcr.NodeIterator;
25  import javax.jcr.Property;
26  import javax.jcr.PropertyType;
27  import javax.jcr.RepositoryException;
28  import javax.jcr.Session;
29  import javax.jcr.nodetype.NodeType;
30  import javax.jcr.query.QueryManager;
31  import javax.jcr.query.QueryResult;
32  import javax.jcr.query.qom.Constraint;
33  import javax.jcr.query.qom.DynamicOperand;
34  import javax.jcr.query.qom.Ordering;
35  import javax.jcr.query.qom.QueryObjectModel;
36  import javax.jcr.query.qom.QueryObjectModelFactory;
37  import javax.jcr.query.qom.Selector;
38  import javax.jcr.query.qom.StaticOperand;
39  
40  import org.argeo.cms.ui.workbench.util.CommandUtils;
41  import org.argeo.cms.ui.workbench.util.PrivilegedJob;
42  import org.argeo.eclipse.ui.EclipseJcrMonitor;
43  import org.argeo.jcr.JcrMonitor;
44  import org.argeo.jcr.JcrUtils;
45  import org.argeo.slc.SlcException;
46  import org.argeo.slc.SlcNames;
47  import org.argeo.slc.SlcTypes;
48  import org.argeo.slc.client.ui.dist.DistConstants;
49  import org.argeo.slc.client.ui.dist.DistPlugin;
50  import org.argeo.slc.client.ui.dist.commands.DeleteArtifacts;
51  import org.argeo.slc.client.ui.dist.commands.OpenModuleEditor;
52  import org.argeo.slc.client.ui.dist.utils.DistNodeViewerComparator;
53  import org.eclipse.core.runtime.IProgressMonitor;
54  import org.eclipse.core.runtime.IStatus;
55  import org.eclipse.core.runtime.Status;
56  import org.eclipse.jface.action.IMenuListener;
57  import org.eclipse.jface.action.IMenuManager;
58  import org.eclipse.jface.action.MenuManager;
59  import org.eclipse.jface.viewers.ColumnLabelProvider;
60  import org.eclipse.jface.viewers.DoubleClickEvent;
61  import org.eclipse.jface.viewers.IDoubleClickListener;
62  import org.eclipse.jface.viewers.IStructuredContentProvider;
63  import org.eclipse.jface.viewers.IStructuredSelection;
64  import org.eclipse.jface.viewers.TableViewer;
65  import org.eclipse.jface.viewers.TableViewerColumn;
66  import org.eclipse.jface.viewers.Viewer;
67  import org.eclipse.swt.SWT;
68  import org.eclipse.swt.events.ModifyEvent;
69  import org.eclipse.swt.events.ModifyListener;
70  import org.eclipse.swt.events.SelectionAdapter;
71  import org.eclipse.swt.events.SelectionEvent;
72  import org.eclipse.swt.layout.FillLayout;
73  import org.eclipse.swt.layout.GridData;
74  import org.eclipse.swt.layout.GridLayout;
75  import org.eclipse.swt.widgets.Composite;
76  import org.eclipse.swt.widgets.Display;
77  import org.eclipse.swt.widgets.Label;
78  import org.eclipse.swt.widgets.Menu;
79  import org.eclipse.swt.widgets.Table;
80  import org.eclipse.swt.widgets.Text;
81  import org.eclipse.ui.IWorkbenchWindow;
82  import org.eclipse.ui.forms.IManagedForm;
83  import org.eclipse.ui.forms.editor.FormPage;
84  import org.eclipse.ui.forms.widgets.FormToolkit;
85  import org.eclipse.ui.forms.widgets.ScrolledForm;
86  import org.eclipse.ui.forms.widgets.Section;
87  
88  /** Show all bundles contained in a given workspace as filter-able table */
89  public class DistWkspSearchPage extends FormPage implements SlcNames {
90  	// final private static Log log = LogFactory
91  	// .getLog(DistributionOverviewPage.class);
92  
93  	final static String PAGE_ID = "distributionOverviewPage";
94  
95  	// Business Objects
96  	private Session session;
97  
98  	// This page widgets
99  	private DistWorkspaceEditor formEditor;
100 	private FormToolkit tk;
101 
102 	private DistNodeViewerComparator comparator;
103 	private TableViewer viewer;
104 
105 	// private Composite header;
106 	private Text artifactTxt;
107 	private final static String FILTER_HELP_MSG = "Filter criterion, separated by a space";
108 
109 	public DistWkspSearchPage(DistWorkspaceEditor formEditor, String title,
110 			Session session) {
111 		super(formEditor, PAGE_ID, title);
112 		this.formEditor = formEditor;
113 		this.session = session;
114 	}
115 
116 	private void asynchronousRefresh() {
117 		RefreshJob job = new RefreshJob(artifactTxt.getText(), viewer,
118 				getSite().getShell().getDisplay());
119 		job.setUser(true);
120 		job.schedule();
121 	}
122 
123 	private class RefreshJob extends PrivilegedJob {
124 		private TableViewer viewer;
125 		private String filter;
126 		private Display display;
127 
128 		public RefreshJob(String filter, TableViewer viewer, Display display) {
129 			super("Get bundle list");
130 			this.filter = filter;
131 			this.viewer = viewer;
132 			this.display = display;
133 		}
134 
135 		@Override
136 		protected IStatus doRun(IProgressMonitor progressMonitor) {
137 			try {
138 				JcrMonitor monitor = new EclipseJcrMonitor(progressMonitor);
139 				monitor.beginTask("Getting bundle list", -1);
140 				final List<Node> result = JcrUtils
141 						.nodeIteratorToList(listBundleArtifacts(session, filter));
142 
143 				display.asyncExec(new Runnable() {
144 					public void run() {
145 						viewer.setInput(result);
146 					}
147 				});
148 			} catch (Exception e) {
149 				return new Status(IStatus.ERROR, DistPlugin.PLUGIN_ID,
150 						"Cannot get bundle list", e);
151 			}
152 			return Status.OK_STATUS;
153 		}
154 	}
155 
156 	@Override
157 	protected void createFormContent(IManagedForm managedForm) {
158 		ScrolledForm form = managedForm.getForm();
159 		tk = managedForm.getToolkit();
160 
161 		// Main Layout
162 		GridLayout layout = new GridLayout(1, false);
163 		Composite body = form.getBody();
164 		body.setLayout(layout);
165 
166 		// Meta info about current workspace
167 		Composite header = tk.createComposite(body);
168 		header.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
169 		createHeaderPart(form, header);
170 
171 		Composite modules = tk.createComposite(body);
172 		modules.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
173 		populateModuleSection(modules);
174 	}
175 
176 	private void createHeaderPart(ScrolledForm form, Composite parent) {
177 		GridLayout layout = new GridLayout(4, false);
178 		// layout.marginWidth = layout.marginHeight = layout.verticalSpacing =
179 		// 0;
180 		// layout.horizontalSpacing = 2;
181 		parent.setLayout(layout);
182 
183 		String wkspName = ((DistWkspEditorInput) getEditorInput())
184 				.getWorkspaceName();
185 		wkspName = wkspName.replaceAll("-", " ");
186 		form.setText(wkspName);
187 
188 		String repoAlias = "";
189 		Node repoNode = ((DistWorkspaceEditor) getEditor()).getRepoNode();
190 		if (repoNode != null)
191 			try {
192 				repoAlias = repoNode.isNodeType(NodeType.MIX_TITLE) ? repoNode
193 						.getProperty(Property.JCR_TITLE).getString() : repoNode
194 						.getName();
195 			} catch (RepositoryException e1) {
196 				throw new SlcException("Unable to get repository alias ", e1);
197 			}
198 		else
199 			repoAlias = " - ";
200 
201 		createLT(parent, "Repository alias", repoAlias);
202 		createLT(parent, "URI",
203 				((DistWkspEditorInput) getEditorInput()).getUri());
204 	}
205 
206 	private void populateModuleSection(Composite parent) {
207 		GridLayout layout = new GridLayout(1, false);
208 		layout.marginWidth = layout.horizontalSpacing = layout.horizontalSpacing = layout.marginHeight = 0;
209 		parent.setLayout(layout);
210 
211 		Section section = tk.createSection(parent, Section.TITLE_BAR
212 				| Section.DESCRIPTION);
213 		section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
214 
215 		section.setText("Artifacts");
216 		section.setDescription("Search among all artifacts that are referenced in the current workspace");
217 		section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
218 
219 		Composite body = tk.createComposite(section);
220 		layout = new GridLayout(1, false);
221 		layout.marginWidth = layout.horizontalSpacing = layout.horizontalSpacing = layout.marginHeight = 0;
222 		body.setLayout(new GridLayout());
223 
224 		// Filter
225 		Composite filter = tk.createComposite(body);
226 		filter.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
227 		createFilterPart(filter);
228 
229 		// Table
230 		Composite tableCmp = tk.createComposite(body);
231 		tableCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
232 		createTableViewer(tableCmp);
233 
234 		section.setClient(body);
235 	}
236 
237 	/** Build repository request */
238 	private NodeIterator listBundleArtifacts(Session session, String filter)
239 			throws RepositoryException {
240 		QueryManager queryManager = session.getWorkspace().getQueryManager();
241 		QueryObjectModelFactory factory = queryManager.getQOMFactory();
242 
243 		final String bundleArtifactsSelector = "bundleArtifacts";
244 		Selector source = factory.selector(SlcTypes.SLC_BUNDLE_ARTIFACT,
245 				bundleArtifactsSelector);
246 
247 		// Create a dynamic operand for each property on which we want to filter
248 		DynamicOperand symbNameDO = factory.propertyValue(
249 				source.getSelectorName(), SlcNames.SLC_SYMBOLIC_NAME);
250 		DynamicOperand versionDO = factory.propertyValue(
251 				source.getSelectorName(), SlcNames.SLC_BUNDLE_VERSION);
252 		DynamicOperand nameDO = factory.propertyValue(source.getSelectorName(),
253 				DistConstants.SLC_BUNDLE_NAME);
254 
255 		// Default Constraint: no source artifacts
256 		Constraint defaultC = factory.not(factory.comparison(
257 				symbNameDO,
258 				QueryObjectModelFactory.JCR_OPERATOR_LIKE,
259 				factory.literal(session.getValueFactory().createValue(
260 						"%.source"))));
261 
262 		// Build constraints based the textArea content
263 		if (filter != null && !"".equals(filter.trim())) {
264 			// Parse the String
265 			String[] strs = filter.trim().split(" ");
266 			for (String token : strs) {
267 				token = token.replace('*', '%');
268 				StaticOperand so = factory.literal(session.getValueFactory()
269 						.createValue("%" + token + "%"));
270 
271 				Constraint currC = factory.comparison(symbNameDO,
272 						QueryObjectModelFactory.JCR_OPERATOR_LIKE, so);
273 				currC = factory.or(currC, factory.comparison(versionDO,
274 						QueryObjectModelFactory.JCR_OPERATOR_LIKE, so));
275 				currC = factory.or(currC, factory.comparison(nameDO,
276 						QueryObjectModelFactory.JCR_OPERATOR_LIKE, so));
277 
278 				defaultC = factory.and(defaultC, currC);
279 			}
280 		}
281 
282 		Ordering order = factory.descending(factory.propertyValue(
283 				bundleArtifactsSelector, SlcNames.SLC_BUNDLE_VERSION));
284 		Ordering order2 = factory.ascending(factory.propertyValue(
285 				bundleArtifactsSelector, SlcNames.SLC_SYMBOLIC_NAME));
286 		Ordering[] orderings = { order, order2 };
287 
288 		QueryObjectModel query = factory.createQuery(source, defaultC,
289 				orderings, null);
290 
291 		QueryResult result = query.execute();
292 		return result.getNodes();
293 
294 	}
295 
296 	private Text createLT(Composite parent, String labelValue, String textValue) {
297 		Label label = new Label(parent, SWT.RIGHT);
298 		label.setText(labelValue);
299 		// label.setFont(EclipseUiUtils.getBoldFont(parent));
300 		label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
301 
302 		// Add a trailing space to workaround a display glitch in RAP 1.3
303 		Text text = new Text(parent, SWT.LEFT); // | SWT.BORDER
304 		text.setText(textValue + " ");
305 		text.setEditable(false);
306 		return text;
307 	}
308 
309 	private void createFilterPart(Composite parent) {
310 		GridLayout layout = new GridLayout();
311 		layout.marginWidth = layout.marginHeight = layout.verticalSpacing = 0;
312 		layout.horizontalSpacing = 5;
313 		parent.setLayout(layout);
314 
315 		// Text Area to filter
316 		artifactTxt = tk.createText(parent, "", SWT.BORDER | SWT.SINGLE
317 				| SWT.SEARCH | SWT.CANCEL);
318 		artifactTxt.setMessage(FILTER_HELP_MSG);
319 		GridData gd = new GridData(SWT.FILL, SWT.FILL, false, false);
320 		gd.grabExcessHorizontalSpace = true;
321 		artifactTxt.setLayoutData(gd);
322 		artifactTxt.addModifyListener(new ModifyListener() {
323 			private static final long serialVersionUID = -2422321852703208573L;
324 
325 			public void modifyText(ModifyEvent event) {
326 				if ("".equals(artifactTxt.getText().trim()))
327 					asynchronousRefresh();
328 				else
329 					refreshFilteredList();
330 			}
331 		});
332 	}
333 
334 	private void refreshFilteredList() {
335 		List<Node> nodes;
336 		try {
337 			nodes = JcrUtils.nodeIteratorToList(listBundleArtifacts(session,
338 					artifactTxt.getText()));
339 			viewer.setInput(nodes);
340 		} catch (RepositoryException e) {
341 			throw new SlcException("Unable to list bundles", e);
342 		}
343 	}
344 
345 	private void createTableViewer(Composite parent) {
346 		parent.setLayout(new FillLayout());
347 		// helpers to enable sorting by column
348 		List<String> propertiesList = new ArrayList<String>();
349 		List<Integer> propertyTypesList = new ArrayList<Integer>();
350 
351 		// Define the TableViewer
352 		viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
353 				| SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
354 
355 		// Name
356 		TableViewerColumn col = new TableViewerColumn(viewer, SWT.NONE);
357 		col.getColumn().setWidth(300);
358 		col.getColumn().setText("Name");
359 		col.setLabelProvider(new ColumnLabelProvider() {
360 			private static final long serialVersionUID = -760226161605987538L;
361 
362 			@Override
363 			public String getText(Object element) {
364 				return JcrUtils.get((Node) element,
365 						DistConstants.SLC_BUNDLE_NAME);
366 			}
367 		});
368 		col.getColumn().addSelectionListener(getSelectionAdapter(0));
369 		propertiesList.add(DistConstants.SLC_BUNDLE_NAME);
370 		propertyTypesList.add(PropertyType.STRING);
371 
372 		// Symbolic name
373 		col = new TableViewerColumn(viewer, SWT.V_SCROLL);
374 		col.getColumn().setWidth(300);
375 		col.getColumn().setText("Symbolic Name");
376 		col.setLabelProvider(new ColumnLabelProvider() {
377 			private static final long serialVersionUID = 4431447542158431355L;
378 
379 			@Override
380 			public String getText(Object element) {
381 				return JcrUtils.get((Node) element, SLC_SYMBOLIC_NAME);
382 			}
383 		});
384 		col.getColumn().addSelectionListener(getSelectionAdapter(1));
385 		propertiesList.add(SLC_SYMBOLIC_NAME);
386 		propertyTypesList.add(PropertyType.STRING);
387 
388 		// Version
389 		col = new TableViewerColumn(viewer, SWT.NONE);
390 		col.getColumn().setWidth(130);
391 		col.getColumn().setText("Version");
392 		col.setLabelProvider(new ColumnLabelProvider() {
393 			private static final long serialVersionUID = -5616215547236158504L;
394 
395 			@Override
396 			public String getText(Object element) {
397 				return JcrUtils.get((Node) element, SLC_BUNDLE_VERSION);
398 			}
399 		});
400 		col.getColumn().addSelectionListener(getSelectionAdapter(2));
401 		propertiesList.add(SLC_BUNDLE_VERSION);
402 		propertyTypesList.add(DistNodeViewerComparator.VERSION_TYPE);
403 
404 		final Table table = viewer.getTable();
405 		table.setHeaderVisible(true);
406 		table.setLinesVisible(true);
407 
408 		viewer.setContentProvider(new DistributionsContentProvider());
409 		getSite().setSelectionProvider(viewer);
410 
411 		comparator = new DistNodeViewerComparator(2,
412 				DistNodeViewerComparator.ASCENDING, propertiesList,
413 				propertyTypesList);
414 		viewer.setComparator(comparator);
415 
416 		// Context Menu
417 		MenuManager menuManager = new MenuManager();
418 		Menu menu = menuManager.createContextMenu(viewer.getTable());
419 		menuManager.addMenuListener(new IMenuListener() {
420 			private static final long serialVersionUID = -3886983092940055195L;
421 
422 			public void menuAboutToShow(IMenuManager manager) {
423 				contextMenuAboutToShow(manager);
424 			}
425 		});
426 		viewer.getTable().setMenu(menu);
427 		getSite().registerContextMenu(menuManager, viewer);
428 
429 		// Double click
430 		viewer.addDoubleClickListener(new DoubleClickListener());
431 	}
432 
433 	@Override
434 	public void setFocus() {
435 		viewer.getTable().setFocus();
436 	}
437 
438 	/** force refresh of the artifact list */
439 	public void refresh() {
440 		asynchronousRefresh();
441 	}
442 
443 	/** Programmatically configure the context menu */
444 	protected void contextMenuAboutToShow(IMenuManager menuManager) {
445 		IWorkbenchWindow window = DistPlugin.getDefault().getWorkbench()
446 				.getActiveWorkbenchWindow();
447 		// Build conditions
448 		// Delete selected artifacts
449 		CommandUtils.refreshCommand(menuManager, window, DeleteArtifacts.ID,
450 				DeleteArtifacts.DEFAULT_LABEL, DeleteArtifacts.DEFAULT_ICON,
451 				true);
452 	}
453 
454 	private SelectionAdapter getSelectionAdapter(final int index) {
455 		SelectionAdapter selectionAdapter = new SelectionAdapter() {
456 			private static final long serialVersionUID = 5515884441510882460L;
457 
458 			@Override
459 			public void widgetSelected(SelectionEvent e) {
460 				Table table = viewer.getTable();
461 				comparator.setColumn(index);
462 				int dir = table.getSortDirection();
463 				if (table.getSortColumn() == table.getColumn(index)) {
464 					dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
465 				} else {
466 					dir = SWT.DOWN;
467 				}
468 				table.setSortDirection(dir);
469 				table.setSortColumn(table.getColumn(index));
470 				viewer.refresh();
471 			}
472 		};
473 		return selectionAdapter;
474 	}
475 
476 	/* LOCAL CLASSES */
477 	private class DistributionsContentProvider implements
478 			IStructuredContentProvider {
479 		private static final long serialVersionUID = -635451814876234147L;
480 
481 		// we keep a cache of the Nodes in the content provider to be able to
482 		// manage long request
483 		private List<Node> nodes;
484 
485 		public void dispose() {
486 		}
487 
488 		// We expect a list of nodes as a new input
489 		@SuppressWarnings("unchecked")
490 		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
491 			nodes = (List<Node>) newInput;
492 		}
493 
494 		public Object[] getElements(Object arg0) {
495 			return nodes.toArray();
496 		}
497 	}
498 
499 	private class DoubleClickListener implements IDoubleClickListener {
500 
501 		public void doubleClick(DoubleClickEvent event) {
502 			Object obj = ((IStructuredSelection) event.getSelection())
503 					.getFirstElement();
504 			if (obj instanceof Node) {
505 				Node node = (Node) obj;
506 				try {
507 					if (node.isNodeType(SlcTypes.SLC_ARTIFACT)) {
508 						DistWkspEditorInput dwip = (DistWkspEditorInput) formEditor
509 								.getEditorInput();
510 						Map<String, String> params = new HashMap<String, String>();
511 						params.put(OpenModuleEditor.PARAM_REPO_NODE_PATH,
512 								dwip.getRepoNodePath());
513 						params.put(OpenModuleEditor.PARAM_REPO_URI,
514 								dwip.getUri());
515 						params.put(OpenModuleEditor.PARAM_WORKSPACE_NAME,
516 								dwip.getWorkspaceName());
517 						String path = node.getPath();
518 						params.put(OpenModuleEditor.PARAM_MODULE_PATH, path);
519 						CommandUtils.callCommand(OpenModuleEditor.ID, params);
520 					}
521 				} catch (RepositoryException re) {
522 					throw new SlcException("Cannot get path for node " + node
523 							+ " while setting parameters for "
524 							+ "command OpenModuleEditor", re);
525 				}
526 
527 			}
528 		}
529 	}
530 }