• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/exist-core/src/main/java/org/exist/client/DocumentView.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.client;
50

51
import java.awt.BorderLayout;
52
import java.awt.Cursor;
53
import java.awt.Dimension;
54
import java.awt.Toolkit;
55
import java.awt.event.KeyEvent;
56
import java.awt.event.WindowAdapter;
57
import java.awt.event.WindowEvent;
58
import java.io.*;
59
import java.net.URISyntaxException;
60
import java.net.URL;
61
import java.nio.charset.Charset;
62
import java.nio.file.Files;
63
import java.nio.file.Paths;
64
import java.util.Observable;
65
import java.util.Observer;
66
import java.util.Properties;
67

68
import javax.swing.BorderFactory;
69
import javax.swing.Box;
70
import javax.swing.ImageIcon;
71
import javax.swing.JButton;
72
import javax.swing.JDialog;
73
import javax.swing.JFileChooser;
74
import javax.swing.JFrame;
75
import javax.swing.JMenu;
76
import javax.swing.JMenuBar;
77
import javax.swing.JMenuItem;
78
import javax.swing.JOptionPane;
79
import javax.swing.JPopupMenu;
80
import javax.swing.JProgressBar;
81
import javax.swing.JScrollPane;
82
import javax.swing.JTextArea;
83
import javax.swing.JTextField;
84
import javax.swing.JToolBar;
85
import javax.swing.KeyStroke;
86
import javax.swing.border.BevelBorder;
87
import javax.xml.transform.OutputKeys;
88

89
import org.exist.security.Account;
90
import org.exist.storage.ElementIndex;
91
import org.exist.util.ProgressIndicator;
92
import org.exist.xmldb.UserManagementService;
93
import org.exist.xmldb.XmldbURI;
94
import org.exist.xquery.util.URIUtils;
95
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
96
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
97
import org.fife.ui.rtextarea.RTextScrollPane;
98
import org.xmldb.api.base.Collection;
99
import org.xmldb.api.base.Resource;
100
import org.xmldb.api.base.XMLDBException;
101
import org.xmldb.api.modules.XMLResource;
102

103
import static org.xmldb.api.base.ResourceType.XML_RESOURCE;
104

105
/**
106
 * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
107
 */
108
class DocumentView extends JFrame {
109

110
    private static final long serialVersionUID = 1L;
111

112
    protected InteractiveClient client;
113
    private XmldbURI resourceName;
114
    protected Resource resource;
115
    protected Collection collection;
116
    protected boolean readOnly = false;
×
117
    protected RSyntaxTextArea text;
118
    protected RTextScrollPane textScrollPane;
119
    protected JButton saveButton;
120
    protected JButton saveAsButton;
121
    protected JTextField statusMessage;
122
    protected JTextField positionDisplay;
123
    protected JProgressBar progress;
124
    protected JPopupMenu popup;
125
    protected Properties properties;
126

127
    public DocumentView(InteractiveClient client, XmldbURI resourceName, Resource resource, Properties properties) throws XMLDBException {
128
        super(URIUtils.urlDecodeUtf8(resourceName.lastSegment()));
×
129
        this.resourceName = resourceName;
×
130
        this.resource = resource;
×
131
        this.client = client;
×
132
        this.setIconImage(InteractiveClient.getElementalIcon(getClass()).getImage());
×
133
        this.collection = client.getCollection();
×
134
        this.properties = properties;
×
135
        getContentPane().setLayout(new BorderLayout());
×
136
        setupComponents();
×
137
        addWindowListener(new WindowAdapter() {
×
138
            public void windowClosing(WindowEvent ev) {
139
                close();
×
140
            }
×
141
        });
142
        pack();
×
143
    }
×
144

145
    public void viewDocument() {
146
        try {
147
            if (XML_RESOURCE.equals(resource.getResourceType()))
×
148
            {
149
                setText((String) resource.getContent());
×
150
            } else {
×
151
                setText(new String((byte[]) resource.getContent()));
×
152
            }
153

154
            // lock the resource for editing
155
            final UserManagementService service = client.current.getService(UserManagementService.class);
×
156
            final Account user = service.getAccount(properties.getProperty("user")); //$NON-NLS-1$
×
157
            final String lockOwner = service.hasUserLock(resource);
×
158
            if (lockOwner != null) {
×
159
                if (JOptionPane.showConfirmDialog(this,
×
160
                        Messages.getString("DocumentView.6") + lockOwner + //$NON-NLS-1$
×
161
                                Messages.getString("DocumentView.7"), //$NON-NLS-1$
×
162
                        Messages.getString("DocumentView.8"), //$NON-NLS-1$
×
163
                        JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
×
164
                    dispose();
×
165
                    this.setCursor(Cursor.getDefaultCursor());
×
166
                    return;
×
167
                }
168
            }
169

170
            try {
171
                service.lockResource(resource, user);
×
172
            } catch (final XMLDBException ex) {
×
173
                System.out.println(ex.getMessage());
×
174
                JOptionPane.showMessageDialog(this,
×
175
                        Messages.getString("DocumentView.9")); //$NON-NLS-1$
×
176
                setReadOnly();
×
177
            }
178
            setVisible(true);
×
179
        } catch (final XMLDBException ex) {
×
180
            showErrorMessage(Messages.getString("DocumentView.10") + ex.getMessage(), ex); //$NON-NLS-1$
×
181
        }
182
    }
×
183

184

185
    private static void showErrorMessage(String message, Throwable t) {
186
        JScrollPane scroll = null;
×
187
        final JTextArea msgArea = new JTextArea(message);
×
188
        msgArea.setBorder(BorderFactory.createTitledBorder("Message:")); //$NON-NLS-1$
×
189
        msgArea.setEditable(false);
×
190
        msgArea.setBackground(null);
×
191
        if (t != null) {
×
192
            final StringWriter out = new StringWriter();
×
193
            final PrintWriter writer = new PrintWriter(out);
×
194
            t.printStackTrace(writer);
×
195
            final JTextArea stacktrace = new JTextArea(out.toString(), 20, 50);
×
196
            stacktrace.setBackground(null);
×
197
            stacktrace.setEditable(false);
×
198
            scroll = new JScrollPane(stacktrace);
×
199
            scroll.setPreferredSize(new Dimension(250, 300));
×
200
            scroll.setBorder(BorderFactory
×
201
                    .createTitledBorder("Exception Stacktrace:")); //$NON-NLS-1$
×
202
        }
203
        final JOptionPane optionPane = new JOptionPane();
×
204
        optionPane.setMessage(new Object[]{msgArea, scroll});
×
205
        optionPane.setMessageType(JOptionPane.ERROR_MESSAGE);
×
206
        final JDialog dialog = optionPane.createDialog(null, Messages.getString("DocumentView.13")); //$NON-NLS-1$
×
207
        dialog.setResizable(true);
×
208
        dialog.pack();
×
209
        dialog.setVisible(true);
×
210
        return;
×
211
    }
212

213
    public void setReadOnly() {
214
        text.setEditable(false);
×
215
        saveButton.setEnabled(false);
×
216
        readOnly = true;
×
217
    }
×
218

219
    private void close() {
220
        unlockView();
×
221
    }
×
222

223
    private void unlockView() {
224
        if (readOnly) {
×
225
            return;
×
226
        }
227
        try {
228
            final UserManagementService service = collection.getService(UserManagementService.class);
×
229
            service.unlockResource(resource);
×
230
        } catch (final XMLDBException e) {
×
231
            e.printStackTrace();
×
232
        }
233
    }
×
234

235
    private void setupComponents() throws XMLDBException {
236

237
        /* start of menubar */
238
        final JMenuBar menubar = new JMenuBar();
×
239

240
        final JMenu fileMenu = new JMenu(Messages.getString("DocumentView.16")); //$NON-NLS-1$
×
241
        fileMenu.setMnemonic(KeyEvent.VK_F);
×
242
        menubar.add(fileMenu);
×
243

244
        JMenuItem item;
245
        // Save to database
246
        item = new JMenuItem(Messages.getString("DocumentView.17"), KeyEvent.VK_S); //$NON-NLS-1$
×
247
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
×
248
                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
×
249
        item.addActionListener(e -> save());
×
250
        fileMenu.add(item);
×
251
        /*
252
        // Refresh
253
        item = new JMenuItem("Refresh", KeyEvent.VK_R);
254
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R,
255
                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
256
        item.addActionListener(new ActionListener() {
257
            public void actionPerformed(ActionEvent e) {
258
                try {
259
                    refresh() ;
260
                } catch (XMLDBException u) {
261
                    u.printStackTrace();
262
                }
263
            }
264
        });
265
        fileMenu.add(item);
266
        */
267

268
        setJMenuBar(menubar);
×
269
        /* end of menubar */
270
        
271
        /* The icon toolbar */
272

273
        final JToolBar toolbar = new JToolBar();
×
274

275
        //Save button
276
        URL url = getClass().getResource("icons/Save24.gif"); //$NON-NLS-1$
×
277
        saveButton = new JButton(new ImageIcon(url));
×
278
        saveButton
×
279
                .setToolTipText(Messages.getString("DocumentView.20")); //$NON-NLS-1$
×
280
        saveButton.addActionListener(e -> save());
×
281
        toolbar.add(saveButton);
×
282

283
        //Save As button
284
        url = getClass().getResource("icons/SaveAs24.gif"); //$NON-NLS-1$
×
285
        saveAsButton = new JButton(new ImageIcon(url));
×
286
        saveAsButton
×
287
                .setToolTipText(Messages.getString("DocumentView.22")); //$NON-NLS-1$
×
288
        saveAsButton.addActionListener(e -> saveAs());
×
289
        toolbar.add(saveAsButton);
×
290

291
        //Export button
292
        url = getClass().getResource("icons/Export24.gif"); //$NON-NLS-1$
×
293
        JButton button = new JButton(new ImageIcon(url));
×
294
        button.setToolTipText(Messages.getString("DocumentView.24")); //$NON-NLS-1$
×
295
        button.addActionListener(e -> {
×
296
            try {
297
                export();
×
298
            } catch (final XMLDBException u) {
×
299
                u.printStackTrace();
×
300
            }
301
        });
×
302
        toolbar.add(button);
×
303

304
        toolbar.addSeparator();
×
305

306
        //Copy button
307
        url = getClass().getResource("icons/Copy24.gif"); //$NON-NLS-1$
×
308
        button = new JButton(new ImageIcon(url));
×
309
        button.setToolTipText(Messages.getString("DocumentView.26")); //$NON-NLS-1$
×
310
        button.addActionListener(e -> text.copy());
×
311
        toolbar.add(button);
×
312

313
        //Cut button
314
        url = getClass().getResource("icons/Cut24.gif"); //$NON-NLS-1$
×
315
        button = new JButton(new ImageIcon(url));
×
316
        button.setToolTipText(Messages.getString("DocumentView.28")); //$NON-NLS-1$
×
317
        button.addActionListener(e -> text.cut());
×
318
        toolbar.add(button);
×
319

320
        //Paste button
321
        url = getClass().getResource("icons/Paste24.gif"); //$NON-NLS-1$
×
322
        button = new JButton(new ImageIcon(url));
×
323
        button.setToolTipText(Messages.getString("DocumentView.30")); //$NON-NLS-1$
×
324
        button.addActionListener(e -> text.paste());
×
325
        toolbar.add(button);
×
326

327
        toolbar.addSeparator();
×
328

329
        //Refresh button
330
        url = getClass().getResource("icons/Refresh24.gif"); //$NON-NLS-1$
×
331
        button = new JButton(new ImageIcon(url));
×
332
        button.setToolTipText(Messages.getString("DocumentView.32")); //$NON-NLS-1$
×
333
        button.addActionListener(e -> {
×
334
            try {
335
                refresh();
×
336
            } catch (final XMLDBException u) {
×
337
                u.printStackTrace();
×
338
            }
339
        });
×
340
        toolbar.add(button);
×
341

342
        getContentPane().add(toolbar, BorderLayout.NORTH);
×
343
        text = new RSyntaxTextArea(14, 80);
×
344
        text.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML);
×
345
        text.setCodeFoldingEnabled(true);
×
346
        textScrollPane = new RTextScrollPane(text);
×
347

348
        getContentPane().add(textScrollPane, BorderLayout.CENTER);
×
349
        final Box statusbar = Box.createHorizontalBox();
×
350
        statusbar.setBorder(BorderFactory
×
351
                .createBevelBorder(BevelBorder.LOWERED));
×
352
        statusMessage = new JTextField(20);
×
353
        statusMessage.setEditable(false);
×
354
        statusMessage.setFocusable(false);
×
355
        statusMessage.setText(Messages.getString("DocumentView.34") + URIUtils.urlDecodeUtf8(resource.getId()) + Messages.getString("DocumentView.35")); //$NON-NLS-1$ //$NON-NLS-2$
×
356
        statusbar.add(statusMessage);
×
357
        progress = new JProgressBar();
×
358
        progress.setPreferredSize(new Dimension(200, 30));
×
359
        progress.setVisible(false);
×
360
        statusbar.add(progress);
×
361
        positionDisplay = new JTextField(5);
×
362
        positionDisplay.setEditable(false);
×
363
        positionDisplay.setFocusable(true);
×
364
        statusbar.add(positionDisplay);
×
365
        text.addCaretListener(e -> {
×
366
            final RSyntaxTextArea txt = (RSyntaxTextArea) e.getSource();
×
367
            positionDisplay.setText("Line: " + (txt.getCaretLineNumber() + 1) + " Column:" + (txt.getCaretOffsetFromLineStart() + 1));
×
368
        });
×
369
        getContentPane().add(statusbar, BorderLayout.SOUTH);
×
370
    }
×
371

372
    private void save() {
373
        final Runnable saveTask = () -> {
×
374
            try {
375
                statusMessage.setText(Messages.getString("DocumentView.36") + URIUtils.urlDecodeUtf8(resource.getId())); //$NON-NLS-1$
×
376
                if (collection instanceof Observable) {
×
377
                    ((Observable) collection)
×
378
                            .addObserver(new ProgressObserver());
×
379
                }
380
                progress.setIndeterminate(true);
×
381
                progress.setVisible(true);
×
382
                resource.setContent(text.getText());
×
383
                collection.storeResource(resource);
×
384
                if (collection instanceof Observable) {
×
385
                    ((Observable) collection).deleteObservers();
×
386
                }
387
            } catch (final XMLDBException e) {
×
388
                ClientFrame.showErrorMessage(Messages.getString("DocumentView.37") //$NON-NLS-1$
×
389
                        + e.getMessage(), e);
×
390
            } finally {
391
                progress.setVisible(false);
×
392
            }
393
        };
×
394
        client.newClientThread("save", saveTask).start();
×
395
    }
×
396

397
    private void saveAs() {
398
        final Runnable saveAsTask = () -> {
×
399

400
            //Get the name to save the resource as
401
            final String nameres = JOptionPane.showInputDialog(null, Messages.getString("DocumentView.38")); //$NON-NLS-1$
×
402
            if (nameres != null) {
×
403
                try {
404
                    //Change status message and display a progress dialog
405
                    statusMessage.setText(Messages.getString("DocumentView.39") + nameres); //$NON-NLS-1$
×
406
                    if (collection instanceof Observable) {
×
407
                        ((Observable) collection).addObserver(new ProgressObserver());
×
408
                    }
409
                    progress.setIndeterminate(true);
×
410
                    progress.setVisible(true);
×
411

412
                    //Create a new resource as named, set the content, store the resource
413
                    XMLResource result = null;
×
414
                    result = collection.createResource(URIUtils.encodeXmldbUriFor(nameres).toString(), XMLResource.class);
×
415
                    result.setContent(text.getText());
×
416
                    collection.storeResource(result);
×
417
                    client.reloadCollection();    //reload the client collection
×
418
                    if (collection instanceof Observable) {
×
419
                        ((Observable) collection).deleteObservers();
×
420
                    }
421
                } catch (final XMLDBException e) {
×
422
                    ClientFrame.showErrorMessage(Messages.getString("DocumentView.40") + e.getMessage(), e); //$NON-NLS-1$
×
423
                } catch (final URISyntaxException e) {
×
424
                    ClientFrame.showErrorMessage(Messages.getString("DocumentView.41") + e.getMessage(), e); //$NON-NLS-1$
×
425
                } finally {
426
                    //hide the progress dialog
427
                    progress.setVisible(false);
×
428
                }
429
            }
430
        };
×
431
        client.newClientThread("save-as", saveAsTask).start();
×
432
    }
×
433

434
    private void export() throws XMLDBException {
435
        final String workDir = properties.getProperty("working-dir", System.getProperty("user.dir"));
×
436
        final JFileChooser chooser = new JFileChooser(workDir);
×
437
        chooser.setMultiSelectionEnabled(false);
×
438
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
×
439
        chooser.setSelectedFile(Paths.get(resource.getId()).toFile());
×
440

441
        if (chooser.showDialog(this, Messages.getString("DocumentView.44")) == JFileChooser.APPROVE_OPTION) {
×
442
            final File file = chooser.getSelectedFile();
×
443
            if (file.exists()
×
444
                    && JOptionPane.showConfirmDialog(this,
×
445
                    Messages.getString("DocumentView.45"), Messages.getString("DocumentView.46"),
×
446
                    JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {
×
447
                return;
×
448
            }
449

450
            final Charset encoding = Charset.forName(properties.getProperty("encoding"));
×
451
            try (final Writer writer = Files.newBufferedWriter(file.toPath(), encoding)) {
×
452
                writer.write(text.getText());
×
453

454
            } catch (final IOException e) {
×
455
                ClientFrame.showErrorMessage(Messages.getString("DocumentView.48") + e.getMessage(), e);
×
456
            }
457

458
            final File selectedDir = chooser.getCurrentDirectory();
×
459
            properties.setProperty("working-dir", selectedDir.getAbsolutePath());
×
460
        }
461
    }
×
462

463
    private void refresh() throws XMLDBException {
464
        //First unlock the resource
465
        unlockView();
×
466

467
        //Reload the resource
468
        this.resource = client.retrieve(resourceName, properties.getProperty(OutputKeys.INDENT, "yes")); //$NON-NLS-1$
×
469

470
        //View and lock the resource
471
        viewDocument();
×
472
    }
×
473

474
    public void setText(String content) throws XMLDBException {
475
        text.setText(content);
×
476
        text.setCaretPosition(0);
×
477
        statusMessage.setText(Messages.getString("DocumentView.52") + XmldbURI.create(client.getCollection().getName()).append(resourceName) + Messages.getString("DocumentView.53") + properties.getProperty("uri")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
×
478
    }
×
479

480
    class ProgressObserver implements Observer {
×
481
        public void update(Observable o, Object arg) {
482
            progress.setIndeterminate(false);
×
483
            final ProgressIndicator ind = (ProgressIndicator) arg;
×
484
            progress.setValue(ind.getPercentage());
×
485

486
            if (o instanceof ElementIndex) {
×
487
                progress.setString("Storing elements"); //$NON-NLS-1$
×
488
            } else {
×
489
                progress.setString("Storing nodes"); //$NON-NLS-1$
×
490
            }
491
        }
×
492
    }
493
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc