]> git.argeo.org Git - gpl/argeo-slc.git/blob - plugins/org.argeo.slc.client.ui.dist/src/main/java/org/argeo/slc/client/ui/dist/views/DistributionsView.java
Add RepoSyncCommand but do not link it
[gpl/argeo-slc.git] / plugins / org.argeo.slc.client.ui.dist / src / main / java / org / argeo / slc / client / ui / dist / views / DistributionsView.java
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.views;
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.Credentials;
24 import javax.jcr.NoSuchWorkspaceException;
25 import javax.jcr.Node;
26 import javax.jcr.NodeIterator;
27 import javax.jcr.Property;
28 import javax.jcr.Repository;
29 import javax.jcr.RepositoryException;
30 import javax.jcr.RepositoryFactory;
31 import javax.jcr.Session;
32 import javax.jcr.nodetype.NodeType;
33 import javax.jcr.query.Query;
34 import javax.jcr.query.QueryResult;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.argeo.ArgeoMonitor;
39 import org.argeo.eclipse.ui.EclipseArgeoMonitor;
40 import org.argeo.eclipse.ui.ErrorFeedback;
41 import org.argeo.eclipse.ui.TreeParent;
42 import org.argeo.jcr.ArgeoJcrUtils;
43 import org.argeo.jcr.ArgeoNames;
44 import org.argeo.jcr.ArgeoTypes;
45 import org.argeo.jcr.JcrUtils;
46 import org.argeo.jcr.UserJcrUtils;
47 import org.argeo.slc.SlcException;
48 import org.argeo.slc.client.ui.dist.DistPlugin;
49 import org.argeo.slc.client.ui.dist.commands.CopyWorkspace;
50 import org.argeo.slc.client.ui.dist.commands.CreateWorkspace;
51 import org.argeo.slc.client.ui.dist.commands.DeleteWorkspace;
52 import org.argeo.slc.client.ui.dist.commands.ManageWorkspaceAuth;
53 import org.argeo.slc.client.ui.dist.commands.NormalizeDistribution;
54 import org.argeo.slc.client.ui.dist.editors.DistributionEditor;
55 import org.argeo.slc.client.ui.dist.editors.DistributionEditorInput;
56 import org.argeo.slc.client.ui.dist.utils.CommandHelpers;
57 import org.argeo.slc.jcr.SlcNames;
58 import org.argeo.slc.repo.RepoConstants;
59 import org.argeo.slc.repo.RepoUtils;
60 import org.argeo.util.security.Keyring;
61 import org.eclipse.core.runtime.IProgressMonitor;
62 import org.eclipse.core.runtime.IStatus;
63 import org.eclipse.core.runtime.Status;
64 import org.eclipse.core.runtime.jobs.Job;
65 import org.eclipse.jface.action.IMenuListener;
66 import org.eclipse.jface.action.IMenuManager;
67 import org.eclipse.jface.action.MenuManager;
68 import org.eclipse.jface.dialogs.MessageDialog;
69 import org.eclipse.jface.viewers.ColumnLabelProvider;
70 import org.eclipse.jface.viewers.DoubleClickEvent;
71 import org.eclipse.jface.viewers.IDoubleClickListener;
72 import org.eclipse.jface.viewers.IStructuredSelection;
73 import org.eclipse.jface.viewers.ITreeContentProvider;
74 import org.eclipse.jface.viewers.TreeViewer;
75 import org.eclipse.jface.viewers.TreeViewerColumn;
76 import org.eclipse.jface.viewers.Viewer;
77 import org.eclipse.jface.viewers.ViewerDropAdapter;
78 import org.eclipse.swt.SWT;
79 import org.eclipse.swt.dnd.DND;
80 import org.eclipse.swt.dnd.DragSourceAdapter;
81 import org.eclipse.swt.dnd.DragSourceEvent;
82 import org.eclipse.swt.dnd.TextTransfer;
83 import org.eclipse.swt.dnd.Transfer;
84 import org.eclipse.swt.dnd.TransferData;
85 import org.eclipse.swt.widgets.Composite;
86 import org.eclipse.swt.widgets.Menu;
87 import org.eclipse.swt.widgets.Tree;
88 import org.eclipse.ui.IWorkbenchWindow;
89 import org.eclipse.ui.PartInitException;
90 import org.eclipse.ui.part.ViewPart;
91
92 /**
93 * Browse and manipulate distributions (like merge, rename, etc.). Only support
94 * one single repository currently.
95 */
96 public class DistributionsView extends ViewPart implements SlcNames, ArgeoNames {
97 private final static Log log = LogFactory.getLog(DistributionsView.class);
98 public final static String ID = DistPlugin.ID + ".distributionsView";
99
100 private Repository nodeRepository;
101 private RepositoryFactory repositoryFactory;
102 private Keyring keyring;
103
104 private TreeViewer viewer;
105
106 @Override
107 public void createPartControl(Composite parent) {
108 // Define the TableViewer
109 viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL
110 | SWT.FULL_SELECTION | SWT.BORDER);
111
112 TreeViewerColumn col = new TreeViewerColumn(viewer, SWT.NONE);
113 col.getColumn().setWidth(200);
114 col.setLabelProvider(new ColumnLabelProvider() {
115 @Override
116 public String getText(Object element) {
117 if (element instanceof RepoElem)
118 return ((RepoElem) element).getLabel();
119 return element.toString();
120 }
121 });
122
123 final Tree table = viewer.getTree();
124 table.setHeaderVisible(false);
125 table.setLinesVisible(false);
126
127 viewer.setContentProvider(new DistributionsContentProvider());
128 viewer.addDoubleClickListener(new DistributionsDCL());
129
130 // Drag'n drop
131 Transfer[] tt = new Transfer[] { TextTransfer.getInstance() };
132 int operations = DND.DROP_COPY | DND.DROP_MOVE;
133 viewer.addDragSupport(operations, tt, new ViewDragListener());
134 viewer.addDropSupport(operations, tt, new ViewDropListener(viewer));
135
136 MenuManager menuManager = new MenuManager();
137 Menu menu = menuManager.createContextMenu(viewer.getTree());
138 menuManager.addMenuListener(new IMenuListener() {
139 public void menuAboutToShow(IMenuManager manager) {
140 contextMenuAboutToShow(manager);
141 }
142 });
143 viewer.getTree().setMenu(menu);
144 getSite().registerContextMenu(menuManager, viewer);
145
146 Session nodeSession = null;
147 try {
148 nodeSession = nodeRepository.login();
149
150 // make sure base directory is available
151 Node repos = JcrUtils.mkdirs(nodeSession,
152 UserJcrUtils.getUserHome(nodeSession).getPath()
153 + RepoConstants.REPOSITORIES_BASE_PATH);
154 nodeSession.save();
155
156 // register default local java repository
157 String alias = RepoConstants.DEFAULT_JAVA_REPOSITORY_ALIAS;
158 Repository javaRepository = ArgeoJcrUtils.getRepositoryByAlias(
159 repositoryFactory, alias);
160 if (javaRepository != null) {
161 if (!repos.hasNode(alias)) {
162 Node repoNode = repos.addNode(alias,
163 ArgeoTypes.ARGEO_REMOTE_REPOSITORY);
164 repoNode.setProperty(ARGEO_URI, "vm:///" + alias);
165 repoNode.addMixin(NodeType.MIX_TITLE);
166 repoNode.setProperty(Property.JCR_TITLE, "Internal "
167 + alias + " repository");
168 nodeSession.save();
169 }
170 }
171 } catch (RepositoryException e) {
172 throw new SlcException("Cannot register repository", e);
173 } finally {
174 JcrUtils.logoutQuietly(nodeSession);
175 }
176
177 viewer.setInput(nodeRepository);
178
179 }
180
181 /** Programatically configure the context menu */
182 protected void contextMenuAboutToShow(IMenuManager menuManager) {
183 IWorkbenchWindow window = DistPlugin.getDefault().getWorkbench()
184 .getActiveWorkbenchWindow();
185 // Get Current selected item :
186 Object firstElement = ((IStructuredSelection) viewer.getSelection())
187 .getFirstElement();
188
189 if (firstElement instanceof TreeParent
190 || firstElement instanceof RepoElem) {
191 String wsName = null;
192
193 // Build conditions depending on element type (repo or distribution
194 // for the time being)
195 boolean isDistribElem = false; // , isRepoElem = false;
196
197 if (firstElement instanceof DistributionElem) {
198 isDistribElem = true;
199 wsName = ((DistributionElem) firstElement).getName();
200 }
201
202 // create workspace
203 CommandHelpers.refreshCommand(menuManager, window,
204 CreateWorkspace.ID, CreateWorkspace.DEFAULT_LABEL,
205 CreateWorkspace.DEFAULT_ICON_PATH, !isDistribElem);
206
207 // Normalize workspace
208 Map<String, String> params = new HashMap<String, String>();
209 params.put(NormalizeDistribution.PARAM_WORKSPACE, wsName);
210 CommandHelpers.refreshParameterizedCommand(menuManager, window,
211 NormalizeDistribution.ID,
212 NormalizeDistribution.DEFAULT_LABEL,
213 NormalizeDistribution.DEFAULT_ICON_PATH, isDistribElem,
214 params);
215
216 // Copy workspace
217 params = new HashMap<String, String>();
218 params.put(CopyWorkspace.PARAM_WORKSPACE_NAME, wsName);
219 CommandHelpers.refreshParameterizedCommand(menuManager, window,
220 CopyWorkspace.ID, CopyWorkspace.DEFAULT_LABEL,
221 CopyWorkspace.DEFAULT_ICON_PATH, isDistribElem, params);
222
223 // Delete Workspace
224 params = new HashMap<String, String>();
225 params.put(DeleteWorkspace.PARAM_WORKSPACE_NAME, wsName);
226 CommandHelpers.refreshParameterizedCommand(menuManager, window,
227 DeleteWorkspace.ID, DeleteWorkspace.DEFAULT_LABEL,
228 DeleteWorkspace.DEFAULT_ICON_PATH, isDistribElem, params);
229
230 // Manage workspace authorizations
231 params = new HashMap<String, String>();
232 params.put(ManageWorkspaceAuth.PARAM_WORKSPACE_NAME, wsName);
233 CommandHelpers.refreshParameterizedCommand(menuManager, window,
234 ManageWorkspaceAuth.ID, ManageWorkspaceAuth.DEFAULT_LABEL,
235 ManageWorkspaceAuth.DEFAULT_ICON_PATH, isDistribElem,
236 params);
237 }
238 }
239
240 @Override
241 public void setFocus() {
242 viewer.getTree().setFocus();
243 }
244
245 /*
246 * DEPENDENCY INJECTION
247 */
248 /**
249 * Force refresh of the whole view
250 */
251 public void refresh() {
252 viewer.setContentProvider(new DistributionsContentProvider());
253 }
254
255 public void setNodeRepository(Repository repository) {
256 this.nodeRepository = repository;
257 }
258
259 public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
260 this.repositoryFactory = repositoryFactory;
261 }
262
263 public void setKeyring(Keyring keyring) {
264 this.keyring = keyring;
265 }
266
267 /*
268 * INTERNAL CLASSES
269 */
270 /** Content provider */
271 private class DistributionsContentProvider implements ITreeContentProvider {
272 Session nodeSession;
273 List<RepoElem> repositories = new ArrayList<RepoElem>();
274
275 public Object[] getElements(Object input) {
276 Repository nodeRepository = (Repository) input;
277 try {
278 if (nodeSession != null)
279 dispose();
280 nodeSession = nodeRepository.login();
281
282 String reposPath = UserJcrUtils.getUserHome(nodeSession)
283 .getPath() + RepoConstants.REPOSITORIES_BASE_PATH;
284 NodeIterator repos = nodeSession.getNode(reposPath).getNodes();
285 while (repos.hasNext()) {
286 Node repoNode = repos.nextNode();
287 if (repoNode.isNodeType(ArgeoTypes.ARGEO_REMOTE_REPOSITORY)) {
288 repositories.add(new RepoElem(repoNode));
289 }
290 }
291 } catch (RepositoryException e) {
292 throw new SlcException("Cannot get base elements", e);
293 }
294 return repositories.toArray();
295 }
296
297 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
298
299 }
300
301 public Object[] getChildren(Object parentElement) {
302 if (parentElement instanceof RepoElem) {
303 return ((RepoElem) parentElement).getChildren();
304 } else if (parentElement instanceof DistributionElem) {
305 return ((DistributionElem) parentElement).getChildren();
306 }
307 return null;
308 }
309
310 public Object getParent(Object element) {
311 // TODO register repo elem in distirbution elem?
312 return null;
313 }
314
315 public boolean hasChildren(Object element) {
316 if (element instanceof RepoElem) {
317 return true;
318 } else if (element instanceof DistributionElem) {
319 return false;
320 }
321 return false;
322 }
323
324 public void dispose() {
325 for (RepoElem repoElem : repositories)
326 repoElem.dispose();
327 repositories = new ArrayList<RepoElem>();
328 JcrUtils.logoutQuietly(nodeSession);
329 }
330
331 }
332
333 /** A software repository */
334 private class RepoElem {
335 private Node repoNode;
336
337 private Repository repository;
338 private Credentials credentials;
339
340 public RepoElem(Node repoNode) {
341 this.repoNode = repoNode;
342 }
343
344 /** Lazily connects to repository */
345 protected void connect() {
346 if (repository != null)
347 return;
348 repository = RepoUtils.getRepository(repositoryFactory, keyring,
349 repoNode);
350 credentials = RepoUtils.getRepositoryCredentials(keyring, repoNode);
351 }
352
353 public String getLabel() {
354 try {
355 if (repoNode.isNodeType(NodeType.MIX_TITLE)) {
356 return repoNode.getProperty(Property.JCR_TITLE).getString();
357 } else {
358 return repoNode.getName();
359 }
360 } catch (RepositoryException e) {
361 throw new SlcException("Cannot read label of " + repoNode, e);
362 }
363 }
364
365 public String toString() {
366 return repoNode.toString();
367 }
368
369 public Object[] getChildren() {
370 connect();
371 Session session = null;
372 try {
373 session = repository.login(credentials);
374 String[] workspaceNames = session.getWorkspace()
375 .getAccessibleWorkspaceNames();
376 List<DistributionElem> distributionElems = new ArrayList<DistributionElem>();
377 for (String workspaceName : workspaceNames) {
378 Node workspaceNode = repoNode.hasNode(workspaceName) ? repoNode
379 .getNode(workspaceName) : repoNode
380 .addNode(workspaceName);
381 repoNode.getSession().save();
382 distributionElems.add(new DistributionElem(this,
383 workspaceNode));
384 // FIXME remove deleted workspaces
385 }
386 return distributionElems.toArray();
387 } catch (RepositoryException e) {
388 throw new SlcException(
389 "Cannot list workspaces for " + repoNode, e);
390 } finally {
391 JcrUtils.logoutQuietly(session);
392 }
393 }
394
395 public void dispose() {
396 }
397
398 public Node getRepoNode() {
399 return repoNode;
400 }
401
402 public String getRepoPath() {
403 try {
404 return repoNode.getPath();
405 } catch (RepositoryException e) {
406 throw new SlcException("Cannot get path for " + repoNode, e);
407 }
408 }
409
410 public Repository getRepository() {
411 connect();
412 return repository;
413 }
414
415 public Credentials getCredentials() {
416 return credentials;
417 }
418
419 }
420
421 /** Abstracts a distribution, that is a workspace */
422 private static class DistributionElem extends TreeParent {
423 private final RepoElem repoElem;
424 private final Node workspaceNode;
425
426 public DistributionElem(RepoElem repoElem, Node workspaceNode) {
427 super(JcrUtils.getNameQuietly(workspaceNode));
428 this.repoElem = repoElem;
429 this.workspaceNode = workspaceNode;
430 }
431
432 public Node getWorkspaceNode() {
433 return workspaceNode;
434 }
435
436 public String getWorkspaceName() {
437 return JcrUtils.getNameQuietly(workspaceNode);
438 }
439
440 public String getWorkspacePath() {
441 try {
442 return workspaceNode.getPath();
443 } catch (RepositoryException e) {
444 throw new SlcException("Cannot get or add workspace path "
445 + getWorkspaceName(), e);
446 }
447 }
448
449 public String getRepoPath() {
450 try {
451 return workspaceNode.getParent().getPath();
452 } catch (RepositoryException e) {
453 throw new SlcException("Cannot get or add workspace path "
454 + getWorkspaceName(), e);
455 }
456 }
457
458 public RepoElem getRepoElem() {
459 return repoElem;
460 }
461
462 public Credentials getCredentials() {
463 return repoElem.getCredentials();
464 }
465 }
466
467 /** Listens to drag */
468 class ViewDragListener extends DragSourceAdapter {
469 public void dragSetData(DragSourceEvent event) {
470 IStructuredSelection selection = (IStructuredSelection) viewer
471 .getSelection();
472 if (selection.getFirstElement() instanceof DistributionElem) {
473 DistributionElem de = (DistributionElem) selection
474 .getFirstElement();
475 if (TextTransfer.getInstance().isSupportedType(event.dataType)) {
476 event.data = de.getWorkspacePath();
477 }
478 }
479 }
480 }
481
482 /** Listens to drop */
483 class ViewDropListener extends ViewerDropAdapter {
484
485 public ViewDropListener(Viewer viewer) {
486 super(viewer);
487 }
488
489 @Override
490 public boolean performDrop(Object data) {
491 DistributionElem sourceDist = (DistributionElem) getSelectedObject();
492 RepoElem targetRepo = (RepoElem) getCurrentTarget();
493
494 Boolean ok = MessageDialog.openConfirm(getSite().getShell(),
495 "Confirm distribution merge", "Do you want to merge "
496 + sourceDist.getWorkspaceName() + " (from repo "
497 + sourceDist.getRepoElem().getLabel()
498 + ") to repo " + targetRepo.getLabel() + "?");
499 if (!ok)
500 return false;
501
502 try {
503 String sourceWorkspace = sourceDist.getWorkspaceName();
504 Repository sourceRepository = RepoUtils.getRepository(
505 repositoryFactory, keyring, sourceDist
506 .getWorkspaceNode().getParent());
507 Credentials sourceCredentials = RepoUtils
508 .getRepositoryCredentials(keyring, sourceDist
509 .getWorkspaceNode().getParent());
510
511 String targetWorkspace = sourceWorkspace;
512 Repository targetRepository = RepoUtils.getRepository(
513 repositoryFactory, keyring, targetRepo.getRepoNode());
514 Credentials targetCredentials = RepoUtils
515 .getRepositoryCredentials(keyring,
516 targetRepo.getRepoNode());
517
518 // Open sessions here since the background thread
519 // won't necessarily be authenticated.
520 // Job should close the sessions.
521 Session sourceSession = sourceRepository.login(
522 sourceCredentials, sourceWorkspace);
523 Session targetSession;
524 try {
525 targetSession = targetRepository.login(targetCredentials,
526 targetWorkspace);
527 } catch (NoSuchWorkspaceException e) {
528 Session defaultSession = targetRepository
529 .login(targetCredentials);
530 try {
531 defaultSession.getWorkspace().createWorkspace(
532 targetWorkspace);
533 } catch (Exception e1) {
534 throw new SlcException("Cannot create new workspace "
535 + targetWorkspace, e);
536 } finally {
537 JcrUtils.logoutQuietly(defaultSession);
538 }
539 targetSession = targetRepository.login(targetCredentials,
540 targetWorkspace);
541 }
542
543 Job workspaceMergeJob = new WorkspaceMergeJob(sourceSession,
544 targetSession);
545 workspaceMergeJob.setUser(true);
546 workspaceMergeJob.schedule();
547 return true;
548 } catch (RepositoryException e) {
549 throw new SlcException("Cannot process drop from " + sourceDist
550 + " to " + targetRepo, e);
551 }
552 }
553
554 @Override
555 public boolean validateDrop(Object target, int operation,
556 TransferData transferType) {
557 if (target instanceof RepoElem) {
558 if (getSelectedObject() instanceof DistributionElem) {
559 // check if not same repository
560 String srcRepoPath = ((DistributionElem) getSelectedObject())
561 .getRepoPath();
562 String targetRepoPath = ((RepoElem) target).getRepoPath();
563 return !targetRepoPath.equals(srcRepoPath);
564 }
565 }
566 return false;
567 }
568 }
569
570 private static class WorkspaceMergeJob extends Job {
571 private Session sourceSession;
572 private Session targetSession;
573
574 public WorkspaceMergeJob(Session sourceSession, Session targetSession) {
575 super("Workspace merge");
576 this.sourceSession = sourceSession;
577 this.targetSession = targetSession;
578 }
579
580 @Override
581 protected IStatus run(IProgressMonitor eclipseMonitor) {
582 long begin = System.currentTimeMillis();
583 try {
584 Query countQuery = sourceSession
585 .getWorkspace()
586 .getQueryManager()
587 .createQuery("select file from [nt:file] as file",
588 Query.JCR_SQL2);
589 QueryResult result = countQuery.execute();
590 Long expectedCount = result.getNodes().getSize();
591 if (log.isDebugEnabled())
592 log.debug("Will copy " + expectedCount + " files...");
593
594 ArgeoMonitor monitor = new EclipseArgeoMonitor(eclipseMonitor);
595 eclipseMonitor
596 .beginTask("Copy files", expectedCount.intValue());
597
598 Long count = JcrUtils.copyFiles(sourceSession.getRootNode(),
599 targetSession.getRootNode(), true, monitor);
600
601 monitor.done();
602 long duration = (System.currentTimeMillis() - begin) / 1000;// in
603 // s
604 if (log.isDebugEnabled())
605 log.debug("Copied " + count + " files in "
606 + (duration / 60) + "min " + (duration % 60) + "s");
607
608 return Status.OK_STATUS;
609 } catch (RepositoryException e) {
610 return new Status(IStatus.ERROR, DistPlugin.ID, "Cannot merge",
611 e);
612 } finally {
613 JcrUtils.logoutQuietly(sourceSession);
614 JcrUtils.logoutQuietly(targetSession);
615 }
616 }
617 }
618
619 /** Listen to double-clicks */
620 private class DistributionsDCL implements IDoubleClickListener {
621
622 public void doubleClick(DoubleClickEvent event) {
623 if (event.getSelection() == null || event.getSelection().isEmpty())
624 return;
625 Object obj = ((IStructuredSelection) event.getSelection())
626 .getFirstElement();
627 if (obj instanceof DistributionElem) {
628 DistributionElem distributionElem = (DistributionElem) obj;
629 DistributionEditorInput dei = new DistributionEditorInput(
630 distributionElem.getName(), distributionElem
631 .getRepoElem().getRepository(),
632 distributionElem.getWorkspaceName(),
633 distributionElem.getCredentials());
634 try {
635 DistPlugin.getDefault().getWorkbench()
636 .getActiveWorkbenchWindow().getActivePage()
637 .openEditor(dei, DistributionEditor.ID);
638 } catch (PartInitException e) {
639 ErrorFeedback.show("Cannot open editor for "
640 + distributionElem.getWorkspaceName(), e);
641 }
642 }
643 }
644 }
645 }