View Javadoc
1   package org.argeo.connect.e4.resources.parts;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   
6   import javax.jcr.Node;
7   import javax.jcr.Property;
8   import javax.jcr.PropertyType;
9   import javax.jcr.RepositoryException;
10  import javax.jcr.Session;
11  import javax.jcr.query.QueryManager;
12  import javax.jcr.query.QueryResult;
13  import javax.jcr.query.Row;
14  import javax.jcr.query.RowIterator;
15  import javax.jcr.query.qom.Constraint;
16  import javax.jcr.query.qom.DynamicOperand;
17  import javax.jcr.query.qom.Ordering;
18  import javax.jcr.query.qom.QueryObjectModel;
19  import javax.jcr.query.qom.QueryObjectModelConstants;
20  import javax.jcr.query.qom.QueryObjectModelFactory;
21  import javax.jcr.query.qom.Selector;
22  import javax.jcr.query.qom.StaticOperand;
23  
24  import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
25  import org.argeo.connect.ConnectException;
26  import org.argeo.connect.e4.parts.AbstractConnectEditor;
27  import org.argeo.connect.resources.ResourcesService;
28  import org.argeo.connect.ui.ConnectColumnDefinition;
29  import org.argeo.connect.ui.ConnectUiConstants;
30  import org.argeo.connect.ui.ConnectUiSnippets;
31  import org.argeo.connect.ui.ConnectUiUtils;
32  import org.argeo.connect.ui.SystemWorkbenchService;
33  import org.argeo.connect.ui.parts.AbstractPanelFormPart;
34  import org.argeo.connect.ui.util.LazyCTabControl;
35  import org.argeo.connect.ui.util.TitleIconRowLP;
36  import org.argeo.connect.ui.util.VirtualJcrTableViewer;
37  import org.argeo.connect.util.ConnectJcrUtils;
38  import org.argeo.connect.util.ConnectUtils;
39  import org.argeo.eclipse.ui.EclipseUiUtils;
40  import org.argeo.eclipse.ui.dialogs.SingleValue;
41  import org.argeo.eclipse.ui.utils.ViewerUtils;
42  import org.eclipse.jface.dialogs.MessageDialog;
43  import org.eclipse.jface.viewers.ColumnLabelProvider;
44  import org.eclipse.jface.viewers.DoubleClickEvent;
45  import org.eclipse.jface.viewers.IDoubleClickListener;
46  import org.eclipse.jface.viewers.ILazyContentProvider;
47  import org.eclipse.jface.viewers.ISelectionChangedListener;
48  import org.eclipse.jface.viewers.IStructuredContentProvider;
49  import org.eclipse.jface.viewers.IStructuredSelection;
50  import org.eclipse.jface.viewers.SelectionChangedEvent;
51  import org.eclipse.jface.viewers.TableViewer;
52  import org.eclipse.jface.viewers.TableViewerColumn;
53  import org.eclipse.jface.viewers.Viewer;
54  import org.eclipse.jface.window.Window;
55  import org.eclipse.jface.wizard.Wizard;
56  import org.eclipse.jface.wizard.WizardDialog;
57  import org.eclipse.jface.wizard.WizardPage;
58  import org.eclipse.rap.rwt.RWT;
59  import org.eclipse.swt.SWT;
60  import org.eclipse.swt.events.SelectionAdapter;
61  import org.eclipse.swt.events.SelectionEvent;
62  import org.eclipse.swt.layout.GridData;
63  import org.eclipse.swt.layout.GridLayout;
64  import org.eclipse.swt.widgets.Button;
65  import org.eclipse.swt.widgets.Composite;
66  import org.eclipse.swt.widgets.Table;
67  import org.eclipse.swt.widgets.Text;
68  
69  /**
70   * A composite to include in a form and that enables edition of the values of a
71   * catalog from a given template.
72   * 
73   * This might be extended to provide a specific request that will limit the
74   * perimeter on which the current catalog might apply (typically, if we use two
75   * distinct nomenclature for a same property that are bound to a given projects)
76   */
77  public class TemplateValueCatalogue extends LazyCTabControl {
78  	private static final long serialVersionUID = -5018569293721397600L;
79  	public final static String CTAB_ID = "org.argeo.connect.ui.ctab.templateValueCatalogue";
80  
81  	// Context
82  	private final ResourcesService resourcesService;
83  	private final SystemWorkbenchService systemWorkbenchService;
84  	private final Node templateNode;
85  	private final String propertyName;
86  	private final String taggableType;
87  
88  	// UI Context private final FormToolkit toolkit;
89  	private final AbstractConnectEditor editor;
90  	private MyFormPart myFormPart;
91  
92  	public TemplateValueCatalogue(Composite parent, int style, AbstractConnectEditor editor,
93  			ResourcesService resourcesService, SystemWorkbenchService systemWorkbenchService, Node templateNode,
94  			String propertyName, String taggableType) {
95  		super(parent, style);
96  		this.editor = editor;
97  		this.resourcesService = resourcesService;
98  		this.systemWorkbenchService = systemWorkbenchService;
99  		this.templateNode = templateNode;
100 		this.propertyName = propertyName;
101 		this.taggableType = taggableType;
102 	}
103 
104 	@Override
105 	public void refreshPartControl() {
106 		myFormPart.refresh();
107 		layout(true, true);
108 	}
109 
110 	@Override
111 	public void createPartControl(Composite parent) {
112 
113 		parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
114 		myFormPart = new MyFormPart(this);
115 		myFormPart.initialize(editor.getManagedForm());
116 		editor.getManagedForm().addPart(myFormPart);
117 	}
118 
119 	private class MyFormPart extends AbstractPanelFormPart {
120 		private TableViewer valuesViewer;
121 		private TableViewer instancesViewer;
122 
123 		public MyFormPart(Composite parent) {
124 			super(parent, editor, templateNode);
125 		}
126 
127 		protected void reCreateChildComposite(Composite panel, Node editionInfo) {
128 			// Add button if needed
129 			Button addBtn = null;
130 			if (isEditing()) {
131 				panel.setLayout(new GridLayout());
132 				addBtn = new Button(panel, SWT.PUSH);
133 				addBtn.setText("Add a value");
134 				configureAddValueBtn(this, addBtn);
135 			} else {
136 				GridLayout gl = EclipseUiUtils.noSpaceGridLayout();
137 				gl.verticalSpacing = 5;
138 				panel.setLayout(gl);
139 			}
140 
141 			// Item list
142 			Composite valuesCmp = new Composite(panel, SWT.NO_FOCUS);
143 			GridData gd = EclipseUiUtils.fillWidth();
144 			gd.heightHint = 150;
145 			valuesCmp.setLayoutData(gd);
146 			valuesViewer = createValuesViewer(valuesCmp);
147 			valuesViewer.setContentProvider(new ValuesTableCP());
148 			valuesViewer.getTable().addSelectionListener(new MyEditRemoveAdapter());
149 
150 			Composite instancesCmp = new Composite(panel, SWT.NO_FOCUS);
151 			instancesCmp.setLayoutData(EclipseUiUtils.fillAll());
152 			instancesViewer = createInstancesViewer(instancesCmp);
153 			instancesViewer.setContentProvider(new InstancesTableCP(instancesViewer));
154 			instancesViewer.addDoubleClickListener(new InstanceDClickAdapter());
155 
156 			// enables update of the bottom table when one of the value is
157 			// selected
158 			valuesViewer.addSelectionChangedListener(new ISelectionChangedListener() {
159 				@Override
160 				public void selectionChanged(SelectionChangedEvent event) {
161 					IStructuredSelection selection = (IStructuredSelection) event.getSelection();
162 					if (!selection.isEmpty()) {
163 						String currSelected = (String) selection.getFirstElement();
164 						RowIterator rit = query(currSelected);
165 						setViewerInput(instancesViewer, ConnectJcrUtils.rowIteratorToArray(rit));
166 					}
167 				}
168 			});
169 
170 			refreshContent(panel, editionInfo);
171 		}
172 
173 		protected void refreshContent(Composite parent, Node editionInfo) {
174 			try {
175 				valuesViewer
176 						.setInput(ConnectJcrUtils.getMultiAsList(templateNode, propertyName).toArray(new String[0]));
177 				valuesViewer.refresh();
178 				setViewerInput(instancesViewer, null);
179 				if (editionInfo.getSession().hasPendingChanges())
180 					MyFormPart.this.markDirty();
181 			} catch (RepositoryException e) {
182 				throw new ConnectException("unable to occurrences for " + editionInfo, e);
183 			}
184 		}
185 	}
186 
187 	/**
188 	 * Retrieves all instances of the repository that have this value, overwrite to
189 	 * provide a more relevant request
190 	 */
191 	protected RowIterator query(String currVal) {
192 		try {
193 			Session session = templateNode.getSession();
194 			QueryManager queryManager = session.getWorkspace().getQueryManager();
195 
196 			QueryObjectModelFactory factory = queryManager.getQOMFactory();
197 			Selector source = factory.selector(taggableType, taggableType);
198 
199 			StaticOperand so = factory.literal(session.getValueFactory().createValue(currVal));
200 			DynamicOperand dyo = factory.propertyValue(source.getSelectorName(), propertyName);
201 			Constraint constraint = factory.comparison(dyo, QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO, so);
202 
203 			Ordering order = factory
204 					.ascending(factory.upperCase(factory.propertyValue(source.getSelectorName(), Property.JCR_TITLE)));
205 			Ordering[] orderings = { order };
206 			QueryObjectModel query = factory.createQuery(source, constraint, orderings, null);
207 
208 			QueryResult result = query.execute();
209 			return result.getRows();
210 		} catch (RepositoryException e) {
211 			throw new ConnectException("Unable to list entities with property " + currVal + " for property "
212 					+ propertyName + " of " + templateNode, e);
213 		}
214 	}
215 
216 	/** Displays existing values for this property */
217 	private TableViewer createValuesViewer(Composite parent) {
218 		parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
219 		final Table table = new Table(parent, SWT.SINGLE | SWT.V_SCROLL | SWT.H_SCROLL);
220 		table.setHeaderVisible(true);
221 		table.setLinesVisible(true);
222 		table.setLayoutData(EclipseUiUtils.fillAll());
223 		table.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
224 		table.setData(RWT.CUSTOM_ITEM_HEIGHT, Integer.valueOf(24));
225 
226 		TableViewer viewer = new TableViewer(table);
227 
228 		TableViewerColumn col = ViewerUtils.createTableViewerColumn(viewer, "Name", SWT.NONE, 400);
229 		col.setLabelProvider(new ColumnLabelProvider() {
230 			private static final long serialVersionUID = 1L;
231 
232 			@Override
233 			public String getText(Object element) {
234 				return ConnectUtils.replaceAmpersand((String) element);
235 			}
236 		});
237 
238 		if (editor.isEditing()) {
239 			col = ViewerUtils.createTableViewerColumn(viewer, "", SWT.NONE, 80);
240 			col.setLabelProvider(new ColumnLabelProvider() {
241 				private static final long serialVersionUID = 1L;
242 
243 				@Override
244 				public String getText(Object element) {
245 					String value = ConnectUtils.replaceAmpersand((String) element);
246 					String editLink = ConnectUiSnippets.getRWTLink(
247 							ConnectUiConstants.CRUD_EDIT + ConnectUiConstants.HREF_SEPARATOR + value,
248 							ConnectUiConstants.CRUD_EDIT);
249 					String removeLink = ConnectUiSnippets.getRWTLink(
250 							ConnectUiConstants.CRUD_DELETE + ConnectUiConstants.HREF_SEPARATOR + value,
251 							ConnectUiConstants.CRUD_DELETE);
252 					return editLink + ConnectUiConstants.NB_DOUBLE_SPACE + removeLink;
253 				}
254 			});
255 		}
256 		return viewer;
257 	}
258 
259 	private class MyEditRemoveAdapter extends SelectionAdapter {
260 		private static final long serialVersionUID = 1L;
261 
262 		public MyEditRemoveAdapter() {
263 		}
264 
265 		public void widgetSelected(SelectionEvent event) {
266 			if (event.detail == RWT.HYPERLINK) {
267 				String href = event.text;
268 				String[] val = href.split(ConnectUiConstants.HREF_SEPARATOR);
269 				EditValueWizard wizard = new EditValueWizard(val[0], val[1]);
270 				WizardDialog dialog = new WizardDialog(TemplateValueCatalogue.this.getShell(), wizard);
271 				// NoProgressBarWizardDialog dialog = new
272 				// NoProgressBarWizardDialog(TemplateValueCatalogue.this.getShell(),
273 				// wizard);
274 
275 				if (Window.OK == dialog.open()) {
276 					try {
277 						// Session is not saved when no object is linked to this
278 						// catalogue value.
279 						if (templateNode.getSession().hasPendingChanges())
280 							templateNode.getSession().save();
281 					} catch (RepositoryException re) {
282 						throw new ConnectException("Unable to save session for " + templateNode, re);
283 					}
284 
285 					// // Small workaround to keep the calling editor in a clean
286 					// a
287 					// // logical state regarding its check out status
288 					// IWorkbench wb = PlatformUI.getWorkbench();
289 					// IEditorPart editor = wb.getActiveWorkbenchWindow()
290 					// .getActivePage().getActiveEditor();
291 					// if (editor != null && editor instanceof Refreshable) {
292 					// // Cancel and Check In
293 					// Map<String, String> params = new HashMap<String,
294 					// String>();
295 					// params.put(ChangeEditingState.PARAM_NEW_STATE,
296 					// ChangeEditingState.NOT_EDITING);
297 					// params.put(ChangeEditingState.PARAM_PRIOR_ACTION,
298 					// ChangeEditingState.PRIOR_ACTION_CANCEL);
299 					// CommandUtils.callCommand(ChangeEditingState.ID);
300 					// ((Refreshable) editor).forceRefresh(null);
301 					//
302 					// }
303 				}
304 			}
305 		}
306 	}
307 
308 	private class ValuesTableCP implements IStructuredContentProvider {
309 		private static final long serialVersionUID = 1L;
310 
311 		private String[] elements;
312 
313 		/** Expects a list of nodes as a new input */
314 		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
315 			elements = (String[]) newInput;
316 		}
317 
318 		public Object[] getElements(Object arg0) {
319 			return elements;
320 		}
321 
322 		@Override
323 		public void dispose() {
324 		}
325 	}
326 
327 	private TableViewer createInstancesViewer(Composite parent) {
328 		parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
329 		ArrayList<ConnectColumnDefinition> colDefs = new ArrayList<ConnectColumnDefinition>();
330 		colDefs.add(new ConnectColumnDefinition(taggableType, Property.JCR_TITLE, PropertyType.STRING, "Instances",
331 				new TitleIconRowLP(systemWorkbenchService, taggableType, Property.JCR_TITLE), 350));
332 		VirtualJcrTableViewer tableCmp = new VirtualJcrTableViewer(parent, SWT.MULTI, colDefs);
333 		tableCmp.setLayoutData(EclipseUiUtils.fillAll());
334 		TableViewer viewer = tableCmp.getTableViewer();
335 		viewer.setContentProvider(new InstancesTableCP(viewer));
336 		return viewer;
337 	}
338 
339 	private class InstanceDClickAdapter implements IDoubleClickListener {
340 		@Override
341 		public void doubleClick(DoubleClickEvent event) {
342 			Object obj = ((IStructuredSelection) event.getSelection()).getFirstElement();
343 			Node occurrence = ConnectJcrUtils.getNodeFromElement(obj, taggableType);
344 			// CommandUtils.callCommand(systemWorkbenchService.getOpenEntityEditorCmdId(),
345 			// ConnectEditor.PARAM_JCR_ID,
346 			// ConnectJcrUtils.getIdentifier(occurrence));
347 			systemWorkbenchService.openEntityEditor(occurrence);
348 		}
349 	}
350 
351 	private class InstancesTableCP implements ILazyContentProvider {
352 		private static final long serialVersionUID = 1L;
353 		private TableViewer viewer;
354 		private Row[] elements;
355 
356 		public InstancesTableCP(TableViewer viewer) {
357 			this.viewer = viewer;
358 		}
359 
360 		public void dispose() {
361 		}
362 
363 		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
364 			// IMPORTANT: don't forget this: an exception will be thrown if
365 			// a selected object is not part of the results anymore.
366 			viewer.setSelection(null);
367 			if (newInput == null)
368 				elements = null;
369 			else
370 				this.elements = (Row[]) newInput;
371 		}
372 
373 		public void updateElement(int index) {
374 			viewer.replace(elements[index], index);
375 		}
376 	}
377 
378 	/** Use this method to update the instances tables */
379 	private void setViewerInput(TableViewer viewer, Row[] rows) {
380 		viewer.setInput(rows);
381 		// we must explicitly set the items count
382 		int count = 0;
383 		if (rows != null)
384 			count = rows.length;
385 		viewer.setItemCount(count);
386 		viewer.refresh();
387 	}
388 
389 	private void configureAddValueBtn(final AbstractFormPart formPart, final Button button) {
390 		String tooltip = "Create a new value for this catalogue";
391 		button.setToolTipText(tooltip);
392 		button.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false));
393 
394 		button.addSelectionListener(new SelectionAdapter() {
395 			private static final long serialVersionUID = 1L;
396 
397 			@Override
398 			public void widgetSelected(SelectionEvent e) {
399 				String value = SingleValue.ask("New Option", "Enrich current catalogue with a new option");
400 				if (EclipseUiUtils.notEmpty(value)) {
401 					String errMsg = ConnectJcrUtils.addMultiPropertyValue(templateNode, propertyName, value);
402 					if (EclipseUiUtils.isEmpty(errMsg)) {
403 						formPart.markDirty();
404 						formPart.refresh();
405 					} else
406 						MessageDialog.openError(button.getShell(), "Duplicate value", errMsg);
407 				}
408 
409 			}
410 		});
411 	}
412 
413 	public class EditValueWizard extends Wizard {
414 
415 		// Context
416 		private final String actionType;
417 		private final String oldValue;
418 
419 		// This part widgets
420 		private Text newValueTxt;
421 
422 		public EditValueWizard(String actionType, String oldValue) {
423 			this.actionType = actionType;
424 			this.oldValue = oldValue;
425 		}
426 
427 		@Override
428 		public void addPages() {
429 			try {
430 				setWindowTitle("Update wizard");
431 				if (ConnectUiConstants.CRUD_EDIT.equals(actionType)) {
432 					MainInfoPage inputPage = new MainInfoPage("Configure");
433 					addPage(inputPage);
434 				}
435 				RecapPage recapPage = new RecapPage("Validate and launch");
436 				addPage(recapPage);
437 			} catch (Exception e) {
438 				throw new ConnectException("Cannot add page to wizard", e);
439 			}
440 		}
441 
442 		/**
443 		 * Called when the user click on 'Finish' in the wizard. The task is then
444 		 * created and the corresponding session saved.
445 		 */
446 		@Override
447 		public boolean performFinish() {
448 			String errMsg = null;
449 			String newValue = null;
450 
451 			List<String> existingValues = resourcesService.getTemplateCatalogue(templateNode, propertyName, null);
452 
453 			// Sanity checks for update only
454 			if (ConnectUiConstants.CRUD_EDIT.equals(actionType)) {
455 				newValue = newValueTxt.getText();
456 
457 				if (EclipseUiUtils.isEmpty(newValue))
458 					errMsg = "New value cannot be blank or an empty string";
459 				else if (oldValue.equals(newValue))
460 					errMsg = "New value is the same as old one.\n" + "Either enter a new one or press cancel.";
461 				else if (existingValues.contains(newValue))
462 					errMsg = "The new chosen value is already used.\n" + "Either enter a new one or press cancel.";
463 			}
464 			if (errMsg != null) {
465 				MessageDialog.openError(getShell(), "Unvalid information", errMsg);
466 				return false;
467 			}
468 
469 			resourcesService.updateCatalogueValue(templateNode, taggableType, propertyName, oldValue, newValue);
470 			return true;
471 		}
472 
473 		@Override
474 		public boolean performCancel() {
475 			return true;
476 		}
477 
478 		@Override
479 		public boolean canFinish() {
480 			return getContainer().getCurrentPage().getNextPage() == null;
481 		}
482 
483 		protected class MainInfoPage extends WizardPage {
484 			private static final long serialVersionUID = 1L;
485 
486 			public MainInfoPage(String pageName) {
487 				super(pageName);
488 				setTitle("Enter a new value for this catalogue's item");
489 				setMessage("As reminder, former value was: \"" + oldValue + "\"");
490 			}
491 
492 			public void createControl(Composite parent) {
493 				Composite body = new Composite(parent, SWT.NONE);
494 				body.setLayout(new GridLayout(2, false));
495 
496 				// New Title Value
497 				ConnectUiUtils.createBoldLabel(body, "New Value");
498 				newValueTxt = new Text(body, SWT.BORDER);
499 				newValueTxt.setMessage("was: " + oldValue);
500 				newValueTxt.setText(oldValue);
501 				newValueTxt.setLayoutData(EclipseUiUtils.fillWidth());
502 				setControl(body);
503 				newValueTxt.setFocus();
504 			}
505 		}
506 
507 		protected class RecapPage extends WizardPage {
508 			private static final long serialVersionUID = 1L;
509 			private TableViewer membersViewer;
510 			private Composite body;
511 
512 			public RecapPage(String pageName) {
513 				super(pageName);
514 				setTitle("Check and confirm");
515 				setMessage("The below listed items will be impacted.\nAre you sure you want to proceed?");
516 			}
517 
518 			public void createControl(Composite parent) {
519 				body = new Composite(parent, SWT.NONE);
520 				membersViewer = createInstancesViewer(body);
521 				setControl(body);
522 			}
523 
524 			@Override
525 			public void setVisible(boolean visible) {
526 				super.setVisible(visible);
527 
528 				// In the newer approach, all nodes are always checked out.
529 				// TODO We should rather rely on another mechanism to
530 				// investigate on potential blockers before launching the batch
531 				// update
532 
533 				RowIterator rit = query(oldValue);
534 				List<Row> rows = new ArrayList<Row>();
535 				while (rit.hasNext())
536 					rows.add(rit.nextRow());
537 				setViewerInput(membersViewer, rows.toArray(new Row[0]));
538 			}
539 		}
540 	}
541 
542 	// Add a decorator to the checked out instances
543 	// private class MyTitleIconRowLP extends TitleIconRowLP {
544 	// private static final long serialVersionUID = 1L;
545 	// private final Map<Image, Image> images = new HashMap<Image, Image>();
546 	// private final ImageDescriptor failedDesc;
547 	//
548 	// public MyTitleIconRowLP(AppWorkbenchService peopleUiService,
549 	// String selectorName, String propertyName) {
550 	// super(peopleUiService, selectorName, propertyName);
551 	// // this.selectorName = selectorName;
552 	// failedDesc = workbench.getSharedImages().getImageDescriptor(
553 	// ISharedImages.IMG_DEC_FIELD_ERROR);
554 	// }
555 	//
556 	// @Override
557 	// public Image getImage(Object element) {
558 	// Image image = super.getImage(element);
559 	// // Node currEntity = ConnectJcrUtils.getNode((Row) element,
560 	// // selectorName);
561 	// // ConnectJcrUtils.isNodeCheckedOut(currEntity)
562 	// if (editor.isEditing() && image != null) {
563 	// if (images.containsKey(image)) {
564 	// image = images.get(image);
565 	// } else {
566 	// Image descImage = new DecorationOverlayIcon(image,
567 	// failedDesc, IDecoration.BOTTOM_RIGHT).createImage();
568 	// images.put(image, descImage);
569 	// image = descImage;
570 	// }
571 	// }
572 	// return image;
573 	// }
574 	//
575 	// @Override
576 	// public void dispose() {
577 	// // Free created image resources
578 	// for (Image image : images.values())
579 	// image.dispose();
580 	// super.dispose();
581 	// }
582 	// }
583 }