001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.web.client.contents;
017
018import com.google.gwt.user.client.rpc.AsyncCallback;
019import com.smartgwt.client.util.BooleanCallback;
020import com.smartgwt.client.util.SC;
021import com.smartgwt.client.widgets.Canvas;
022import com.smartgwt.client.widgets.layout.VLayout;
023import java.util.Date;
024import java.util.HashMap;
025import org.modeshape.web.client.Console;
026import org.modeshape.web.client.JcrServiceAsync;
027import org.modeshape.web.shared.BaseCallback;
028import org.modeshape.web.shared.RemoteException;
029import org.modeshape.web.shared.JcrNode;
030import org.modeshape.web.shared.JcrPermission;
031import org.modeshape.web.shared.JcrProperty;
032
033/**
034 * Displays contents.
035 * 
036 *  |--------------------------------|
037 *  | session & workspace            |
038 *  |--------------------------------|
039 *  | path & buttons                 |
040 *  |--------------------------------|
041 *  | child nodes, binary & props    |
042 *  |--------------------------------|
043 * 
044 * 
045 * 
046 * @author kulikov
047 */
048@SuppressWarnings("synthetic-access")
049public class Contents extends VLayout {
050
051    private final static String ROOT_PATH = "/";
052    
053    private Console console;
054    
055    //
056    private String repository;
057    private JcrNode node;
058    private String path;
059    
060    private WorkspacePanel wsp = new WorkspacePanel(this);
061    private PathControl pathLabel = new PathControl(this);
062    
063    //frames
064    private ChildrenEditor childrenEditor = new ChildrenEditor(this);
065    private PropertiesEditor propertiesEditor = new PropertiesEditor(this);
066    private PermissionsEditor permissionsEditor = new PermissionsEditor(this);
067    private BinaryEditor binaryEditor = new BinaryEditor();
068    
069    //layouts
070    private DetailsLayout details = new DetailsLayout(propertiesEditor, permissionsEditor);
071    private ContentsLayout contentsLayout = new ContentsLayout(childrenEditor, binaryEditor, details);
072    
073    //dialog
074    private final AddNodeDialog addNodeDialog = new AddNodeDialog(this);
075    private final ExportDialog exportDialog = new ExportDialog(this);
076    private final ImportDialog importDialog = new ImportDialog(this);
077    
078    @SuppressWarnings("unchecked")
079    private final HashMap<String, Session> sessions = new HashMap();
080
081    /**
082     * Creates contents instance.
083     * 
084     * @param console
085     */
086    public Contents(final Console console) {
087        this.console = console;
088
089        addMember(description());
090        addMember(new Spacer(20));
091        
092        addMember(wsp);
093        addMember(new Spacer(20));
094        addMember(pathLabel);
095        addMember(contentsLayout);
096    }
097
098    private Canvas description() {
099        Canvas text = new Canvas();
100        text.setContents("ModeShape is a distributed, hierarchical, transactional, and consistent data store with support for queries, full-text search, events, versioning, references, and flexible and dynamic schemas. It is very fast, highly available, extremely scalable, and it is 100% open source and written in Java. Clients use the JSR-283 standard Java API for content repositories (aka, JCR) or ModeShape's REST API, and can query content through JDBC and SQL.");
101        text.setWidth100();
102        text.setAutoHeight();
103        text.setStyleName("caption");
104        return text;
105    }
106    
107    private Session session() {
108        Session s = sessions.get(repository() + "$" + workspace());
109        if (s == null) {
110            s = new Session();
111            sessions.put(repository() + "$" + workspace(), s);
112        }
113        return s;
114    }
115    
116    /**
117     * Expose interface to the server side.
118     *
119     * @return the service
120     */
121    public JcrServiceAsync jcrService() {
122        return this.console.jcrService();
123    }
124
125    private void showLoadIcon() {
126        pathLabel.setVisible(false);
127        contentsLayout.setVisible(false);
128        console.showLoadingIcon();
129    }
130
131    private void hideLoadIcon() {
132        console.hideLoadingIcon();
133        pathLabel.setVisible(true);
134        contentsLayout.setVisible(true);
135    }
136
137    protected void toggleDetails() {
138        contentsLayout.setShowDetails(!contentsLayout.showDetails());
139    }
140    
141    /**
142     * Displays root node of the specified workspace.
143     *
144     * @param name the name of the workspace.
145     */
146    public void changeWorkspace(final String name) {
147        this.getAndDisplayNode(ROOT_PATH, true);
148        updateControls();
149    }
150
151    /**
152     * Shows content of the root node of the first reachable workspace of the
153     * given repository.
154     *
155     * @param repository the name of the given repository.
156     * @param changeHistory if true then this action of navigation will be
157     * reflected in the browser URL and will don't touch URL in case of false
158     * value.
159     */
160    public void show(String repository, final boolean changeHistory) {
161        this.repository = repository;
162        refreshWorkspacesAndReloadNode(null, ROOT_PATH, changeHistory);
163    }
164
165    /**
166     * Shows nodes identified by repository, workspace and path to node.
167     *
168     * @param repository the name of the repository
169     * @param workspace the name of the workspace
170     * @param path the path to node
171     * @param changeHistory true if this action should be reflected in browser
172     * history.
173     */
174    public void show(final String repository, final String workspace,
175            final String path, final boolean changeHistory) {
176        this.repository = repository;
177        this.refreshWorkspacesAndReloadNode(null, path, changeHistory);
178    }
179
180    /**
181     * Reloads values of the combo box with workspace names.
182     *
183     * Gets values from server side, assigns to combo box and select given name.
184     *
185     * @param name the name to be selected.
186     * @param path the path
187     * @param changeHistory true if the history is to
188     */
189    private void refreshWorkspacesAndReloadNode(final String name, final String path,
190            final boolean changeHistory) {
191        showLoadIcon();
192        console.jcrService().getWorkspaces(repository, new AsyncCallback<String[]>() {
193            @Override
194            public void onFailure(Throwable caught) {
195                hideLoadIcon();
196                RemoteException e = (RemoteException) caught;
197                SC.say(caught.getMessage());
198                if (e.code() == RemoteException.SECURITY_ERROR) {
199                    console.loadRepositoriesList();
200                }
201            }
202
203            @Override
204            public void onSuccess(String[] workspaces) {
205                wsp.setWorkspaceNames(workspaces);
206                getAndDisplayNode(path, changeHistory);
207                hideLoadIcon();
208            }
209        });
210    }
211
212    /**
213     * Reads node with given path and selected repository and workspace.
214     *
215     * @param path the path to the node.
216     * @param changeHistory if true then path will be reflected in browser
217     * history.
218     */
219    public void getAndDisplayNode(final String path, final boolean changeHistory) {
220        showLoadIcon();
221        console.jcrService().node(repository(), workspace(), path, new AsyncCallback<JcrNode>() {
222            @Override
223            public void onFailure(Throwable caught) {
224                hideLoadIcon();
225                SC.say(caught.getMessage());
226            }
227
228            @Override
229            public void onSuccess(JcrNode node) {
230                displayNode(node);
231                console.changeWorkspaceInURL(workspace(), changeHistory);
232                console.changePathInURL(path, changeHistory);
233                hideLoadIcon();
234            }
235        });
236    }
237
238    /**
239     * Displays specified node.
240     *
241     * @param node the node being displayed.
242     */
243    private void displayNode(JcrNode node) {
244        this.node = node;
245        this.path = node.getPath();
246
247        pathLabel.display(node.getPath());
248
249        //display childs, properties and ACLs
250        childrenEditor.show(node);
251        propertiesEditor.show(node);
252        permissionsEditor.show(node);
253
254        displayBinaryContent(node);
255
256        //bring this page on top
257//        console.display(Contents.this);
258    }
259
260    private void displayBinaryContent(JcrNode node) {
261        //check for binary content
262        binaryEditor.setVisible(false);
263        for (JcrProperty property : node.getProperties()) {
264            if (property.isBinary()) {
265                binaryEditor.setVisible(true);
266                binaryEditor.setValue(node, property.getName(), property.getValue());
267            }
268        }
269    }
270
271    /**
272     * Save session's changes.
273     */
274    public void save() {
275        SC.ask("Do you want to save changes", new BooleanCallback() {
276            @Override
277            public void execute(Boolean yesSelected) {
278                if (yesSelected) {
279                    jcrService().save(repository(), workspace(), new BaseCallback<Object>() {
280                            @Override
281                            public void onSuccess(Object result) {
282                                session().setHasChanges(false);
283                                updateControls();
284                            }
285                    });
286                }
287            }
288        });
289    }
290    
291    
292    public void refreshSession(boolean keepChanges) {
293        console.jcrService().refreshSession(repository(), workspace(), keepChanges, new AsyncCallback() {
294            @Override
295            public void onFailure(Throwable caught) {
296                SC.say(caught.getMessage());
297            }
298
299            @Override
300            public void onSuccess(Object result) {
301                getAndDisplayNode(path(), true);
302            }
303        });
304    }
305
306    /**
307     * Prepares dialog for creating new node.
308     *
309     */
310    public void showAddNodeDialog() {
311        jcrService().getPrimaryTypes(node.getRepository(),
312                node.getWorkspace(),
313                null,
314                false, new AsyncCallback<String[]>() {
315            @Override
316            public void onFailure(Throwable caught) {
317                SC.say(caught.getMessage());
318            }
319
320            @Override
321            public void onSuccess(String[] result) {
322                addNodeDialog.setPrimaryTypes(result);
323                addNodeDialog.showModal();
324            }
325        });
326    }
327
328    /**
329     * Initiates export dialog
330     */
331    public void showExportDialog() {
332        exportDialog.showModal();
333    }
334
335    /**
336     * Shows import node dialog
337     */
338    public void showImportDialog() {
339        importDialog.showModal();
340    }
341
342    /**
343     * Exports contents to the given file.
344     *
345     * @param name the name of the file.
346     * @param skipBinary
347     * @param noRecurse
348     */
349    public void exportXML(String name, boolean skipBinary, boolean noRecurse) {
350        console.jcrService().export(repository, workspace(), path(), name, true, true, new AsyncCallback<Object>() {
351            @Override
352            public void onFailure(Throwable caught) {
353                SC.say(caught.getMessage());
354            }
355
356            @Override
357            public void onSuccess(Object result) {
358                SC.say("Complete");
359            }
360        });
361    }
362
363    /**
364     * Imports contents from the given file.
365     *
366     * @param name
367     * @param option
368     */
369    public void importXML(String name, int option) {
370        console.jcrService().importXML(repository, workspace(), path(), name,
371                option, new AsyncCallback<Object>() {
372            @Override
373            public void onFailure(Throwable caught) {
374                SC.say(caught.getMessage());
375            }
376
377            @Override
378            public void onSuccess(Object result) {
379                SC.say("Complete");
380            }
381        });
382    }
383
384    public void removeNode(JcrNode node) {
385        final String parent = parent(node.getPath());
386        console.jcrService().removeNode(repository(), workspace(), node.getPath(), new AsyncCallback<Object>() {
387            @Override
388            public void onFailure(Throwable caught) {
389                SC.say(caught.getMessage());
390            }
391
392            @Override
393            public void onSuccess(Object result) {
394                session().setHasChanges(true);
395                getAndDisplayNode(parent, true);
396                updateControls();
397            }
398        });
399    }
400
401    public void addMixin(String name) {
402        console.jcrService().addMixin(repository(), workspace(), path(), name, new AsyncCallback<Object>() {
403            @Override
404            public void onFailure(Throwable caught) {
405                SC.say(caught.getMessage());
406            }
407
408            @Override
409            public void onSuccess(Object result) {
410                session().setHasChanges(true);
411                show();
412                updateControls();
413            }
414        });
415    }
416
417    public void removeMixin(String name) {
418        console.jcrService().removeMixin(repository(), workspace(), path(), name, new AsyncCallback<Object>() {
419            @Override
420            public void onFailure(Throwable caught) {
421                SC.say(caught.getMessage());
422            }
423
424            @Override
425            public void onSuccess(Object result) {
426                session().setHasChanges(true);
427                show();
428                updateControls();
429            }
430        });
431    }
432
433    public void setNodeProperty(JcrNode node, String name, Boolean value) {
434        console.jcrService().setProperty(node, name, value, new AsyncCallback<Object>() {
435            @Override
436            public void onFailure(Throwable caught) {
437                SC.say(caught.getMessage());
438            }
439
440            @Override
441            public void onSuccess(Object result) {
442                session().setHasChanges(true);
443                show();
444                updateControls();
445            }
446        });
447    }
448
449    public void setNodeProperty(JcrNode node, String name, Date value) {
450        console.jcrService().setProperty(node, name, value, new AsyncCallback<Object>() {
451            @Override
452            public void onFailure(Throwable caught) {
453                SC.say(caught.getMessage());
454            }
455
456            @Override
457            public void onSuccess(Object result) {
458                session().setHasChanges(true);
459                show();
460                updateControls();
461            }
462        });
463    }
464
465    public void setNodeProperty(JcrNode node, String name, String value) {
466        console.jcrService().setProperty(node, name, value, new AsyncCallback<Object>() {
467            @Override
468            public void onFailure(Throwable caught) {
469                SC.say(caught.getMessage());
470            }
471
472            @Override
473            public void onSuccess(Object result) {
474                session().setHasChanges(true);
475                show();
476                updateControls();
477            }
478        });
479    }
480
481    protected void addNode(String name, String primaryType) {
482        console.jcrService().addNode(repository(), workspace(), path(), name, primaryType, new AsyncCallback<JcrNode>() {
483            @Override
484            public void onFailure(Throwable caught) {
485                SC.say(caught.getMessage());
486            }
487
488            @Override
489            public void onSuccess(JcrNode node) {
490                session().setHasChanges(true);
491                getAndDisplayNode(path(), false);
492                updateControls();
493            }
494        });
495    }
496
497    protected void renameNode(JcrNode node, String name) {
498        console.jcrService().renameNode(repository(), workspace(), node.getPath(), name, new AsyncCallback<Object>() {
499            @Override
500            public void onFailure(Throwable caught) {
501                SC.say(caught.getMessage());
502            }
503
504            @Override
505            public void onSuccess(Object result) {
506                session().setHasChanges(true);
507                getAndDisplayNode(path(), false);
508                updateControls();
509            }
510        });
511    }
512
513    public void addAccessList(String name) {
514        console.jcrService().addAccessList(repository(), workspace(), path(), name, new AsyncCallback() {
515            @Override
516            public void onFailure(Throwable caught) {
517                SC.say(caught.getMessage());
518            }
519
520            @Override
521            public void onSuccess(Object result) {
522                session().setHasChanges(true);
523                getAndDisplayNode(path(), false);
524                updateControls();
525            }
526        });
527    }
528
529    public void updateAccessList(String principal, JcrPermission permission, boolean enabled) {
530        console.jcrService().updateAccessList(repository, workspace(), path(), principal, permission, enabled, new AsyncCallback<Object>() {
531            @Override
532            public void onFailure(Throwable caught) {
533                SC.say(caught.getMessage());
534            }
535
536            @Override
537            public void onSuccess(Object result) {
538                session().setHasChanges(true);
539                getAndDisplayNode(path(), false);
540                updateControls();
541            }
542        });
543    }
544
545    public void removeAccessList(String name) {
546        console.jcrService().removeAccessList(repository(), workspace(), path(), name, new AsyncCallback() {
547
548            @Override
549            public void onFailure(Throwable caught) {
550                SC.say(caught.getMessage());
551            }
552
553            @Override
554            public void onSuccess(Object result) {
555                session().setHasChanges(true);
556                getAndDisplayNode(path(), false);
557                updateControls();
558            }
559        });
560    }
561
562    public String repository() {
563        return repository;
564    }
565
566    public String workspace() {
567        return wsp.getSelectedWorkspace();
568    }
569
570    public JcrNode node() {
571        return node;
572    }
573
574    public String path() {
575        return path == null ? "/" : path;
576    }
577
578    private String parent(String path) {
579        if (path == null) {
580            return "/";
581        }
582
583        path = path.substring(0, path.lastIndexOf('/'));
584        if (path.length() == 0) {
585            return "/";
586        }
587
588        return path;
589    }
590
591    private void updateControls() {
592        wsp.setEnabled(session().hasChanges());
593    }
594    
595    private class Spacer extends VLayout {
596        public Spacer(int size) {
597            super();
598            setHeight(size);
599        }
600    }
601}