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.cms.e4.jcr;
17  
18  import java.util.List;
19  
20  import javax.annotation.PostConstruct;
21  import javax.annotation.PreDestroy;
22  import javax.inject.Inject;
23  import javax.jcr.Property;
24  import javax.jcr.PropertyType;
25  import javax.jcr.Repository;
26  import javax.jcr.RepositoryException;
27  import javax.jcr.RepositoryFactory;
28  import javax.jcr.Session;
29  import javax.jcr.Value;
30  import javax.jcr.observation.Event;
31  import javax.jcr.observation.EventListener;
32  import javax.jcr.observation.ObservationManager;
33  
34  import org.argeo.api.NodeConstants;
35  import org.argeo.api.security.CryptoKeyring;
36  import org.argeo.api.security.Keyring;
37  import org.argeo.cms.CmsException;
38  import org.argeo.cms.ui.jcr.JcrBrowserUtils;
39  import org.argeo.cms.ui.jcr.NodeContentProvider;
40  import org.argeo.cms.ui.jcr.NodeLabelProvider;
41  import org.argeo.cms.ui.jcr.OsgiRepositoryRegister;
42  import org.argeo.cms.ui.jcr.PropertiesContentProvider;
43  import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
44  import org.argeo.cms.ui.util.CmsUiUtils;
45  import org.argeo.eclipse.ui.EclipseUiException;
46  import org.argeo.eclipse.ui.TreeParent;
47  import org.argeo.eclipse.ui.jcr.AsyncUiEventListener;
48  import org.argeo.eclipse.ui.jcr.util.NodeViewerComparer;
49  import org.argeo.jcr.JcrUtils;
50  import org.eclipse.e4.core.contexts.IEclipseContext;
51  import org.eclipse.e4.core.di.annotations.Optional;
52  import org.eclipse.e4.ui.services.EMenuService;
53  import org.eclipse.e4.ui.workbench.modeling.EPartService;
54  import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
55  import org.eclipse.jface.viewers.ColumnLabelProvider;
56  import org.eclipse.jface.viewers.IBaseLabelProvider;
57  import org.eclipse.jface.viewers.ISelectionChangedListener;
58  import org.eclipse.jface.viewers.IStructuredSelection;
59  import org.eclipse.jface.viewers.ITreeContentProvider;
60  import org.eclipse.jface.viewers.SelectionChangedEvent;
61  import org.eclipse.jface.viewers.StructuredSelection;
62  import org.eclipse.jface.viewers.TableViewer;
63  import org.eclipse.jface.viewers.TableViewerColumn;
64  import org.eclipse.jface.viewers.TreeViewer;
65  import org.eclipse.swt.SWT;
66  import org.eclipse.swt.custom.SashForm;
67  import org.eclipse.swt.layout.FillLayout;
68  import org.eclipse.swt.layout.GridData;
69  import org.eclipse.swt.widgets.Composite;
70  import org.eclipse.swt.widgets.Display;
71  
72  /**
73   * Basic View to display a sash form to browse a JCR compliant multiple
74   * repository environment
75   */
76  public class JcrBrowserView {
77  	final static String ID = "org.argeo.cms.e4.jcrbrowser";
78  	final static String NODE_VIEWER_POPUP_MENU_ID = "org.argeo.cms.e4.popupmenu.nodeViewer";
79  
80  	private boolean sortChildNodes = true;
81  
82  	/* DEPENDENCY INJECTION */
83  	@Inject
84  	@Optional
85  	private Keyring keyring;
86  	@Inject
87  	private RepositoryFactory repositoryFactory;
88  	@Inject
89  	private Repository nodeRepository;
90  
91  	// Current user session on the home repository default workspace
92  	private Session userSession;
93  
94  	private OsgiRepositoryRegister repositoryRegister = new OsgiRepositoryRegister();
95  
96  	// This page widgets
97  	private TreeViewer nodesViewer;
98  	private NodeContentProvider nodeContentProvider;
99  	private TableViewer propertiesViewer;
100 	private EventListener resultsObserver;
101 
102 	@PostConstruct
103 	public void createPartControl(Composite parent, IEclipseContext context, EPartService partService,
104 			ESelectionService selectionService, EMenuService menuService) {
105 		repositoryRegister.init();
106 
107 		parent.setLayout(new FillLayout());
108 		SashForm sashForm = new SashForm(parent, SWT.VERTICAL);
109 		// sashForm.setSashWidth(4);
110 		// sashForm.setLayout(new FillLayout());
111 
112 		// Create the tree on top of the view
113 		Composite top = new Composite(sashForm, SWT.NONE);
114 		// GridLayout gl = new GridLayout(1, false);
115 		top.setLayout(CmsUiUtils.noSpaceGridLayout());
116 
117 		try {
118 			this.userSession = this.nodeRepository.login(NodeConstants.HOME_WORKSPACE);
119 		} catch (RepositoryException e) {
120 			throw new CmsException("Cannot open user session", e);
121 		}
122 
123 		nodeContentProvider = new NodeContentProvider(userSession, keyring, repositoryRegister, repositoryFactory,
124 				sortChildNodes);
125 
126 		// nodes viewer
127 		nodesViewer = createNodeViewer(top, nodeContentProvider);
128 
129 		// context menu : it is completely defined in the plugin.xml file.
130 		// MenuManager menuManager = new MenuManager();
131 		// Menu menu = menuManager.createContextMenu(nodesViewer.getTree());
132 
133 		// nodesViewer.getTree().setMenu(menu);
134 
135 		nodesViewer.setInput("");
136 
137 		// Create the property viewer on the bottom
138 		Composite bottom = new Composite(sashForm, SWT.NONE);
139 		bottom.setLayout(CmsUiUtils.noSpaceGridLayout());
140 		propertiesViewer = createPropertiesViewer(bottom);
141 
142 		sashForm.setWeights(getWeights());
143 		nodesViewer.setComparer(new NodeViewerComparer());
144 		nodesViewer.addSelectionChangedListener(new ISelectionChangedListener() {
145 			public void selectionChanged(SelectionChangedEvent event) {
146 				IStructuredSelection selection = (IStructuredSelection) event.getSelection();
147 				selectionService.setSelection(selection.toList());
148 			}
149 		});
150 		nodesViewer.addDoubleClickListener(new JcrE4DClickListener(nodesViewer, partService));
151 		menuService.registerContextMenu(nodesViewer.getControl(), NODE_VIEWER_POPUP_MENU_ID);
152 		// getSite().registerContextMenu(menuManager, nodesViewer);
153 		// getSite().setSelectionProvider(nodesViewer);
154 	}
155 
156 	@PreDestroy
157 	public void dispose() {
158 		JcrUtils.logoutQuietly(userSession);
159 		repositoryRegister.destroy();
160 	}
161 
162 	public void refresh(Object obj) {
163 		// Enable full refresh from a command when no element of the tree is
164 		// selected
165 		if (obj == null) {
166 			Object[] elements = nodeContentProvider.getElements(null);
167 			for (Object el : elements) {
168 				if (el instanceof TreeParent)
169 					JcrBrowserUtils.forceRefreshIfNeeded((TreeParent) el);
170 				getNodeViewer().refresh(el);
171 			}
172 		} else
173 			getNodeViewer().refresh(obj);
174 	}
175 
176 	/**
177 	 * To be overridden to adapt size of form and result frames.
178 	 */
179 	protected int[] getWeights() {
180 		return new int[] { 70, 30 };
181 	}
182 
183 	protected TreeViewer createNodeViewer(Composite parent, final ITreeContentProvider nodeContentProvider) {
184 
185 		final TreeViewer tmpNodeViewer = new TreeViewer(parent, SWT.MULTI);
186 
187 		tmpNodeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
188 
189 		tmpNodeViewer.setContentProvider(nodeContentProvider);
190 		tmpNodeViewer.setLabelProvider((IBaseLabelProvider) new NodeLabelProvider());
191 		tmpNodeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
192 			public void selectionChanged(SelectionChangedEvent event) {
193 				if (!event.getSelection().isEmpty()) {
194 					IStructuredSelection sel = (IStructuredSelection) event.getSelection();
195 					Object firstItem = sel.getFirstElement();
196 					if (firstItem instanceof SingleJcrNodeElem)
197 						propertiesViewer.setInput(((SingleJcrNodeElem) firstItem).getNode());
198 				} else {
199 					propertiesViewer.setInput("");
200 				}
201 			}
202 		});
203 
204 		resultsObserver = new TreeObserver(tmpNodeViewer.getTree().getDisplay());
205 		if (keyring != null)
206 			try {
207 				ObservationManager observationManager = userSession.getWorkspace().getObservationManager();
208 				observationManager.addEventListener(resultsObserver, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED, "/",
209 						true, null, null, false);
210 			} catch (RepositoryException e) {
211 				throw new EclipseUiException("Cannot register listeners", e);
212 			}
213 
214 		// tmpNodeViewer.addDoubleClickListener(new JcrDClickListener(tmpNodeViewer));
215 		return tmpNodeViewer;
216 	}
217 
218 	protected TableViewer createPropertiesViewer(Composite parent) {
219 		propertiesViewer = new TableViewer(parent, SWT.NONE);
220 		propertiesViewer.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
221 		propertiesViewer.getTable().setHeaderVisible(true);
222 		propertiesViewer.setContentProvider(new PropertiesContentProvider());
223 		TableViewerColumn col = new TableViewerColumn(propertiesViewer, SWT.NONE);
224 		col.getColumn().setText("Name");
225 		col.getColumn().setWidth(200);
226 		col.setLabelProvider(new ColumnLabelProvider() {
227 			private static final long serialVersionUID = -6684361063107478595L;
228 
229 			public String getText(Object element) {
230 				try {
231 					return ((Property) element).getName();
232 				} catch (RepositoryException e) {
233 					throw new EclipseUiException("Unexpected exception in label provider", e);
234 				}
235 			}
236 		});
237 		col = new TableViewerColumn(propertiesViewer, SWT.NONE);
238 		col.getColumn().setText("Value");
239 		col.getColumn().setWidth(400);
240 		col.setLabelProvider(new ColumnLabelProvider() {
241 			private static final long serialVersionUID = -8201994187693336657L;
242 
243 			public String getText(Object element) {
244 				try {
245 					Property property = (Property) element;
246 					if (property.getType() == PropertyType.BINARY)
247 						return "<binary>";
248 					else if (property.isMultiple()) {
249 						StringBuffer buf = new StringBuffer("[");
250 						Value[] values = property.getValues();
251 						for (int i = 0; i < values.length; i++) {
252 							if (i != 0)
253 								buf.append(", ");
254 							buf.append(values[i].getString());
255 						}
256 						buf.append(']');
257 						return buf.toString();
258 					} else
259 						return property.getValue().getString();
260 				} catch (RepositoryException e) {
261 					throw new EclipseUiException("Unexpected exception in label provider", e);
262 				}
263 			}
264 		});
265 		col = new TableViewerColumn(propertiesViewer, SWT.NONE);
266 		col.getColumn().setText("Type");
267 		col.getColumn().setWidth(200);
268 		col.setLabelProvider(new ColumnLabelProvider() {
269 			private static final long serialVersionUID = -6009599998150286070L;
270 
271 			public String getText(Object element) {
272 				return JcrBrowserUtils.getPropertyTypeAsString((Property) element);
273 			}
274 		});
275 		propertiesViewer.setInput("");
276 		return propertiesViewer;
277 	}
278 
279 	protected TreeViewer getNodeViewer() {
280 		return nodesViewer;
281 	}
282 
283 	/**
284 	 * Resets the tree content provider
285 	 * 
286 	 * @param sortChildNodes if true the content provider will use a comparer to
287 	 *                       sort nodes that might slow down the display
288 	 */
289 	public void setSortChildNodes(boolean sortChildNodes) {
290 		this.sortChildNodes = sortChildNodes;
291 		((NodeContentProvider) nodesViewer.getContentProvider()).setSortChildren(sortChildNodes);
292 		nodesViewer.setInput("");
293 	}
294 
295 	/** Notifies the current view that a node has been added */
296 	public void nodeAdded(TreeParent parentNode) {
297 		// insure that Ui objects have been correctly created:
298 		JcrBrowserUtils.forceRefreshIfNeeded(parentNode);
299 		getNodeViewer().refresh(parentNode);
300 		getNodeViewer().expandToLevel(parentNode, 1);
301 	}
302 
303 	/** Notifies the current view that a node has been removed */
304 	public void nodeRemoved(TreeParent parentNode) {
305 		IStructuredSelection newSel = new StructuredSelection(parentNode);
306 		getNodeViewer().setSelection(newSel, true);
307 		// Force refresh
308 		IStructuredSelection tmpSel = (IStructuredSelection) getNodeViewer().getSelection();
309 		getNodeViewer().refresh(tmpSel.getFirstElement());
310 	}
311 
312 	class TreeObserver extends AsyncUiEventListener {
313 
314 		public TreeObserver(Display display) {
315 			super(display);
316 		}
317 
318 		@Override
319 		protected Boolean willProcessInUiThread(List<Event> events) throws RepositoryException {
320 			for (Event event : events) {
321 				if (getLog().isTraceEnabled())
322 					getLog().debug("Received event " + event);
323 				String path = event.getPath();
324 				int index = path.lastIndexOf('/');
325 				String propertyName = path.substring(index + 1);
326 				if (getLog().isTraceEnabled())
327 					getLog().debug("Concerned property " + propertyName);
328 			}
329 			return false;
330 		}
331 
332 		protected void onEventInUiThread(List<Event> events) throws RepositoryException {
333 			if (getLog().isTraceEnabled())
334 				getLog().trace("Refresh result list");
335 			nodesViewer.refresh();
336 		}
337 
338 	}
339 
340 	public boolean getSortChildNodes() {
341 		return sortChildNodes;
342 	}
343 
344 	public void setFocus() {
345 		getNodeViewer().getTree().setFocus();
346 	}
347 
348 	/* DEPENDENCY INJECTION */
349 	// public void setRepositoryRegister(RepositoryRegister repositoryRegister) {
350 	// this.repositoryRegister = repositoryRegister;
351 	// }
352 
353 	public void setKeyring(CryptoKeyring keyring) {
354 		this.keyring = keyring;
355 	}
356 
357 	public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
358 		this.repositoryFactory = repositoryFactory;
359 	}
360 
361 	public void setNodeRepository(Repository nodeRepository) {
362 		this.nodeRepository = nodeRepository;
363 	}
364 }