• 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

18.08
/exist-core/src/main/java/org/exist/client/InteractiveClient.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.Dimension;
52
import java.io.*;
53
import java.lang.reflect.Field;
54
import java.net.URISyntaxException;
55
import java.net.URLDecoder;
56
import java.nio.charset.Charset;
57
import java.nio.charset.StandardCharsets;
58
import java.nio.file.Files;
59
import java.nio.file.Path;
60
import java.nio.file.Paths;
61
import java.time.Instant;
62
import java.time.format.DateTimeFormatter;
63
import java.util.*;
64
import java.util.concurrent.atomic.AtomicReference;
65
import java.util.function.BinaryOperator;
66
import java.util.function.UnaryOperator;
67
import java.util.regex.Pattern;
68
import java.util.stream.Stream;
69
import java.util.zip.ZipEntry;
70
import java.util.zip.ZipFile;
71
import javax.swing.ImageIcon;
72
import javax.swing.UIManager;
73
import javax.swing.UnsupportedLookAndFeelException;
74
import javax.xml.XMLConstants;
75
import javax.xml.parsers.DocumentBuilder;
76
import javax.xml.parsers.DocumentBuilderFactory;
77
import javax.xml.transform.OutputKeys;
78

79
import org.apache.tools.ant.DirectoryScanner;
80
import org.exist.SystemProperties;
81
import org.exist.dom.persistent.XMLUtil;
82
import org.exist.security.Account;
83
import org.exist.security.Group;
84
import org.exist.security.Permission;
85
import org.exist.security.SecurityManager;
86
import org.exist.security.internal.aider.UserAider;
87
import org.exist.start.CompatibleJavaVersionCheck;
88
import org.exist.start.StartException;
89
import org.exist.storage.ElementIndex;
90
import org.exist.util.*;
91
import org.exist.util.serializer.SAXSerializer;
92
import org.exist.util.serializer.SerializerPool;
93
import org.exist.xmldb.EXistCollectionManagementService;
94
import org.exist.xmldb.DatabaseInstanceManager;
95
import org.exist.xmldb.EXistResource;
96
import org.exist.xmldb.ExtendedResource;
97
import org.exist.xmldb.IndexQueryService;
98
import org.exist.xmldb.UserManagementService;
99
import org.exist.xmldb.EXistXPathQueryService;
100
import org.exist.xmldb.XmldbURI;
101
import org.exist.xquery.Constants;
102
import org.jline.reader.*;
103
import org.jline.reader.impl.history.DefaultHistory;
104
import org.jline.terminal.Terminal;
105
import org.jline.terminal.TerminalBuilder;
106
import org.w3c.dom.Document;
107
import org.w3c.dom.Element;
108
import org.w3c.dom.Node;
109
import org.w3c.dom.NodeList;
110
import org.xml.sax.SAXException;
111
import org.xml.sax.helpers.AttributesImpl;
112
import org.xmldb.api.DatabaseManager;
113
import org.xmldb.api.base.*;
114
import org.xmldb.api.base.Collection;
115
import org.xmldb.api.modules.BinaryResource;
116
import org.xmldb.api.modules.XUpdateQueryService;
117
import se.softhouse.jargo.ArgumentException;
118

119
import static java.nio.charset.StandardCharsets.UTF_8;
120
import static java.time.ZoneOffset.UTC;
121
import static javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION;
122
import static org.exist.storage.serializers.EXistOutputKeys.OMIT_ORIGINAL_XML_DECLARATION;
123
import static org.exist.storage.serializers.EXistOutputKeys.OUTPUT_DOCTYPE;
124
import static org.xmldb.api.base.ResourceType.XML_RESOURCE;
125

126
/**
127
 * Command-line client based on the XML:DB API.
128
 *
129
 * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
130
 * @author wolf
131
 */
132
public class InteractiveClient {
133

134
    static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(UTC);
1✔
135

136
    // ANSI colors for ls display
137
    // private final static String ANSI_BLUE = "\033[0;34m";
138
    private static final String ANSI_CYAN = "\033[0;36m";
139
    private static final String ANSI_WHITE = "\033[0;37m";
140

141
    private static final String EOL = System.getProperty("line.separator");
1✔
142
    private static final Pattern UNKNOWN_USER_PATTERN = Pattern.compile("User .* unknown");
1✔
143
    private static final String DONE = "done.";
144

145
    // keys
146
    public static final String USER = "user";
147
    public static final String PASSWORD = "password";
148
    public static final String URI = "uri";
149
    public static final String PERMISSIONS = "permissions";
150
    public static final String INDENT = "indent";
151
    public static final String ENCODING = "encoding";
152
    public static final String COLORS = "colors";
153
    public static final String EDITOR = "editor";
154
    public static final String EXPAND_XINCLUDES = "expand-xincludes";
155
    public static final String CONFIGURATION = "configuration";
156
    public static final String DRIVER = "driver";
157
    public static final String SSL_ENABLE = "ssl-enable";
158
    public static final String CREATE_DATABASE = "create-database";
159
    public static final String LOCAL_MODE = "local-mode-opt";
160
    public static final String NO_EMBED_MODE = "NO_EMBED_MODE";
161

162
    // values
163
    protected static final String EDIT_CMD = "emacsclient -t $file";
164
    protected static final Charset ENCODING_DEFAULT = StandardCharsets.UTF_8;
1✔
165
    protected static final String URI_DEFAULT = "xmldb:exist://localhost:8080/exist/xmlrpc";
166
    protected static final String SSL_ENABLE_DEFAULT = "FALSE";
167
    protected static final String LOCAL_MODE_DEFAULT = "FALSE";
168
    protected static final String NO_EMBED_MODE_DEFAULT = "FALSE";
169
    protected static final String USER_DEFAULT = SecurityManager.DBA_USER;
170
    protected static final String DRIVER_IMPL_CLASS = "org.exist.xmldb.DatabaseImpl";
171

172
    // Set properties
173
    private static final Properties DEFAULT_PROPERTIES = new Properties();
1✔
174
    static {
175
        DEFAULT_PROPERTIES.setProperty(DRIVER, DRIVER_IMPL_CLASS);
1✔
176
        DEFAULT_PROPERTIES.setProperty(URI, URI_DEFAULT);
1✔
177
        DEFAULT_PROPERTIES.setProperty(USER, USER_DEFAULT);
1✔
178
        DEFAULT_PROPERTIES.setProperty(EDITOR, EDIT_CMD);
1✔
179
        DEFAULT_PROPERTIES.setProperty(INDENT, "true");
1✔
180
        DEFAULT_PROPERTIES.setProperty(OMIT_XML_DECLARATION, "no");
1✔
181
        DEFAULT_PROPERTIES.setProperty(OMIT_ORIGINAL_XML_DECLARATION, "no");
1✔
182
        DEFAULT_PROPERTIES.setProperty(OUTPUT_DOCTYPE, "true");
1✔
183
        DEFAULT_PROPERTIES.setProperty(ENCODING, ENCODING_DEFAULT.name());
1✔
184
        DEFAULT_PROPERTIES.setProperty(COLORS, "false");
1✔
185
        DEFAULT_PROPERTIES.setProperty(PERMISSIONS, "false");
1✔
186
        DEFAULT_PROPERTIES.setProperty(EXPAND_XINCLUDES, "true");
1✔
187
        DEFAULT_PROPERTIES.setProperty(SSL_ENABLE, SSL_ENABLE_DEFAULT);
1✔
188
    }
189
    protected static final int[] COL_SIZES = new int[]{10, 10, 10, -1};
1✔
190

191
    protected static String configuration = null;
1✔
192

193
    protected final TreeSet<String> completions = new TreeSet<>();
1✔
194
    protected final LinkedList<String> queryHistory = new LinkedList<>();
1✔
195
    protected final Properties properties = new Properties(DEFAULT_PROPERTIES);
1✔
196
    protected final Map<String, String> namespaceMappings = new HashMap<>();
1✔
197

198
    protected Path queryHistoryFile;
199
    protected Path historyFile;
200

201
    protected LineReader console = null;
1✔
202

203
    private Database database = null;
1✔
204
    protected Collection current = null;
1✔
205
    protected int nextInSet = 1;
1✔
206

207
    protected String[] resources = null;
1✔
208
    protected ResourceSet result = null;
1✔
209

210
    /**
211
     * number of files of a recursive store
212
     */
213
    protected int filesCount = 0;
1✔
214

215
    /**
216
     * total length of a recursive store
217
     */
218
    protected long totalLength = 0;
1✔
219

220
    protected ClientFrame frame;
221

222
    //*************************************
223

224
    private final CommandlineOptions options;
225
    protected XmldbURI path = XmldbURI.ROOT_COLLECTION_URI;
1✔
226
    private Optional<Writer> lazyTraceWriter = Optional.empty();
1✔
227

228
    public InteractiveClient(CommandlineOptions options) {
1✔
229
        this.options = options;
1✔
230
    }
1✔
231

232
    /**
233
     * Display help on commands
234
     */
235
    protected void displayHelp() {
236
        messageln("--- general commands ---");
1✔
237
        messageln("ls                   list collection contents");
1✔
238
        messageln("cd [collection|..]   change current collection");
1✔
239
        messageln("put [file pattern]   upload file or directory to the database");
1✔
240
        messageln("putgz [file pattern] upload possibly gzip compressed file or directory to the database");
1✔
241
        messageln("putzip [file pattern] upload the contents of a ZIP archive to the database");
1✔
242
        messageln("edit [resource] open the resource for editing");
1✔
243
        messageln("mkcol collection     create new sub-collection in current collection");
1✔
244
        messageln("rm document          remove document from current collection");
1✔
245
        messageln("rmcol collection     remove collection");
1✔
246
        messageln("set [key=value]      set property. Calling set without ");
1✔
247
        messageln("                     argument shows current settings.");
1✔
248
        messageln(EOL + "--- search commands ---");
1✔
249
        messageln("find xpath-expr      execute the given XPath expression.");
1✔
250
        messageln("show [position]      display query result value at position.");
1✔
251
        messageln(EOL + "--- user management (may require dba rights) ---");
1✔
252
        messageln("users                list existing users.");
1✔
253
        messageln("adduser username     create a new user.");
1✔
254
        messageln("passwd username      change password for user. ");
1✔
255
        messageln("chown user group [resource]");
1✔
256
        messageln("                     change resource ownership. chown without");
1✔
257
        messageln("                     resource changes ownership of the current");
1✔
258
        messageln("                     collection.");
1✔
259
        messageln("chmod [resource] permissions");
1✔
260
        messageln("                     change resource permissions. Format:");
1✔
261
        messageln("                     [user|group|other]=[+|-][read|write|execute].");
1✔
262
        messageln("                     chmod without resource changes permissions for");
1✔
263
        messageln("                     the current collection.");
1✔
264
        messageln("lock resource        put a write lock on the specified resource.");
1✔
265
        messageln("unlock resource      remove a write lock from the specified resource.");
1✔
266
        messageln("quit                 quit the program");
1✔
267
    }
1✔
268

269
    /**
270
     * The main program for the InteractiveClient class.
271
     *
272
     * @param args The command line arguments
273
     */
274
    public static void main(final String[] args) {
275
        try {
276
            CompatibleJavaVersionCheck.checkForCompatibleJavaVersion();
×
277

278
            // parse command-line options
279
            final CommandlineOptions options = CommandlineOptions.parse(args);
×
280
            final InteractiveClient client = new InteractiveClient(options);
×
281
            if (!client.run()) {
×
282
                System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE); // return non-zero exit status on failure
×
283
            }
284

285
        } catch (final StartException e) {
×
286
            if (e.getMessage() != null && !e.getMessage().isEmpty()) {
×
287
                consoleErr(e.getMessage());
×
288
            }
289
            System.exit(e.getErrorCode());
×
290

291
        } catch (final ArgumentException e) {
×
292
            consoleOut(e.getMessageAndUsage().toString());
×
293
            System.exit(SystemExitCodes.INVALID_ARGUMENT_EXIT_CODE);
×
294

295
        } catch (final Exception e) {
×
296
            e.printStackTrace();
×
297
            System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE); // return non-zero exit status on exception
×
298
        }
299
    }
×
300

301
    /**
302
     * Create a new thread for this client instance.
303
     *
304
     * @param threadName the name of the thread
305
     * @param runnable   the function to execute on the thread
306
     * @return the thread
307
     */
308
    Thread newClientThread(final String threadName, final Runnable runnable) {
309
        return new Thread(runnable, "java-admin-client." + threadName);
×
310
    }
311

312
    /**
313
     * Register XML:DB driver and retrieve root collection.
314
     *
315
     * @throws Exception Description of the Exception
316
     */
317
    protected void connect() throws Exception {
318
        consoleOut("Connecting to database...");
×
319

320
        final String uri = properties.getProperty(InteractiveClient.URI);
×
321
        if (options.startGUI && frame != null) {
×
322
            frame.setStatus("connecting to " + uri);
×
323
        }
324

325
        // Create database
326
        final Class<?> cl = Class.forName(properties.getProperty(DRIVER));
×
327
        database = (Database) cl.getConstructor().newInstance();
×
328

329
        // Configure database
330
        database.setProperty(CREATE_DATABASE, "true");
×
331
        database.setProperty(SSL_ENABLE, properties.getProperty(SSL_ENABLE));
×
332

333
        // secure empty configuration
334
        final String configProp = properties.getProperty(InteractiveClient.CONFIGURATION);
×
335

336
        if (configProp != null && (!configProp.isEmpty())) {
×
337
            database.setProperty(CONFIGURATION, configProp);
×
338
        }
339

340
        DatabaseManager.registerDatabase(database);
×
341

342
        final String collectionUri = uri + path;
×
343
        current = DatabaseManager.getCollection(collectionUri, properties.getProperty(USER), properties.getProperty(PASSWORD));
×
344
        if (options.startGUI && frame != null) {
×
345
            frame.setStatus("connected to " + uri + " as user " + properties.getProperty(USER));
×
346
        }
347

348
        if (database.getProperty(CONFIGURATION) != null) {
×
349
            consoleOut("Using config: " + database.getProperty(CONFIGURATION));
×
350
        }
351

352
        consoleOut("Connected :-)");
×
353
    }
×
354

355
    /**
356
     * Returns the current collection.
357
     *
358
     * @return the current collection
359
     */
360
    protected Collection getCollection() {
361
        return current;
1✔
362
    }
363

364
    public Properties getProperties() {
365
        return properties;
1✔
366
    }
367

368
    public void reloadCollection() throws XMLDBException {
369
        current = DatabaseManager.getCollection(properties.getProperty(URI)
×
370
                        + path, properties.getProperty(USER),
×
371
                properties.getProperty(PASSWORD));
×
372
        getResources();
×
373
    }
×
374

375
    protected void setProperties() throws XMLDBException {
376
        for (Map.Entry<Object, Object> properry : properties.entrySet()) {
1✔
377
            current.setProperty((String) properry.getKey(), (String) properry.getValue());
1✔
378
        }
379
    }
1✔
380

381
    private String getOwnerName(final Permission perm) {
382
        return Optional.ofNullable(perm).map(Permission::getOwner).map(Account::getName).orElse("?");
1✔
383
    }
384

385
    private String getGroupName(final Permission perm) {
386
        return Optional.ofNullable(perm).map(Permission::getGroup).map(Group::getName).orElse("?");
1✔
387
    }
388

389
    /**
390
     * Get list of resources contained in collection.
391
     *
392
     * @throws XMLDBException Description of the Exception
393
     */
394
    protected void getResources() throws XMLDBException {
395
        if (current == null) {
1✔
396
            return;
1✔
397
        }
398
        setProperties();
1✔
399
        final UserManagementService mgtService = current.getService(UserManagementService.class);
1✔
400
        final List<String> childCollections = current.listChildCollections();
1✔
401
        final List<String> childResources = current.listResources();
1✔
402

403
        resources = new String[childCollections.size() + childResources.size()];
1✔
404
        //Collection child;
405
        Permission perm;
406

407
        final List<ResourceDescriptor> tableData = new ArrayList<>(resources.length); // A list of ResourceDescriptor for the GUI
1✔
408

409
        int i = 0;
1✔
410
        for (String collectionName : childCollections) {
1✔
411
            perm = mgtService.getSubCollectionPermissions(current, collectionName);
1✔
412

413
            final Instant created = mgtService.getSubCollectionCreationTime(current, collectionName);
1✔
414

415
            if ("true".equals(properties.getProperty(PERMISSIONS))) {
1✔
416
                resources[i] = 'c' + perm.toString() + '\t' + getOwnerName(perm)
1✔
417
                        + '\t' + getGroupName(perm) + '\t'
1✔
418
                        + DATE_TIME_FORMATTER.format(created) + '\t'
1✔
419
                        + collectionName;
1✔
420
            } else {
1✔
421
                resources[i] = collectionName;
1✔
422
            }
423

424
            if (options.startGUI) {
1!
425
                try {
426
                    tableData.add(
1✔
427
                            new ResourceDescriptor.Collection(
1✔
428
                                    XmldbURI.xmldbUriFor(collectionName),
1✔
429
                                    perm,
1✔
430
                                    created
1✔
431
                            )
432
                    );
433
                } catch (final URISyntaxException e) {
1✔
434
                    errorln("could not parse collection name into a valid URI: " + e.getMessage());
×
435
                }
436
            }
437
            completions.add(collectionName);
1✔
438
            i++;
1✔
439
        }
440
        for (String resourceId : childResources) {
1✔
441
            try (final Resource res = current.getResource(resourceId)) {
1✔
442
                perm = mgtService.getPermissions(res);
1✔
443
                if (perm == null) {
1!
444
                    errorln("no permissions found for resource " + resourceId);
×
445
                }
446

447
                final Instant lastModificationTime = res.getLastModificationTime();
1✔
448

449
                if ("true".equals(properties.getProperty(PERMISSIONS))) {
1✔
450
                    resources[i] = '-' + perm.toString() + '\t' + getOwnerName(perm)
1✔
451
                            + '\t' + getGroupName(perm) + '\t'
1✔
452
                            + DATE_TIME_FORMATTER.format(lastModificationTime) + '\t'
1✔
453
                            + resourceId;
1✔
454
                } else {
1✔
455
                    resources[i] = resourceId;
1✔
456
                }
457

458
                if (options.startGUI) {
1!
459
                    try {
460
                        tableData.add(
1✔
461
                                new ResourceDescriptor.Document(
1✔
462
                                        XmldbURI.xmldbUriFor(resourceId),
1✔
463
                                        perm,
1✔
464
                                        lastModificationTime
1✔
465
                                )
466
                        );
467
                    } catch (final URISyntaxException e) {
1✔
468
                        errorln("could not parse document name into a valid URI: " + e.getMessage());
×
469
                    }
470
                }
471
                completions.add(resourceId);
1✔
472
            }
473
            i++;
1✔
474
        }
475
        if (options.startGUI) {
1!
476
            frame.setResources(tableData);
1✔
477
        }
478
    }
1✔
479

480
    /**
481
     * Display document on screen, by 24 lines.
482
     *
483
     * @param str string containing the document.
484
     */
485
    protected void more(final String str) {
486
        final LineNumberReader reader = new LineNumberReader(new StringReader(str));
×
487
        String line;
488
        // int count = 0;
489
        int ch;
490
        try {
491
            while (System.in.available() > 0) {
×
492
                System.in.read();
×
493
            }
494

495
            while ((line = reader.readLine()) != null) {
×
496
                if (reader.getLineNumber() % 24 == 0) {
×
497
                    consoleOut("line: " + reader.getLineNumber()
×
498
                            + "; press [return] for more or [q] for quit.");
499
                    ch = System.in.read();
×
500
                    if (ch == 'q' || ch == 'Q') {
×
501
                        return;
×
502
                    }
503
                }
504
                consoleOut(line);
×
505
            }
506
        } catch (final IOException ioe) {
×
507
            consoleErr("IOException: " + ioe);
×
508
        }
509
    }
×
510

511
    /**
512
     * In interactive mode, process a line entered by the user.
513
     *
514
     * @param line the line entered
515
     * @return true if command != quit
516
     */
517
    protected boolean process(final String line) {
518
        if (options.startGUI) {
×
519
            frame.setPath(path);
×
520
        }
521
        final String args[];
522
        if (line.startsWith("find")) {
×
523
            args = new String[2];
×
524
            args[0] = "find";
×
525
            args[1] = line.substring(5);
×
526
        } else {
×
527
            final StreamTokenizer tok = new StreamTokenizer(new StringReader(line));
×
528
            tok.resetSyntax();
×
529
            tok.wordChars(0x21, 0x7FFF);
×
530
            tok.quoteChar('"');
×
531
            tok.whitespaceChars(0x20, 0x20);
×
532

533
            final List<String> argList = new ArrayList<>(3);
×
534
            // int i = 0;
535
            int token;
536
            try {
537
                while ((token = tok.nextToken()) != StreamTokenizer.TT_EOF) {
×
538
                    if (token == StreamTokenizer.TT_WORD || token == '"') {
×
539
                        argList.add(tok.sval);
×
540
                    }
541
                }
542
            } catch (final IOException e) {
×
543
                consoleErr("Could not parse command line.");
×
544
                return true;
×
545
            }
546
            args = new String[argList.size()];
×
547
            argList.toArray(args);
×
548
        }
549

550
        if (args.length == 0) {
×
551
            return true;
×
552
        }
553

554
        try {
555
            XmldbURI newPath = path;
×
556
            final XmldbURI currUri = XmldbURI.xmldbUriFor(properties.getProperty(URI)).resolveCollectionPath(path);
×
557
            if (args[0].equalsIgnoreCase("ls")) {
×
558
                // list collection contents
559
                getResources();
×
560
                if ("true".equals(properties.getProperty(PERMISSIONS))) {
×
561
                    for (String resource : resources) {
×
562
                        messageln(resource);
×
563
                    }
564
                } else {
×
565
                    for (int i = 0; i < resources.length; i++) {
×
566
                        final StringBuilder buf = new StringBuilder();
×
567
                        int k = 0;
×
568
                        for (int j = 0; i < resources.length && j < 5; i++, j++) {
×
569
                            buf.append(resources[i]);
×
570
                            buf.append('\t');
×
571
                            k = j;
×
572
                        }
573
                        if (k == 4 && i < resources.length) {
×
574
                            i--;
×
575
                        }
576
                        messageln(buf.toString());
×
577
                    }
578
                }
579
            } else if (args[0].equalsIgnoreCase("cd")) {
×
580
                // change current collection
581
                completions.clear();
×
582
                Collection temp;
583
                XmldbURI collectionPath;
584
                if (args.length < 2 || args[1] == null) {
×
585
                    collectionPath = XmldbURI.ROOT_COLLECTION_URI;
×
586
                } else {
×
587
                    collectionPath = XmldbURI.xmldbUriFor(args[1]);
×
588
                }
589
                collectionPath = currUri.resolveCollectionPath(collectionPath);
×
590
                if (collectionPath.numSegments() == 0) {
×
591
                    collectionPath = currUri.resolveCollectionPath(XmldbURI.ROOT_COLLECTION_URI);
×
592
                    messageln("cannot go above " + XmldbURI.ROOT_COLLECTION_URI.toString());
×
593
                }
594
                temp = DatabaseManager.getCollection(
×
595
                        collectionPath.toString(),
×
596
                        properties.getProperty(USER),
×
597
                        properties.getProperty(PASSWORD));
×
598
                if (temp != null) {
×
599
                    current.close();
×
600
                    current = temp;
×
601
                    newPath = collectionPath.toCollectionPathURI();
×
602
                    if (options.startGUI) {
×
603
                        frame.setPath(collectionPath.toCollectionPathURI());
×
604
                    }
605
                } else {
×
606
                    messageln("no such collection.");
×
607
                }
608
                getResources();
×
609
            } else if (args[0].equalsIgnoreCase("cp")) {
×
610
                if (args.length != 3) {
×
611
                    messageln("cp requires two arguments.");
×
612
                    return true;
×
613
                }
614
                final XmldbURI src;
615
                final XmldbURI dest;
616
                try {
617
                    src = XmldbURI.xmldbUriFor(args[1]);
×
618
                    dest = XmldbURI.xmldbUriFor(args[2]);
×
619
                } catch (final URISyntaxException e) {
×
620
                    errorln("could not parse collection name into a valid URI: " + e.getMessage());
×
621
                    return false;
×
622
                }
623
                copy(src, dest);
×
624
                getResources();
×
625

626
            } else if (args[0].equalsIgnoreCase("edit")) {
×
627
                if (args.length == 2) {
×
628
                    final XmldbURI resource;
629
                    try {
630
                        resource = XmldbURI.xmldbUriFor(args[1]);
×
631
                    } catch (final URISyntaxException e) {
×
632
                        errorln("could not parse resource name into a valid URI: " + e.getMessage());
×
633
                        return false;
×
634
                    }
635
                    editResource(resource);
×
636
                } else {
×
637
                    messageln("Please specify a resource.");
×
638
                }
639
            } else if (args[0].equalsIgnoreCase("get")) {
×
640
                if (args.length < 2) {
×
641
                    consoleErr("wrong number of arguments.");
×
642
                    return true;
×
643
                }
644
                final XmldbURI resource;
645
                try {
646
                    resource = XmldbURI.xmldbUriFor(args[1]);
×
647
                } catch (final URISyntaxException e) {
×
648
                    errorln("could not parse resource name into a valid URI: " + e.getMessage());
×
649
                    return false;
×
650
                }
651
                final Resource res = retrieve(resource);
×
652
                // display document
653
                if (res != null) {
×
654
                    final String data;
655
                    if (XML_RESOURCE.equals(res.getResourceType())) {
×
656
                        data = (String) res.getContent();
×
657
                    } else {
×
658
                        data = new String((byte[]) res.getContent());
×
659
                    }
660
                    if (options.startGUI) {
×
661
                        frame.setEditable(false);
×
662
                        frame.display(data);
×
663
                        frame.setEditable(true);
×
664
                    } else {
×
665
                        final String content = data;
×
666
                        more(content);
×
667
                    }
668
                }
669
                return true;
×
670
            } else if (args[0].equalsIgnoreCase("find")) {
×
671
                // search
672
                if (args.length < 2) {
×
673
                    messageln("no query argument found.");
×
674
                    return true;
×
675
                }
676
                messageln(args[1]);
×
677
                final long start = System.currentTimeMillis();
×
678
                result = find(args[1]);
×
679
                if (result == null) {
×
680
                    messageln("nothing found");
×
681
                } else {
×
682
                    messageln("found " + result.getSize() + " hits in "
×
683
                            + (System.currentTimeMillis() - start) + "ms.");
×
684
                }
685

686
                nextInSet = 1;
×
687

688
            } else if (args[0].equalsIgnoreCase("run")) {
×
689
                if (args.length < 2) {
×
690
                    messageln("please specify a query file.");
×
691
                    return true;
×
692
                }
693
                try (final BufferedReader reader = Files.newBufferedReader(Paths.get(args[1]))) {
×
694
                    final StringBuilder buf = new StringBuilder();
×
695
                    String nextLine;
696
                    while ((nextLine = reader.readLine()) != null) {
×
697
                        buf.append(nextLine);
×
698
                        buf.append(EOL);
×
699
                    }
700
                    args[1] = buf.toString();
×
701
                    final long start = System.currentTimeMillis();
×
702
                    result = find(args[1]);
×
703
                    if (result == null) {
×
704
                        messageln("nothing found");
×
705
                    } else {
×
706
                        messageln("found " + result.getSize() + " hits in "
×
707
                                + (System.currentTimeMillis() - start) + "ms.");
×
708
                    }
709

710
                    nextInSet = 1;
×
711
                } catch (final Exception e) {
×
712
                    errorln("An error occurred: " + e.getMessage());
×
713
                }
714
            } else if (args[0].equalsIgnoreCase("show")) {
×
715
                // show search results
716
                if (result == null) {
×
717
                    messageln("no result set.");
×
718
                    return true;
×
719
                }
720
                try {
721
                    int start = nextInSet;
×
722
                    int count = 1;
×
723
                    if (args.length > 1) {
×
724
                        start = Integer.parseInt(args[1]);
×
725
                    }
726

727
                    if (args.length > 2) {
×
728
                        count = Integer.parseInt(args[2]);
×
729
                    }
730

731
                    final int s = (int) result.getSize();
×
732
                    if (start < 1 || start > s) {
×
733
                        messageln("start offset out of range");
×
734
                        return true;
×
735
                    }
736
                    --start;
×
737
                    if (start + count > s) {
×
738
                        count = s - start;
×
739
                    }
740

741
                    nextInSet = start + count + 1;
×
742
                    for (int i = start; i < start + count; i++) {
×
743
                        final Resource r = result.getResource(i);
×
744
                        if (options.startGUI) {
×
745
                            frame.display((String) r.getContent());
×
746
                        } else {
×
747
                            more((String) r.getContent());
×
748
                        }
749
                    }
750
                    messageln("displayed items " + (start + 1) + " to "
×
751
                            + (start + count) + " of " + result.getSize());
×
752
                } catch (final NumberFormatException nfe) {
×
753
                    errorln("wrong argument");
×
754
                    return true;
×
755
                }
756

757
            } else if (args[0].equalsIgnoreCase("mkcol")) {
×
758
                // create collection
759
                if (args.length < 2) {
×
760
                    messageln("missing argument.");
×
761
                    return true;
×
762
                }
763
                final XmldbURI collUri;
764
                try {
765
                    collUri = XmldbURI.xmldbUriFor(args[1]);
×
766
                } catch (final URISyntaxException e) {
×
767
                    errorln("could not parse collection name into a valid URI: " + e.getMessage());
×
768
                    return false;
×
769
                }
770
                final EXistCollectionManagementService mgtService = current.getService(EXistCollectionManagementService.class);
×
771
                final Collection newCollection = mgtService.createCollection(collUri);
×
772
                if (newCollection == null) {
×
773
                    messageln("could not create collection.");
×
774
                } else {
×
775
                    messageln("created collection.");
×
776
                }
777

778
                // re-read current collection
779
                current = DatabaseManager.getCollection(properties
×
780
                        .getProperty(URI)
×
781
                        + path, properties.getProperty(USER), properties
×
782
                        .getProperty(PASSWORD));
×
783
                getResources();
×
784

785
            } else if (args[0].equalsIgnoreCase("put")) {
×
786
                // put a document or directory into the database
787
                if (args.length < 2) {
×
788
                    messageln("missing argument.");
×
789
                    return true;
×
790
                }
791
                final boolean r = parse(Paths.get(args[1]));
×
792
                getResources();
×
793
                return r;
×
794

795
            } else if (args[0].equalsIgnoreCase("putzip")) {
×
796
                // put the contents of a zip archive into the database
797
                if (args.length < 2) {
×
798
                    messageln("missing argument.");
×
799
                    return true;
×
800
                }
801
                final boolean r = parseZip(Paths.get(args[1]));
×
802
                getResources();
×
803
                return r;
×
804

805
            } else if (args[0].equalsIgnoreCase("putgz")) {
×
806
                // put the contents of a zip archive into the database
807
                if (args.length < 2) {
×
808
                    messageln("missing argument.");
×
809
                    return true;
×
810
                }
811
                final boolean r = parseGZip(args[1]);
×
812
                getResources();
×
813
                return r;
×
814

815
            } else if (args[0].equalsIgnoreCase("blob")) {
×
816
                // put a document or directory into the database
817
                if (args.length < 2) {
×
818
                    messageln("missing argument.");
×
819
                    return true;
×
820
                }
821
                storeBinary(args[1]);
×
822
                getResources();
×
823

824
            } else if (args[0].equalsIgnoreCase("rm")) {
×
825
                // remove document
826
                if (args.length < 2) {
×
827
                    messageln("missing argument.");
×
828
                    return true;
×
829
                }
830

831
                remove(args[1]);
×
832

833
                // re-read current collection
834
                current = DatabaseManager.getCollection(properties
×
835
                        .getProperty("uri")
×
836
                        + path, properties.getProperty(USER), properties
×
837
                        .getProperty(PASSWORD));
×
838
                getResources();
×
839

840
            } else if (args[0].equalsIgnoreCase("rmcol")) {
×
841
                // remove collection
842
                if (args.length < 2) {
×
843
                    messageln("wrong argument count.");
×
844
                    return true;
×
845
                }
846
                final XmldbURI collUri;
847
                try {
848
                    collUri = XmldbURI.xmldbUriFor(args[1]);
×
849
                } catch (final URISyntaxException e) {
×
850
                    errorln("could not parse collection name into a valid URI: " + e.getMessage());
×
851
                    return false;
×
852
                }
853
                rmcol(collUri);
×
854
                // re-read current collection
855
                current = DatabaseManager.getCollection(properties
×
856
                        .getProperty(URI)
×
857
                        + path, properties.getProperty(USER), properties
×
858
                        .getProperty(PASSWORD));
×
859
                getResources();
×
860
            } else if (args[0].equalsIgnoreCase("adduser")) {
×
861
                if (args.length < 2) {
×
862
                    consoleErr("Usage: adduser name");
×
863
                    return true;
×
864
                }
865
                if (options.startGUI) {
×
866
                    messageln("command not supported in GUI mode. Please use the \"Edit users\" menu option.");
×
867
                    return true;
×
868
                }
869
                try {
870
                    final UserManagementService mgtService = current.getService(UserManagementService.class);
×
871

872
                    String p1;
873
                    String p2;
874
                    while (true) {
×
875
                        p1 = console.readLine("password: ", '*');
×
876
                        p2 = console.readLine("re-enter password: ", '*');
×
877
                        if (p1.equals(p2)) {
×
878
                            break;
×
879
                        }
880
                        messageln("Entered passwords differ. Try again...");
×
881

882
                    }
883
                    final UserAider user = new UserAider(args[1]);
×
884
                    user.setPassword(p1);
×
885
                    final String groups = console.readLine("enter groups: ");
×
886
                    final StringTokenizer tok = new StringTokenizer(groups, " ,");
×
887
                    while (tok.hasMoreTokens()) {
×
888
                        final String group = tok.nextToken();
×
889
                        if (group.length() > 0) {
×
890
                            user.addGroup(group);
×
891
                        }
892
                    }
893

894
                    if (user.getGroups().length == 0) {
×
895
                        messageln("No groups specified, will be a member of the '" + SecurityManager.GUEST_GROUP + "' group!");
×
896
                        user.addGroup(SecurityManager.GUEST_GROUP);
×
897
                    }
898

899
                    mgtService.addAccount(user);
×
900
                    messageln("User '" + user.getName() + "' created.");
×
901
                } catch (final Exception e) {
×
902
                    errorln("ERROR: " + e.getMessage());
×
903
                    e.printStackTrace();
×
904
                }
905
            } else if (args[0].equalsIgnoreCase("users")) {
×
906
                final UserManagementService mgtService = current.getService(UserManagementService.class);
×
907
                final Account users[] = mgtService.getAccounts();
×
908
                messageln("User\t\tGroups");
×
909
                messageln("-----------------------------------------");
×
910
                for (Account user : users) {
×
911
                    StringBuilder sb = new StringBuilder();
×
912
                    sb.append(user.getName() + "\t\t");
×
913
                    final String[] groups = user.getGroups();
×
914
                    for (int j = 0; j < groups.length; j++) {
×
915
                        sb.append(groups[j]);
×
916
                        if (j + 1 < groups.length) {
×
917
                            sb.append(", ");
×
918
                        }
919
                    }
920
                    messageln(sb.toString());
×
921
                }
922
            } else if (args[0].equalsIgnoreCase("passwd")) {
×
923
                if (options.startGUI) {
×
924
                    messageln("command not supported in GUI mode. Please use the \"Edit users\" menu option.");
×
925
                    return true;
×
926
                }
927
                if (args.length < 2) {
×
928
                    messageln("Usage: passwd username");
×
929
                    return true;
×
930
                }
931
                try {
932
                    final UserManagementService mgtService = current.getService(UserManagementService.class);
×
933
                    final Account user = mgtService.getAccount(args[1]);
×
934
                    if (user == null) {
×
935
                        messageln("no such user.");
×
936
                        return true;
×
937
                    }
938
                    String p1;
939
                    String p2;
940
                    while (true) {
×
941
                        p1 = console.readLine("password: ", '*');
×
942
                        p2 = console.readLine("re-enter password: ", '*');
×
943
                        if (p1.equals(p2)) {
×
944
                            break;
×
945
                        }
946
                        consoleOut(EOL + "entered passwords differ. Try again...");
×
947
                    }
948
                    user.setPassword(p1);
×
949
                    mgtService.updateAccount(user);
×
950
                    properties.setProperty(PASSWORD, p1);
×
951
                } catch (final Exception e) {
×
952
                    errorln("ERROR: " + e.getMessage());
×
953
                    e.printStackTrace();
×
954
                }
955
            } else if (args[0].equalsIgnoreCase("chmod")) {
×
956
                if (args.length < 2) {
×
957
                    consoleOut("Usage: chmod [resource] mode");
×
958
                    return true;
×
959
                }
960

961
                final Collection temp;
962
                if (args.length == 3) {
×
963
                    consoleOut("trying collection: " + args[1]);
×
964
                    temp = current.getChildCollection(args[1]);
×
965
                    if (temp == null) {
×
966
                        consoleOut(EOL + "trying resource: " + args[1]);
×
967
                        final Resource r = current.getResource(args[1]);
×
968
                        if (r != null) {
×
969
                            final UserManagementService mgtService = current.getService(UserManagementService.class);
×
970
                            mgtService.chmod(r, args[2]);
×
971
                        } else {
×
972
                            consoleErr("Resource " + args[1] + " not found.");
×
973
                        }
974
                    } else {
×
975
                        final UserManagementService mgtService = temp.getService(UserManagementService.class);
×
976
                        mgtService.chmod(args[2]);
×
977
                    }
978
                } else {
×
979
                    final UserManagementService mgtService = current.getService(UserManagementService.class);
×
980
                    mgtService.chmod(args[1]);
×
981
                }
982
                // re-read current collection
983
                current = DatabaseManager.getCollection(properties
×
984
                        .getProperty(URI)
×
985
                        + path, properties.getProperty(USER), properties
×
986
                        .getProperty(PASSWORD));
×
987
                getResources();
×
988
            } else if (args[0].equalsIgnoreCase("chown")) {
×
989
                if (args.length < 3) {
×
990
                    consoleOut("Usage: chown username group [resource]");
×
991
                    return true;
×
992
                }
993

994
                final Collection temp;
995
                if (args.length == 4) {
×
996
                    temp = current.getChildCollection(args[3]);
×
997
                } else {
×
998
                    temp = current;
×
999
                }
1000
                if (temp != null) {
×
1001
                    final UserManagementService mgtService = temp.getService(UserManagementService.class);
×
1002
                    final Account u = mgtService.getAccount(args[1]);
×
1003
                    if (u == null) {
×
1004
                        consoleOut("unknown user");
×
1005
                        return true;
×
1006
                    }
1007
                    mgtService.chown(u, args[2]);
×
1008
                    consoleOut("owner changed.");
×
1009
                    getResources();
×
1010
                    return true;
×
1011
                }
1012
                final Resource res = current.getResource(args[3]);
×
1013
                if (res != null) {
×
1014
                    final UserManagementService mgtService = current.getService(UserManagementService.class);
×
1015
                    final Account u = mgtService.getAccount(args[1]);
×
1016
                    if (u == null) {
×
1017
                        consoleOut("unknown user");
×
1018
                        return true;
×
1019
                    }
1020
                    mgtService.chown(res, u, args[2]);
×
1021
                    getResources();
×
1022
                    return true;
×
1023
                }
1024
                consoleErr("Resource " + args[3] + " not found.");
×
1025

1026
            } else if (args[0].equalsIgnoreCase("lock") || args[0].equalsIgnoreCase("unlock")) {
×
1027
                if (args.length < 2) {
×
1028
                    messageln("Usage: lock resource");
×
1029
                    return true;
×
1030
                }
1031
                final Resource res = current.getResource(args[1]);
×
1032
                if (res != null) {
×
1033
                    final UserManagementService mgtService = current.getService(UserManagementService.class);
×
1034
                    final Account user = mgtService.getAccount(properties.getProperty(USER, "guest"));
×
1035
                    if (args[0].equalsIgnoreCase("lock")) {
×
1036
                        mgtService.lockResource(res, user);
×
1037
                    } else {
×
1038
                        mgtService.unlockResource(res);
×
1039
                    }
1040
                }
1041

1042
            } else if (args[0].equalsIgnoreCase("elements")) {
×
1043
                consoleOut("Element occurrences in collection "
×
1044
                        + current.getName());
×
1045
                consoleOut("--------------------------------------------"
×
1046
                                + "-----------");
1047
                final IndexQueryService service = current.getService(IndexQueryService.class);
×
1048
                final Occurrences[] elements = service.getIndexedElements(true);
×
1049
                for (Occurrences element : elements) {
×
1050
                    consoleOut(formatString(element.getTerm().toString(),
×
1051
                                    Integer.toString(element
×
1052
                                            .getOccurrences()), 50));
×
1053
                }
1054
                return true;
×
1055

1056
            } else if (args[0].equalsIgnoreCase("xupdate")) {
×
1057
                if (options.startGUI) {
×
1058
                    messageln("command not supported in GUI mode.");
×
1059
                    return true;
×
1060
                }
1061
                final StringBuilder command = new StringBuilder();
×
1062
                try {
1063
                    while (true) {
×
1064
                        final String lastLine = console.readLine("| ");
×
1065
                        if (lastLine == null || lastLine.length() == 0) {
×
1066
                            break;
×
1067
                        }
1068
                        command.append(lastLine);
×
1069
                    }
1070
                } catch (final UserInterruptException e) {
×
1071
                    errorln("Interrupted by user: " + e.getMessage());
×
1072
                }
1073
                final String xupdate = "<xu:modifications version=\"1.0\" "
×
1074
                        + "xmlns:xu=\"http://www.xmldb.org/xupdate\">"
1075
                        + command.toString() + "</xu:modifications>";
×
1076
                final XUpdateQueryService service = current.getService(XUpdateQueryService.class);
×
1077
                final long mods = service.update(xupdate);
×
1078
                consoleOut(mods + " modifications processed.");
×
1079

1080
            } else if (args[0].equalsIgnoreCase("map")) {
×
1081
                final StringTokenizer tok = new StringTokenizer(args[1], "= ");
×
1082
                final String prefix;
1083
                if (args[1].startsWith("=")) {
×
1084
                    prefix = "";
×
1085
                } else {
×
1086
                    if (tok.countTokens() < 2) {
×
1087
                        messageln("please specify a namespace/prefix mapping as: prefix=namespaceURI");
×
1088
                        return true;
×
1089
                    }
1090
                    prefix = tok.nextToken();
×
1091
                }
1092
                final String uri = tok.nextToken();
×
1093
                namespaceMappings.put(prefix, uri);
×
1094

1095
            } else if (args[0].equalsIgnoreCase("set")) {
×
1096
                if (args.length == 1) {
×
1097
                    properties.list(System.out);
×
1098
                } else {
×
1099
                    try {
1100
                        final StringTokenizer tok = new StringTokenizer(args[1], "= ");
×
1101
                        if (tok.countTokens() < 2) {
×
1102
                            consoleErr("please specify a key=value pair");
×
1103
                            return true;
×
1104
                        }
1105
                        final String key = tok.nextToken();
×
1106
                        final String val = tok.nextToken();
×
1107
                        properties.setProperty(key, val);
×
1108
                        current.setProperty(key, val);
×
1109
                        getResources();
×
1110
                    } catch (final Exception e) {
×
1111
                        consoleErr("Exception: " + e.getMessage());
×
1112
                    }
1113
                }
1114
            } else if (args[0].equalsIgnoreCase("shutdown")) {
×
1115
                final DatabaseInstanceManager mgr = current.getService(DatabaseInstanceManager.class);
×
1116
                if (mgr == null) {
×
1117
                    messageln("Service is not available");
×
1118
                    return true;
×
1119
                }
1120
                mgr.shutdown();
×
1121
                return true;
×
1122
            } else if (args[0].equalsIgnoreCase("help") || "?".equals(args[0])) {
×
1123
                displayHelp();
×
1124
            } else if (args[0].equalsIgnoreCase("quit")) {
×
1125
                return false;
×
1126
            } else {
1127
                messageln("unknown command: '" + args[0] + "'");
×
1128
                return true;
×
1129
            }
1130
            path = newPath;
×
1131
            return true;
×
1132
        } catch (final Throwable e) {
×
1133
            if (options.startGUI) {
×
1134
                ClientFrame.showErrorMessage(getExceptionMessage(e), e);
×
1135
            } else {
×
1136
                errorln(getExceptionMessage(e));
×
1137
                e.printStackTrace();
×
1138
            }
1139
            return true;
×
1140
        }
1141
    }
1142

1143
    /**
1144
     * @param name
1145
     */
1146
    private void editResource(final XmldbURI name) {
1147
        try {
1148
            final Resource doc = retrieve(name, properties.getProperty(OutputKeys.INDENT, "yes")); //$NON-NLS-1$
×
1149
            final DocumentView view = new DocumentView(this, name, doc, properties);
×
1150
            view.setSize(new Dimension(640, 400));
×
1151
            view.viewDocument();
×
1152
        } catch (final XMLDBException ex) {
×
1153
            errorln("XMLDB error: " + ex.getMessage());
×
1154
            ex.printStackTrace();
×
1155
        }
1156
    }
×
1157

1158
    private Optional<Writer> getTraceWriter() {
1159

1160
        //should there be a trace writer?
1161
        if (options.traceQueriesFile.isPresent()) {
×
1162

1163
            //lazy initialization
1164
            if (!lazyTraceWriter.isPresent()) {
×
1165
                try (final Writer traceWriter = Files.newBufferedWriter(options.traceQueriesFile.get(), UTF_8)) {
×
1166
                    traceWriter.write("<?xml version=\"1.0\"?>" + EOL);
×
1167
                    traceWriter.write("<query-log>" + EOL);
×
1168
                    this.lazyTraceWriter = Optional.of(traceWriter);
×
1169
                } catch (final IOException ioe) {
×
1170
                    errorln("Cannot open file " + options.traceQueriesFile.get());
×
1171
                    return Optional.empty();
×
1172
                }
1173
            }
1174

1175
            return lazyTraceWriter;
×
1176

1177
        } else {
1178
            return Optional.empty();
×
1179
        }
1180
    }
1181

1182
    private ResourceSet find(String xpath) throws XMLDBException {
1183
        if (xpath.substring(xpath.length() - EOL.length()).equals(EOL)) {
×
1184
            xpath = xpath.substring(0, xpath.length() - EOL.length());
×
1185
        }
1186

1187
        final String xpathCopy = xpath;
×
1188
        final Optional<Writer> maybeWriter = getTraceWriter();
×
1189
        if (maybeWriter.isPresent()) {
×
1190
            final Writer writer = maybeWriter.get();
×
1191
            try {
1192
                writer.write("<query>");
×
1193
                writer.write(xpathCopy);
×
1194
                writer.write("</query>");
×
1195
                writer.write(EOL);
×
1196
            } catch (final IOException e) {
×
1197
                throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e);
×
1198
            }
1199
        }
1200

1201
        String sortBy = null;
×
1202
        final int p = xpath.indexOf(" sort by ");
×
1203
        if (p != Constants.STRING_NOT_FOUND) {
×
1204
            final String xp = xpath.substring(0, p);
×
1205
            sortBy = xpath.substring(p + " sort by ".length());
×
1206
            xpath = xp;
×
1207
            consoleOut("XPath =   " + xpath);
×
1208
            consoleOut("Sort-by = " + sortBy);
×
1209
        }
1210

1211
        final EXistXPathQueryService service = current.getService(EXistXPathQueryService.class);
×
1212
        service.setProperty(OutputKeys.INDENT, properties.getProperty(INDENT));
×
1213
        service.setProperty(OutputKeys.ENCODING, properties.getProperty(ENCODING));
×
1214

1215
        for (final Map.Entry<String, String> mapping : namespaceMappings.entrySet()) {
×
1216
            service.setNamespace(mapping.getKey(), mapping.getValue());
×
1217
        }
1218

1219
        return (sortBy == null) ? service.query(xpath) : service.query(xpath, sortBy);
×
1220
    }
1221

1222
    protected final Resource retrieve(final XmldbURI resource) throws XMLDBException {
1223
        return retrieve(resource, properties.getProperty(INDENT));
×
1224
    }
1225

1226
    protected final Resource retrieve(final XmldbURI resource, final String indent) throws XMLDBException {
1227
        final Resource res = current.getResource(resource.toString());
×
1228
        if (res == null) {
×
1229
            messageln("document not found.");
×
1230
            return null;
×
1231
        }
1232
        return res;
×
1233
    }
1234

1235
    private void remove(final String pattern) throws XMLDBException {
1236
        final Collection collection = current;
×
1237
        if (pattern.startsWith("/")) {
×
1238
            consoleErr("path pattern should be relative to current collection");
×
1239
            return;
×
1240
        }
1241
        final Resource[] resources;
1242
        final Resource res = collection.getResource(pattern);
×
1243
        if (res == null) {
×
1244
            resources = CollectionScanner.scan(collection, "", pattern);
×
1245
        } else {
×
1246
            resources = new Resource[1];
×
1247
            resources[0] = res;
×
1248
        }
1249
        for (Resource resource : resources) {
×
1250
            message("removing document " + resource.getId() + " ...");
×
1251
            final Collection parent = resource.getParentCollection();
×
1252
            parent.removeResource(resource);
×
1253
            messageln(DONE);
×
1254
        }
1255
    }
×
1256

1257
    private void xupdate(final Optional<String> resource, final Path file) throws XMLDBException, IOException {
1258
        if (!(Files.exists(file) && Files.isReadable(file))) {
×
1259
            messageln("cannot read file " + file.normalize().toAbsolutePath().toString());
×
1260
            return;
×
1261
        }
1262
        final String commands = XMLUtil.readFile(file, UTF_8);
×
1263
        final XUpdateQueryService service = current.getService(XUpdateQueryService.class);
×
1264
        final long modifications;
1265
        if (resource.isPresent()) {
×
1266
            modifications = service.updateResource(resource.get(), commands);
×
1267
        } else {
×
1268
            modifications = service.update(commands);
×
1269

1270
        }
1271
        messageln(modifications + " modifications processed " + "successfully.");
×
1272
    }
×
1273

1274
    private void rmcol(final XmldbURI collection) throws XMLDBException {
1275
        final EXistCollectionManagementService mgtService = current.getService(EXistCollectionManagementService.class);
×
1276
        message("removing collection " + collection + " ...");
×
1277
        mgtService.removeCollection(collection);
×
1278
        messageln(DONE);
×
1279
    }
×
1280

1281
    private void copy(final XmldbURI source, XmldbURI destination) throws XMLDBException {
1282
        try {
1283
            final EXistCollectionManagementService mgtService = current.getService(EXistCollectionManagementService.class);
×
1284
            final XmldbURI destName = destination.lastSegment();
×
1285
            final Collection destCol = resolveCollection(destination);
×
1286
            if (destCol == null) {
×
1287
                if (destination.numSegments() == 1) {
×
1288
                    destination = XmldbURI.xmldbUriFor(current.getName());
×
1289
                } else {
×
1290
                    destination = destination.removeLastSegment();
×
1291
                }
1292
            }
1293
            final Resource srcDoc = resolveResource(source);
×
1294
            if (srcDoc != null) {
×
1295
                final XmldbURI resourcePath = XmldbURI.xmldbUriFor(srcDoc.getParentCollection().getName()).append(srcDoc.getId());
×
1296
                messageln("Copying resource '" + resourcePath + "' to '" + destination + "'");
×
1297
                mgtService.copyResource(resourcePath, destination, destName);
×
1298
            } else {
×
1299
                messageln("Copying collection '" + source + "' to '" + destination + "'");
×
1300
                mgtService.copy(source, destination, destName);
×
1301
            }
1302
        } catch (final URISyntaxException e) {
×
1303
            errorln("could not parse name into a valid URI: " + e.getMessage());
×
1304
        }
1305
    }
×
1306

1307
    private void reindex() throws XMLDBException {
1308
        final IndexQueryService service = current.getService(IndexQueryService.class);
×
1309
        message("reindexing collection " + current.getName());
×
1310
        service.reindexCollection();
×
1311
        messageln(DONE);
×
1312
    }
×
1313

1314
    private void storeBinary(final String fileName) throws XMLDBException {
1315
        final Path file = Paths.get(fileName).normalize();
×
1316
        if (Files.isReadable(file)) {
×
1317
            final MimeType mime = MimeTable.getInstance().getContentTypeFor(FileUtils.fileName(file));
×
1318
            try (final BinaryResource resource = current.createResource(FileUtils.fileName(file), BinaryResource.class)) {
×
1319
                resource.setContent(file);
×
1320
                ((EXistResource) resource).setMimeType(mime == null ? "application/octet-stream" : mime.getName());
×
1321
                current.storeResource(resource);
×
1322
            }
1323
        }
1324
    }
×
1325

1326
    private synchronized boolean findRecursive(final Collection collection, final Path dir, final XmldbURI base) throws XMLDBException {
1327
        Collection c;
1328
        EXistCollectionManagementService mgtService;
1329
        //The XmldbURIs here aren't really used...
1330
        XmldbURI next;
1331
        MimeType mimeType;
1332

1333
        try {
1334
            final List<Path> files = FileUtils.list(dir);
×
1335
            int i = 0;
×
1336
            for (final Path file : files) {
×
1337
                next = base.append(FileUtils.fileName(file));
×
1338
                try {
1339
                    if (Files.isDirectory(file)) {
×
1340
                        messageln("entering directory " + file.toAbsolutePath());
×
1341
                        c = collection.getChildCollection(FileUtils.fileName(file));
×
1342
                        if (c == null) {
×
1343
                            mgtService = collection.getService(EXistCollectionManagementService.class);
×
1344
                            c = mgtService.createCollection(XmldbURI.xmldbUriFor(FileUtils.fileName(file)));
×
1345
                        }
1346

1347
                        if (c instanceof Observable && options.verbose) {
×
1348
                            final ProgressObserver observer = new ProgressObserver();
×
1349
                            ((Observable) c).addObserver(observer);
×
1350
                        }
1351
                        findRecursive(c, file, next);
×
1352
                    } else {
×
1353
                        final long start1 = System.currentTimeMillis();
×
1354
                        mimeType = MimeTable.getInstance().getContentTypeFor(FileUtils.fileName(file));
×
1355
                        if (mimeType == null) {
×
1356
                            messageln("File " + FileUtils.fileName(file) + " has an unknown suffix. Cannot determine file type.");
×
1357
                            mimeType = MimeType.BINARY_TYPE;
×
1358
                        }
1359
                        try (final Resource document = collection.createResource(FileUtils.fileName(file), mimeType.getXMLDBType())) {
×
1360
                            message("storing document " + FileUtils.fileName(file) + " (" + i + " of " + files.size() + ") " + "...");
×
1361
                            document.setContent(file);
×
1362
                            ((EXistResource) document).setMimeType(mimeType.getName());
×
1363
                            collection.storeResource(document);
×
1364
                            ++filesCount;
×
1365
                            messageln(" " + FileUtils.sizeQuietly(file) + " bytes in " + (System.currentTimeMillis() - start1) + "ms.");
×
1366
                        }
1367
                    }
1368
                } catch (final URISyntaxException e) {
×
1369
                    errorln("uri syntax exception parsing " + file.toAbsolutePath() + ": " + e.getMessage());
×
1370
                }
1371
                i++;
×
1372
            }
1373
            return true;
×
1374
        } catch (final IOException e) {
×
1375
            throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e);
×
1376
        }
1377
    }
1378

1379
    /**
1380
     * Stores given Resource
1381
     *
1382
     * @param file file or directory
1383
     * @return TRUE if file or files in directory were all correctly stored.
1384
     * @throws XMLDBException An error was detected.
1385
     */
1386
    protected synchronized boolean parse(final Path file) throws XMLDBException {
1387
        try {
1388
            // String xml;
1389

1390
            if (current instanceof Observable && options.verbose) {
×
1391
                final ProgressObserver observer = new ProgressObserver();
×
1392
                ((Observable) current).addObserver(observer);
×
1393
            }
1394

1395
            List<Path> files = new ArrayList<>();
×
1396
            if (Files.isReadable(file)) {
×
1397
                // TODO, same logic as for the graphic client
1398
                if (Files.isDirectory(file)) {
×
1399
                    if (options.reindexRecurse) {
×
1400
                        filesCount = 0;
×
1401
                        final long start = System.currentTimeMillis();
×
1402
                        final boolean result = findRecursive(current, file, path);
×
1403
                        messageln("storing " + filesCount + " files took " + ((System.currentTimeMillis() - start) / 1000) + "sec.");
×
1404
                        return result;
×
1405
                    }
1406
                    files = FileUtils.list(file);
×
1407
                } else {
×
1408
                    files.add(file);
×
1409
                }
1410
            } else {
×
1411
                final DirectoryScanner directoryScanner = new DirectoryScanner();
×
1412
                directoryScanner.setIncludes(new String[]{file.toString()});
×
1413
                //TODO(AR) do we need to call scanner.setBasedir()?
1414
                directoryScanner.setCaseSensitive(true);
×
1415
                directoryScanner.scan();
×
1416
                for (final String includedFile : directoryScanner.getIncludedFiles()) {
×
1417
//                    files.add(baseDir.resolve(includedFile));
1418
                    files.add(Paths.get(includedFile));
×
1419
                }
1420
            }
1421

1422
            final long start0 = System.currentTimeMillis();
×
1423
            long bytes = 0;
×
1424
            MimeType mimeType;
1425
            for (int i = 0; i < files.size(); i++) {
×
1426
                if (Files.isDirectory(files.get(i))) {
×
1427
                    continue;
×
1428
                }
1429
                final long start = System.currentTimeMillis();
×
1430
                mimeType = MimeTable.getInstance().getContentTypeFor(FileUtils.fileName(files.get(i)));
×
1431
                if (mimeType == null) {
×
1432
                    mimeType = MimeType.BINARY_TYPE;
×
1433
                }
1434
                try (final Resource document = current.createResource(FileUtils.fileName(files.get(i)), mimeType.getXMLDBType())) {
×
1435
                    message("storing document " + FileUtils.fileName(files.get(i)) + " (" + (i + 1) + " of " + files.size() + ") ...");
×
1436
                    document.setContent(files.get(i));
×
1437
                    ((EXistResource) document).setMimeType(mimeType.getName());
×
1438
                    current.storeResource(document);
×
1439
                    messageln(DONE);
×
1440
                    messageln("parsing " + FileUtils.sizeQuietly(files.get(i)) + " bytes took " + (System.currentTimeMillis() - start) + "ms." + EOL);
×
1441
                    bytes += FileUtils.sizeQuietly(files.get(i));
×
1442
                }
1443
            }
1444
            messageln("parsed " + bytes + " bytes in " + (System.currentTimeMillis() - start0) + "ms.");
×
1445
            return true;
×
1446
        } catch (final IOException e) {
×
1447
            e.printStackTrace();
×
1448
            throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e);
×
1449
        }
1450
    }
1451

1452

1453
    private synchronized boolean findGZipRecursive(final Collection collection, final Path dir, final XmldbURI base) throws XMLDBException, IOException {
1454
        final List<Path> files = FileUtils.list(dir);
×
1455
        Collection c;
1456
        EXistCollectionManagementService mgtService;
1457
        //The XmldbURIs here aren't really used...
1458
        XmldbURI next;
1459
        MimeType mimeType;
1460
        int i = 0;
×
1461
        for (final Path file : files) {
×
1462
            i++;
×
1463
            next = base.append(FileUtils.fileName(file));
×
1464
            try {
1465
                if (Files.isDirectory(file)) {
×
1466
                    messageln("entering directory " + file.toAbsolutePath().toString());
×
1467
                    c = collection.getChildCollection(FileUtils.fileName(file));
×
1468
                    if (c == null) {
×
1469
                        mgtService = collection.getService(EXistCollectionManagementService.class);
×
1470
                        c = mgtService.createCollection(XmldbURI.xmldbUriFor(FileUtils.fileName(file)));
×
1471
                    }
1472
                    if (c instanceof Observable && options.verbose) {
×
1473
                        final ProgressObserver observer = new ProgressObserver();
×
1474
                        ((Observable) c).addObserver(observer);
×
1475
                    }
1476
                    findGZipRecursive(c, file, next);
×
1477
                } else {
×
1478
                    final long start1 = System.currentTimeMillis();
×
1479
                    final String compressedName = FileUtils.fileName(file);
×
1480
                    String localName = compressedName;
×
1481
                    final String[] cSuffix = {".gz", ".Z"};
×
1482
                    boolean isCompressed = false;
×
1483
                    for (final String suf : cSuffix) {
×
1484
                        if (localName.endsWith(suf)) {
×
1485
                            // Removing compressed prefix to validate
1486
                            localName = compressedName.substring(0, localName.length() - suf.length());
×
1487
                            isCompressed = true;
×
1488
                            break;
×
1489
                        }
1490
                    }
1491
                    mimeType = MimeTable.getInstance().getContentTypeFor(localName);
×
1492
                    if (mimeType == null) {
×
1493
                        messageln("File " + compressedName + " has an unknown suffix. Cannot determine file type.");
×
1494
                        mimeType = MimeType.BINARY_TYPE;
×
1495
                    }
1496
                    try (final Resource document = collection.createResource(compressedName, mimeType.getXMLDBType())) {
×
1497
                        message("storing document " + compressedName + " (" + i + " of " + files.size() + ") " + "...");
×
1498
                        document.setContent(isCompressed ? new GZIPInputSource(file) : file);
×
1499
                        ((EXistResource) document).setMimeType(mimeType.getName());
×
1500
                        collection.storeResource(document);
×
1501
                        ++filesCount;
×
1502
                        messageln(" " + Files.size(file) + (isCompressed ? " compressed" : "") + " bytes in "
×
1503
                                + (System.currentTimeMillis() - start1) + "ms.");
×
1504
                    }
1505
                }
1506
            } catch (final URISyntaxException e) {
×
1507
                errorln("uri syntax exception parsing " + file.toAbsolutePath().toString() + ": " + e.getMessage());
×
1508
            }
1509
        }
1510
        return true;
×
1511
    }
1512

1513
    /**
1514
     * stores given Resource
1515
     *
1516
     * @param fileName simple file or directory
1517
     * @return true if the operation succeeded
1518
     * @throws XMLDBException in case of database error storing the resource
1519
     * @throws IOException    in case of a read error
1520
     */
1521
    protected synchronized boolean parseGZip(String fileName) throws XMLDBException, IOException {
1522
        //TODO : why is this test for ? Fileshould make it, shouldn't it ? -pb
1523
        fileName = fileName.replace('/', java.io.File.separatorChar).replace('\\',
×
1524
                java.io.File.separatorChar);
×
1525
        final Path file = Paths.get(fileName);
×
1526
        // String xml;
1527
        if (current instanceof Observable && options.verbose) {
×
1528
            final ProgressObserver observer = new ProgressObserver();
×
1529
            ((Observable) current).addObserver(observer);
×
1530
        }
1531
        final List<Path> files;
1532
        if (Files.isReadable(file)) {
×
1533
            // TODO, same logic as for the graphic client
1534
            if (Files.isDirectory(file)) {
×
1535
                if (options.reindexRecurse) {
×
1536
                    filesCount = 0;
×
1537
                    final long start = System.currentTimeMillis();
×
1538
                    final boolean result = findGZipRecursive(current, file, path);
×
1539
                    messageln("storing " + filesCount + " compressed files took "
×
1540
                            + ((System.currentTimeMillis() - start) / 1000)
×
1541
                            + "sec.");
1542
                    return result;
×
1543
                }
1544
                files = FileUtils.list(file);
×
1545
            } else {
×
1546
                files = new ArrayList<>();
×
1547
                files.add(file);
×
1548
            }
1549
        } else {
×
1550
            final DirectoryScanner directoryScanner = new DirectoryScanner();
×
1551
            directoryScanner.setIncludes(new String[]{fileName});
×
1552
            //TODO(AR) do we need to call scanner.setBasedir()?
1553
            directoryScanner.setCaseSensitive(true);
×
1554
            directoryScanner.scan();
×
1555

1556
            final String[] includedFiles = directoryScanner.getIncludedFiles();
×
1557
            files = new ArrayList<>(includedFiles.length);
×
1558
            for (final String includedFile : includedFiles) {
×
1559
//                files.add(baseDir.resolve(includedFile));
1560
                files.add(Paths.get(includedFile));
×
1561
            }
1562
        }
1563

1564
        final long start0 = System.currentTimeMillis();
×
1565
        long bytes = 0;
×
1566
        MimeType mimeType;
1567
        int i = 0;
×
1568
        for (final Path p : files) {
×
1569
            i++;
×
1570
            if (Files.isDirectory(p)) {
×
1571
                continue;
×
1572
            }
1573
            final long start = System.currentTimeMillis();
×
1574
            final String compressedName = FileUtils.fileName(p);
×
1575
            String localName = compressedName;
×
1576
            final String[] cSuffix = {".gz", ".Z"};
×
1577
            boolean isCompressed = false;
×
1578
            for (final String suf : cSuffix) {
×
1579
                if (localName.endsWith(suf)) {
×
1580
                    // Removing compressed prefix to validate
1581
                    localName = compressedName.substring(0, localName.length() - suf.length());
×
1582
                    isCompressed = true;
×
1583
                    break;
×
1584
                }
1585
            }
1586
            mimeType = MimeTable.getInstance().getContentTypeFor(localName);
×
1587
            if (mimeType == null) {
×
1588
                mimeType = MimeType.BINARY_TYPE;
×
1589
            }
1590
            try (final Resource document = current.createResource(compressedName, mimeType.getXMLDBType())) {
×
1591
                message("storing document " + compressedName + " (" + i
×
1592
                        + " of " + Files.size(p) + ") ...");
×
1593
                document.setContent(isCompressed ? new GZIPInputSource(p) : p);
×
1594
                ((EXistResource) document).setMimeType(mimeType.getName());
×
1595
                current.storeResource(document);
×
1596
                messageln(DONE);
×
1597
                messageln("parsing " + Files.size(p) + (isCompressed ? " compressed" : "") + " bytes took "
×
1598
                        + (System.currentTimeMillis() - start) + "ms." + EOL);
×
1599
                bytes += Files.size(p);
×
1600
            }
1601
        }
1602
        messageln("parsed " + bytes + " compressed bytes in "
×
1603
                + (System.currentTimeMillis() - start0) + "ms.");
×
1604
        return true;
×
1605
    }
1606

1607
    /**
1608
     * stores given Resource.
1609
     *
1610
     * @param zipPath Path to a zip file
1611
     * @return true if operation succeeded
1612
     * @throws XMLDBException in case of error writing to the database
1613
     */
1614
    protected synchronized boolean parseZip(final Path zipPath) throws XMLDBException {
1615
        try (final ZipFile zfile = new ZipFile(zipPath.toFile())) {
×
1616
            if (current instanceof Observable && options.verbose) {
×
1617
                final ProgressObserver observer = new ProgressObserver();
×
1618
                ((Observable) current).addObserver(observer);
×
1619
            }
1620

1621
            final long start0 = System.currentTimeMillis();
×
1622
            long bytes = 0;
×
1623
            final Enumeration<? extends ZipEntry> e = zfile.entries();
×
1624
            int number = 0;
×
1625

1626
            Collection base = current;
×
1627
            String baseStr = "";
×
1628
            while (e.hasMoreElements()) {
×
1629
                number++;
×
1630
                final ZipEntry ze = e.nextElement();
×
1631
                final String zeName = ze.getName().replace('\\', '/');
×
1632

1633
                if (!Paths.get("/db").resolve(zeName).normalize().startsWith(Paths.get("/db"))) {
×
1634
                    throw new IOException("Detected archive exit attack! zipFile=" + zipPath.toAbsolutePath().toString() + ", entry=" + ze.getName());
×
1635
                }
1636

1637
                final String[] pathSteps = zeName.split("/");
×
1638
                final StringBuilder currStr = new StringBuilder(pathSteps[0]);
×
1639
                for (int i = 1; i < pathSteps.length - 1; i++) {
×
1640
                    currStr
×
1641
                            .append("/")
×
1642
                            .append(pathSteps[i]);
×
1643
                }
1644
                if (!currStr.toString().equals(baseStr)) {
×
1645
                    base = current;
×
1646
                    for (int i = 0; i < pathSteps.length - 1; i++) {
×
1647
                        Collection c = base.getChildCollection(pathSteps[i]);
×
1648
                        if (c == null) {
×
1649
                            final EXistCollectionManagementService mgtService = base.getService(EXistCollectionManagementService.class);
×
1650
                            c = mgtService.createCollection(XmldbURI.xmldbUriFor(pathSteps[i]));
×
1651
                        }
1652
                        base = c;
×
1653
                    }
1654
                    if (base instanceof Observable && options.verbose) {
×
1655
                        final ProgressObserver observer = new ProgressObserver();
×
1656
                        ((Observable) base).addObserver(observer);
×
1657
                    }
1658
                    baseStr = currStr.toString();
×
1659
                    messageln("entering directory " + baseStr);
×
1660
                }
1661
                if (!ze.isDirectory()) {
×
1662
                    final String localName = pathSteps[pathSteps.length - 1];
×
1663
                    final long start = System.currentTimeMillis();
×
1664
                    MimeType mimeType = MimeTable.getInstance().getContentTypeFor(localName);
×
1665
                    if (mimeType == null) {
×
1666
                        mimeType = MimeType.BINARY_TYPE;
×
1667
                    }
1668
                    try (final Resource document = base.createResource(localName, mimeType.getXMLDBType())) {
×
1669
                        message("storing Zip-entry document " + localName + " (" + (number)
×
1670
                                + " of " + zfile.size() + ") ...");
×
1671
                        document.setContent(new ZipEntryInputSource(zfile, ze));
×
1672
                        ((EXistResource) document).setMimeType(mimeType.getName());
×
1673
                        base.storeResource(document);
×
1674
                        messageln(DONE);
×
1675
                        messageln("parsing " + ze.getSize() + " bytes took "
×
1676
                                + (System.currentTimeMillis() - start) + "ms." + EOL);
×
1677
                        bytes += ze.getSize();
×
1678
                    }
1679
                }
1680
            }
1681
            messageln("parsed " + bytes + " bytes in "
×
1682
                    + (System.currentTimeMillis() - start0) + "ms.");
×
1683
        } catch (final URISyntaxException e) {
×
1684
            errorln("uri syntax exception parsing a ZIP entry from " + zipPath.toString() + ": " + e.getMessage());
×
1685
        } catch (final IOException e) {
×
1686
            errorln("could not parse ZIP file " + zipPath.toAbsolutePath() + ": " + e.getMessage());
×
1687
        }
1688
        return true;
×
1689
    }
1690

1691
    /**
1692
     * Method called by the store Dialog
1693
     *
1694
     * @param files  : selected
1695
     * @param upload : GUI object
1696
     * @return true if the operation succeeded
1697
     * @throws XMLDBException in case of an error uploading the resources
1698
     */
1699
    protected synchronized boolean parse(final List<Path> files, final UploadDialog upload) throws XMLDBException {
1700
        final Collection uploadRootCollection = current;
×
1701
        if (!upload.isVisible()) {
×
1702
            upload.setVisible(true);
×
1703
        }
1704

1705
        if (uploadRootCollection instanceof Observable) {
×
1706
            ((Observable) uploadRootCollection).addObserver(upload.getObserver());
×
1707
        }
1708
        upload.setTotalSize(FileUtils.sizeQuietly(files));
×
1709
        for (final Path file : files) {
×
1710
            if (upload.isCancelled()) {
×
1711
                break;
×
1712
            }
1713
            // should replace the lines above
1714
            store(uploadRootCollection, file, upload);
×
1715
        }
1716
        if (uploadRootCollection instanceof Observable) {
×
1717
            ((Observable) uploadRootCollection).deleteObservers();
×
1718
        }
1719
        upload.uploadCompleted();
×
1720
        return true;
×
1721
    }
1722

1723
    /**
1724
     * Pass to this method a java file object
1725
     * (may be a file or a directory), GUI object
1726
     * will create relative collections or resources
1727
     * recursively
1728
     */
1729

1730
    private void store(final Collection collection, final Path file, final UploadDialog upload) {
1731

1732
        // cancel, stop crawl
1733
        if (upload.isCancelled()) {
×
1734
            return;
×
1735
        }
1736

1737
        // can't read there, inform client
1738
        if (!Files.isReadable(file)) {
×
1739
            upload.showMessage(file.toAbsolutePath() + " impossible to read ");
×
1740
            return;
×
1741
        }
1742

1743
        final XmldbURI filenameUri;
1744
        try {
1745
            filenameUri = XmldbURI.xmldbUriFor(FileUtils.fileName(file));
×
1746
        } catch (final URISyntaxException e1) {
×
1747
            upload.showMessage(file.toAbsolutePath() + " could not be encoded as a URI");
×
1748
            return;
×
1749
        }
1750

1751
        // Directory, create collection, and crawl it
1752
        if (Files.isDirectory(file)) {
×
1753
            Collection c = null;
×
1754
            try {
1755
                c = collection.getChildCollection(filenameUri.toString());
×
1756
                if (c == null) {
×
1757
                    final EXistCollectionManagementService mgtService = collection.getService(EXistCollectionManagementService.class);
×
1758
                    c = mgtService.createCollection(filenameUri);
×
1759
                }
1760
            } catch (final XMLDBException e) {
×
1761
                upload.showMessage("Impossible to create a collection " + file.toAbsolutePath() + ": " + e.getMessage());
×
1762
                e.printStackTrace();
×
1763
            }
1764

1765
            // change displayed collection if it's OK
1766
            upload.setCurrentDir(file.toAbsolutePath().toString());
×
1767
            if (c instanceof Observable) {
×
1768
                ((Observable) c).addObserver(upload.getObserver());
×
1769
            }
1770
            // maybe a depth or recurs flag could be added here
1771
            final Collection childCollection = c;
×
1772
            try (final Stream<Path> children = Files.list(file)) {
×
1773
                children.forEach(child -> store(childCollection, child, upload));
×
1774
            } catch (final IOException e) {
×
1775
                upload.showMessage("Impossible to upload " + file.toAbsolutePath() + ": " + e.getMessage());
×
1776
                e.printStackTrace();
×
1777
            }
1778

1779
            return;
×
1780
        }
1781

1782
        // File, create and store resource
1783
        if (!Files.isDirectory(file)) {
×
1784
            upload.reset();
×
1785
            upload.setCurrent(FileUtils.fileName(file));
×
1786
            final long fileSize = FileUtils.sizeQuietly(file);
×
1787
            upload.setCurrentSize(fileSize);
×
1788

1789
            MimeType mimeType = MimeTable.getInstance().getContentTypeFor(FileUtils.fileName(file));
×
1790
            // unknown mime type, here prefered is to do nothing
1791
            if (mimeType == null) {
×
1792
                upload.showMessage(file.toAbsolutePath() +
×
1793
                        " - unknown suffix. No matching mime-type found in : " +
1794
                        MimeTable.getInstance().getSrc());
×
1795

1796
                // if some one prefers to store it as binary by default, but dangerous
1797
                mimeType = MimeType.BINARY_TYPE;
×
1798
            }
1799

1800
            try (final Resource res = collection.createResource(filenameUri.toString(), mimeType.getXMLDBType())) {
×
1801
                ((EXistResource) res).setMimeType(mimeType.getName());
×
1802
                res.setContent(file);
×
1803
                collection.storeResource(res);
×
1804
                ++filesCount;
×
1805
                this.totalLength += fileSize;
×
1806
                upload.setStoredSize(this.totalLength);
×
1807
            } catch (final XMLDBException e) {
×
1808
                upload.showMessage("Impossible to store a resource "
×
1809
                        + file.toAbsolutePath() + ": " + e.getMessage());
×
1810
            }
1811
        }
1812
    }
×
1813

1814

1815
    private void mkcol(final XmldbURI collPath) throws XMLDBException {
1816
        messageln("creating '" + collPath + "'");
×
1817
        final XmldbURI[] segments = collPath.getPathSegments();
×
1818
        XmldbURI p = XmldbURI.ROOT_COLLECTION_URI;
×
1819
        for (int i = 1; i < segments.length; i++) {
×
1820
            p = p.append(segments[i]);
×
1821
            final Collection c = DatabaseManager.getCollection(properties.getProperty(URI) + p, properties.getProperty(USER), properties.getProperty(PASSWORD));
×
1822
            if (c == null) {
×
1823
                final EXistCollectionManagementService mgtService = current.getService(EXistCollectionManagementService.class);
×
1824
                current = mgtService.createCollection(segments[i]);
×
1825
            } else {
×
1826
                current = c;
×
1827
            }
1828
        }
1829
        path = p;
×
1830
    }
×
1831

1832
    protected Collection getCollection(final String path) throws XMLDBException {
1833
        return DatabaseManager.getCollection(properties.getProperty(URI) + path, properties.getProperty(USER), properties.getProperty(PASSWORD));
×
1834
    }
1835

1836
    private Properties loadClientProperties() {
1837
        try {
1838
            final Properties properties = ConfigurationHelper.loadProperties("client.properties", getClass());
1✔
1839
            if (properties != null) {
1!
1840
                return properties;
×
1841
            }
1842
            consoleErr("WARN - Unable to find client.properties");
1✔
1843
        } catch (final IOException e) {
1✔
1844
            consoleErr("WARN - Unable to load client.properties: " + e.getMessage());
×
1845
        }
1846

1847
        // return new empty properties
1848
        return new Properties();
1✔
1849
    }
1850

1851
    /**
1852
     * Set any relevant properties from command line arguments
1853
     *
1854
     * @param options CommandLineOptions
1855
     * @param props   Client configuration
1856
     */
1857
    protected void setPropertiesFromCommandLine(final CommandlineOptions options, final Properties props) {
1858
        options.options.forEach(properties::setProperty);
1✔
1859

1860
        options.username.ifPresent(username -> props.setProperty(USER, username));
1✔
1861
        options.password.ifPresent(password -> props.setProperty(PASSWORD, password));
1✔
1862
        boolean needPassword = options.username.isPresent() && !options.password.isPresent();
1!
1863
        if (options.useSSL) {
1!
1864
            props.setProperty(SSL_ENABLE, "TRUE");
×
1865
        }
1866
        if (options.embedded) {
1!
1867
            props.setProperty(LOCAL_MODE, "TRUE");
×
1868
            props.setProperty(URI, XmldbURI.EMBEDDED_SERVER_URI.toString());
×
1869
        }
1870
        options.embeddedConfig.ifPresent(config -> properties.setProperty(CONFIGURATION, config.toAbsolutePath().toString()));
1✔
1871
        if (options.noEmbeddedMode) {
1!
1872
            props.setProperty(NO_EMBED_MODE, "TRUE");
×
1873
        }
1874
    }
1✔
1875

1876
    /**
1877
     * Process the command line options
1878
     *
1879
     * @return true if all are successful, otherwise false
1880
     * @throws java.io.IOException
1881
     */
1882
    private boolean processCommandLineActions() throws IOException {
1883
        final boolean foundCollection = options.setCol.isPresent();
1✔
1884

1885
        // process command-line actions
1886
        if (options.reindex) {
1!
1887
            if (!foundCollection) {
×
1888
                consoleErr("Please specify target collection with --collection");
×
1889
                shutdown(false);
×
1890
                return false;
×
1891
            }
1892
            try {
1893
                reindex();
×
1894
            } catch (final XMLDBException e) {
×
1895
                consoleErr("XMLDBException while reindexing collection: " + getExceptionMessage(e));
×
1896
                e.printStackTrace();
×
1897
                return false;
×
1898
            }
1899
        }
1900

1901
        if (options.rmCol.isPresent()) {
1!
1902
            try {
1903
                rmcol(options.rmCol.get());
×
1904
            } catch (final XMLDBException e) {
×
1905
                consoleErr("XMLDBException while removing collection: " + getExceptionMessage(e));
×
1906
                e.printStackTrace();
×
1907
                return false;
×
1908
            }
1909
        }
1910

1911
        if (options.mkCol.isPresent()) {
1!
1912
            try {
1913
                mkcol(options.mkCol.get());
×
1914
            } catch (final XMLDBException e) {
×
1915
                consoleErr("XMLDBException during mkcol: " + getExceptionMessage(e));
×
1916
                e.printStackTrace();
×
1917
                return false;
×
1918
            }
1919
        }
1920

1921
        if (options.getDoc.isPresent()) {
1!
1922
            try {
1923
                final Resource res = retrieve(options.getDoc.get());
×
1924
                if (res != null) {
×
1925
                    // String data;
1926
                    if (XML_RESOURCE.equals(res.getResourceType())) {
×
1927
                        if (options.outputFile.isPresent()) {
×
1928
                            writeOutputFile(options.outputFile.get(), res.getContent());
×
1929
                        } else {
×
1930
                            consoleOut(res.getContent().toString());
×
1931
                        }
1932
                    } else {
×
1933
                        if (options.outputFile.isPresent()) {
×
1934
                            ((ExtendedResource) res).getContentIntoAFile(options.outputFile.get());
×
1935
                            ((EXistResource) res).freeResources();
×
1936
                        } else {
×
1937
                            ((ExtendedResource) res).getContentIntoAStream(System.out);
×
1938
                            consoleOut("");
×
1939
                        }
1940
                    }
1941
                }
1942
            } catch (final XMLDBException e) {
×
1943
                consoleErr("XMLDBException while trying to retrieve document: " + getExceptionMessage(e));
×
1944
                e.printStackTrace();
×
1945
                return false;
×
1946
            }
1947
        } else if (options.rmDoc.isPresent()) {
1!
1948
            if (!foundCollection) {
×
1949
                consoleErr("Please specify target collection with --collection");
×
1950
            } else {
×
1951
                try {
1952
                    remove(options.rmDoc.get());
×
1953
                } catch (final XMLDBException e) {
×
1954
                    consoleErr("XMLDBException during parse: " + getExceptionMessage(e));
×
1955
                    e.printStackTrace();
×
1956
                    return false;
×
1957
                }
1958
            }
1959
        } else if (!options.parseDocs.isEmpty()) {
1!
1960
            if (!foundCollection) {
×
1961
                consoleErr("Please specify target collection with --collection");
×
1962
            } else {
×
1963
                for (final Path path : options.parseDocs) {
×
1964
                    try {
1965
                        parse(path);
×
1966
                    } catch (final XMLDBException e) {
×
1967
                        consoleErr("XMLDBException during parse: " + getExceptionMessage(e));
×
1968
                        e.printStackTrace();
×
1969
                        return false;
×
1970
                    }
1971
                }
1972
            }
1973
        } else if (options.xpath.isPresent() || !options.queryFiles.isEmpty()) {
1!
1974
            String xpath = null;
×
1975
            if (!options.queryFiles.isEmpty()) {
×
1976
                try (final BufferedReader reader = Files.newBufferedReader(options.queryFiles.get(0))) {
×
1977
                    final StringBuilder buf = new StringBuilder();
×
1978
                    String line;
1979
                    while ((line = reader.readLine()) != null) {
×
1980
                        buf.append(line);
×
1981
                        buf.append(EOL);
×
1982
                    }
1983
                    xpath = buf.toString();
×
1984
                }
1985
            }
1986

1987
            // if no argument has been found, read query from stdin
1988
            if (options.xpath.isPresent()) {
×
1989

1990
                final String xpathStr = options.xpath.get();
×
1991
                if (!xpathStr.equals(CommandlineOptions.XPATH_STDIN)) {
×
1992
                    xpath = xpathStr;
×
1993
                } else {
×
1994
                    // read from stdin
1995
                    try (final BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in))) {
×
1996
                        final StringBuilder buf = new StringBuilder();
×
1997
                        String line;
1998
                        while ((line = stdin.readLine()) != null) {
×
1999
                            buf.append(line);
×
2000
                            buf.append(EOL);
×
2001
                        }
2002
                        xpath = buf.toString();
×
2003
                    } catch (final IOException e) {
×
2004
                        consoleErr("failed to read query from stdin");
×
2005
                        xpath = null;
×
2006
                        return false;
×
2007
                    }
2008
                }
2009
            }
2010

2011
            if (xpath != null) {
×
2012
                try {
2013
                    final ResourceSet result = find(xpath);
×
2014

2015
                    final int maxResults = options.howManyResults.filter(n -> n > 0).orElse((int) result.getSize());
×
2016
                    if (options.outputFile.isPresent()) {
×
2017
                        try (final OutputStream fos = new BufferedOutputStream(Files.newOutputStream(options.outputFile.get()));
×
2018
                             final BufferedOutputStream bos = new BufferedOutputStream(fos);
×
2019
                             final PrintStream ps = new PrintStream(bos)
×
2020
                        ) {
2021

2022
                            for (int i = 0; i < maxResults && i < result.getSize(); i++) {
×
2023
                                final Resource res = result.getResource(i);
×
2024
                                if (res instanceof ExtendedResource) {
×
2025
                                    ((ExtendedResource) res).getContentIntoAStream(ps);
×
2026
                                } else {
×
2027
                                    ps.print(res.getContent().toString());
×
2028
                                }
2029
                            }
2030
                        }
2031
                    } else {
2032
                        for (int i = 0; i < maxResults && i < result.getSize(); i++) {
×
2033
                            final Resource res = result.getResource(i);
×
2034
                            if (res instanceof ExtendedResource) {
×
2035
                                ((ExtendedResource) res).getContentIntoAStream(System.out);
×
2036
                            } else {
×
2037
                                consoleOut(String.valueOf(res.getContent()));
×
2038
                            }
2039
                        }
2040
                    }
2041
                } catch (final XMLDBException e) {
×
2042
                    consoleErr("XMLDBException during query: " + getExceptionMessage(e));
×
2043
                    e.printStackTrace();
×
2044
                    return false;
×
2045
                }
2046
            }
2047

2048
        } else if (options.xupdateFile.isPresent()) {
1!
2049
            try {
2050
                xupdate(options.setDoc, options.xupdateFile.get());
×
2051
            } catch (final XMLDBException e) {
×
2052
                consoleErr("XMLDBException during xupdate: " + getExceptionMessage(e));
×
2053
                return false;
×
2054
            } catch (final IOException e) {
×
2055
                consoleErr("IOException during xupdate: " + getExceptionMessage(e));
×
2056
                return false;
×
2057
            }
2058
        }
2059

2060
        return true;
1✔
2061
    }
2062

2063
    /**
2064
     * Ask user for login data using gui.
2065
     *
2066
     * @param props Client properties
2067
     * @return FALSE when pressed cancel, TRUE is sucessfull.
2068
     */
2069
    private boolean getGuiLoginData(final Properties props) {
2070
        return getGuiLoginData(props, ClientFrame::getLoginData);
1✔
2071
    }
2072

2073
    boolean getGuiLoginData(final Properties props, final UnaryOperator<Properties> loginPropertyOperator) {
2074
        final Properties loginData = loginPropertyOperator.apply(props);
1✔
2075
        if (loginData == null || loginData.isEmpty()) {
1!
2076
            // User pressed <cancel>
2077
            return false;
1✔
2078
        }
2079
        props.putAll(loginData);
×
2080

2081
        return true;
×
2082
    }
2083

2084
    /**
2085
     * Reusable method for connecting to database. Exits process on failure.
2086
     */
2087
    private void connectToDatabase() {
2088
        try {
2089
            connect();
1✔
2090
        } catch (final Exception cnf) {
1✔
2091
            if (options.startGUI && frame != null) {
×
2092
                frame.setStatus("Connection to database failed; message: " + cnf.getMessage());
×
2093
            } else {
×
2094
                consoleErr("Connection to database failed; message: " + cnf.getMessage());
×
2095
            }
2096
            cnf.printStackTrace();
×
2097
            System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE);
×
2098
        }
2099
    }
1✔
2100

2101
    /**
2102
     * Main processing method for the InteractiveClient object
2103
     *
2104
     * @return true on success, false on failure
2105
     * @throws Exception if an error occurs
2106
     */
2107
    public boolean run() throws Exception {
2108
        this.path = options.setCol.orElse(XmldbURI.ROOT_COLLECTION_URI);
1✔
2109

2110
        // get Elemental home
2111
        final Optional<Path> home = ConfigurationHelper.getExistHome();
1✔
2112

2113
        // get default configuration filename from the driver class and set it in properties
2114
        applyDefaultConfig(home);
1✔
2115

2116
        properties.putAll(loadClientProperties());
1✔
2117

2118
        setPropertiesFromCommandLine(options, properties);
1✔
2119

2120
        printNotice();
1✔
2121

2122
        // Fix "uri" property: Excalibur CLI can't parse dashes, so we need to URL encode them:
2123
        properties.setProperty(URI, URLDecoder.decode(properties.getProperty(URI), UTF_8.name()));
1✔
2124

2125
        final boolean interactive = isInteractive();
1✔
2126

2127
        // prompt for password if needed
2128
        if (checkLoginInfos(interactive)) {
1!
2129
            return false;
×
2130
        }
2131

2132
        historyFile = home.map(h -> h.resolve(".elemental_jac_history")).orElse(Paths.get(".elemental_jac_history"));
1✔
2133
        queryHistoryFile = home.map(h -> h.resolve(".elemental_jac_query_history")).orElse(Paths.get(".elemental_jac_query_history"));
1✔
2134
        readQueryHistory();
1✔
2135

2136
        if (interactive) {
1!
2137
            // in gui mode we use Readline for history management
2138
            // initialize Readline library
2139
            final Terminal terminal = TerminalBuilder.builder()
1✔
2140
                    .build();
1✔
2141

2142
            final History history = new DefaultHistory();
1✔
2143

2144
            console = LineReaderBuilder.builder()
1✔
2145
                    .terminal(terminal)
1✔
2146
                    .variable(LineReader.HISTORY_FILE, historyFile)
1✔
2147
                    .history(history)
1✔
2148
                    .completer(new CollectionCompleter())
1✔
2149
                    .build();
1✔
2150
        }
2151

2152
        // connect to the db
2153
        connectToDatabase();
1✔
2154

2155
        if (current == null) {
1!
2156
            if (options.startGUI && frame != null) {
×
2157
                frame.setStatus("Could not retrieve collection " + path);
×
2158
            } else {
×
2159
                consoleErr("Could not retrieve collection " + path);
×
2160
            }
2161
            shutdown(false);
×
2162
            return false;
×
2163
        }
2164

2165
        final boolean processingOK = processCommandLineActions();
1✔
2166
        if (!processingOK) {
1!
2167
            return false;
×
2168
        }
2169

2170
        if (interactive) {
1!
2171
            if (initializeGui()) return false;
1!
2172
        } else {
2173
            shutdown(false);
×
2174
        }
2175
        return true;
1✔
2176
    }
2177

2178
    private boolean checkLoginInfos(boolean interactive) {
2179
        if (!hasLoginDetails(options)) {
1!
2180
            if (interactive && options.startGUI) {
1!
2181
                final boolean haveLoginData = getGuiLoginData(properties);
1✔
2182
                if (!haveLoginData) {
1!
2183
                    return true;
×
2184
                }
2185

2186
            } else if (options.username.isPresent() && !options.password.isPresent()) {
×
2187
                try {
2188
                    properties.setProperty(PASSWORD, console.readLine("password: ", '*'));
×
2189
                } catch (final Exception e) {
×
2190
                    // ignore errors
2191
                }
2192
            }
2193
        }
2194
        return false;
1✔
2195
    }
2196

2197
    private void applyDefaultConfig(Optional<Path> home) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
2198
        Optional<Path> configFile = ConfigurationHelper.getFromSystemProperty();
1✔
2199
        if (!configFile.isPresent()) {
1!
2200
            final Class<?> cl = Class.forName(properties.getProperty(DRIVER));
×
2201
            final Field CONF_XML = cl.getDeclaredField("CONF_XML");
×
2202
            if (CONF_XML != null && home.isPresent()) {
×
2203
                configFile = Optional.ofNullable(ConfigurationHelper.lookup((String) CONF_XML.get("")));
×
2204
            }
2205
        }
2206
        configFile.ifPresent(value -> properties.setProperty(CONFIGURATION, value.toString()));
1✔
2207
    }
1✔
2208

2209
    final boolean isInteractive() {
2210
        boolean interactive = true;
1✔
2211
        if ((!options.parseDocs.isEmpty()) || options.rmDoc.isPresent() || options.getDoc.isPresent()
1!
2212
                || options.rmCol.isPresent() || options.xpath.isPresent() || (!options.queryFiles.isEmpty())
1!
2213
                || options.xupdateFile.isPresent() || options.reindex) {
1!
2214
            interactive = false;
×
2215
        }
2216
        return interactive;
1✔
2217
    }
2218

2219
    final boolean initializeGui() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
2220
        initializeFrame();
1✔
2221

2222
        // enter interactive mode
2223
        if (!options.startGUI || frame == null) {
1!
2224

2225
            // No gui
2226
            ClientAction.call(this::getResources, e -> {
×
2227
                consoleErr("XMLDBException while "
×
2228
                        + "retrieving collection contents: "
2229
                        + getExceptionMessage(e));
×
2230
                e.getCause().printStackTrace();
×
2231
            });
×
2232
            return true;
×
2233

2234
        } else {
2235

2236
            // with gui ; re-login posibility
2237
            boolean retry = true;
1✔
2238

2239
            while (retry) {
1✔
2240

2241
                AtomicReference<String> errorMessageReference = new AtomicReference<>("");
1✔
2242
                ClientAction.call(this::getResources, e -> {
1✔
2243
                    errorMessageReference.set(getExceptionMessage(e));
×
2244
                    ClientFrame.showErrorMessage(
×
2245
                            "XMLDBException occurred while retrieving collection: "
×
2246
                                    + errorMessageReference, e);
×
2247
                });
×
2248

2249
                // Determine error text. For special reasons we can retry
2250
                // to connect.
2251
                String errorMessage = errorMessageReference.get();
1✔
2252
                if (isRetryableError(errorMessage)) {
1!
2253

2254
                    final boolean haveLoginData = getGuiLoginData(properties);
×
2255
                    if (!haveLoginData) {
×
2256
                        // pressed cancel
2257
                        return true;
×
2258
                    }
2259

2260
                    // Need to shutdown ?? ask wolfgang
2261
                    shutdown(false);
×
2262

2263
                    // connect to the db
2264
                    connectToDatabase();
×
2265

2266
                } else if (!errorMessage.isEmpty()) {
1!
2267
                    // No pattern match, but we have an error. stop here
2268
                    frame.dispose();
×
2269
                    return true;
×
2270
                } else {
2271
                    // No error message, continue startup.
2272
                    retry = false;
1✔
2273
                }
2274
            }
2275
        }
2276

2277
        messageln(EOL + "type help or ? for help.");
1✔
2278

2279
        if (options.openQueryGUI) {
1!
2280
            final QueryDialog qd = new QueryDialog(this, current, properties);
×
2281
            qd.setLocation(100, 100);
×
2282
            qd.setVisible(true);
×
2283
        } else if (!options.startGUI) {
1!
2284
            readlineInputLoop();
×
2285
        } else {
×
2286
            frame.displayPrompt();
1✔
2287
        }
2288
        return false;
1✔
2289
    }
2290

2291
    boolean isRetryableError(String errorMessage) {
2292
        return errorMessage.contains("Invalid password for user") ||
1✔
2293
                errorMessage.contains("Connection refused: connect") ||
1✔
2294
                UNKNOWN_USER_PATTERN.matcher(errorMessage).find();
1✔
2295
    }
2296

2297
    private void initializeFrame() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
2298
        if (options.startGUI) {
1!
2299
            setLookAndFeel();
1✔
2300

2301
            frame = createClientFrame();
1✔
2302
            frame.setLocation(100, 100);
1✔
2303
            frame.setSize(500, 500);
1✔
2304
            frame.setVisible(true);
1✔
2305
        }
2306
    }
1✔
2307

2308
    private void setLookAndFeel() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
2309
        try {
2310
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
1✔
2311
        } catch (final UnsupportedLookAndFeelException ulafe) {
1✔
2312
            consoleErr("Warning: Unable to set native look and feel: " + ulafe.getMessage());
×
2313
        }
2314
    }
1✔
2315

2316
    ClientFrame createClientFrame() {
2317
        return new ClientFrame(this, path, properties);
×
2318
    }
2319

2320
    private boolean hasLoginDetails(final CommandlineOptions options) {
2321
        return options.username.isPresent()
1!
2322
                && options.password.isPresent()
×
2323
                && (options.embedded || options.options.containsKey("uri"));
×
2324
    }
2325

2326
    public static String getExceptionMessage(Throwable e) {
2327
        Throwable cause;
2328
        while ((cause = e.getCause()) != null) {
×
2329
            e = cause;
×
2330
        }
2331
        return e.getMessage();
×
2332
    }
2333

2334
    /**
2335
     * Read Query History file.
2336
     */
2337
    protected void readQueryHistory() {
2338
        if (!Files.isReadable(queryHistoryFile)) {
1✔
2339
            return;
1✔
2340
        }
2341
        try {
2342
            final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1✔
2343
            final DocumentBuilder builder = factory.newDocumentBuilder();
1✔
2344
            try (InputStream in = Files.newInputStream(queryHistoryFile)) {
1✔
2345
                final Document doc = builder.parse(in);
1✔
2346
                final NodeList nodes = doc.getElementsByTagName("query");
1✔
2347
                for (int i = 0; i < nodes.getLength(); i++) {
1✔
2348
                    final Element query = (Element) nodes.item(i);
1✔
2349
                    final StringBuilder value = new StringBuilder();
1✔
2350
                    Node next = query.getFirstChild();
1✔
2351
                    while (next != null) {
1✔
2352
                        value.append(next.getTextContent());
1✔
2353
                        next = next.getNextSibling();
1✔
2354
                    }
2355
                    queryHistory.addLast(value.toString());
1✔
2356
                }
2357
            }
2358
        } catch (final Exception e) {
×
2359
            if (options.startGUI) {
×
2360
                ClientFrame.showErrorMessage(
×
2361
                        "Error while reading query history: " + e.getMessage(),
×
2362
                        e);
×
2363
            } else {
×
2364
                errorln("Error while reading query history: "
×
2365
                        + e.getMessage());
×
2366
            }
2367
        }
2368
    }
1✔
2369

2370
    protected void addToHistory(final String query) {
2371
        queryHistory.add(query);
×
2372
    }
×
2373

2374
    protected void writeQueryHistory() {
2375
        try {
2376
            console.getHistory().save();
1✔
2377
        } catch (final IOException e) {
1✔
2378
            consoleErr("Could not write history File to " + historyFile.toAbsolutePath().toString());
×
2379
        }
2380

2381
        final SAXSerializer serializer = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
1✔
2382
        try (final BufferedWriter writer = Files.newBufferedWriter(queryHistoryFile, StandardCharsets.UTF_8)) {
1✔
2383
            serializer.setOutput(writer, null);
1✔
2384
            int p = 0;
1✔
2385
            if (queryHistory.size() > 20) {
1!
2386
                p = queryHistory.size() - 20;
1✔
2387
            }
2388
            final AttributesImpl attrs = new AttributesImpl();
1✔
2389
            serializer.startDocument();
1✔
2390
            serializer.startElement(XMLConstants.NULL_NS_URI, "history", "history", attrs);
1✔
2391
            for (final ListIterator<String> i = queryHistory.listIterator(p); i.hasNext(); ) {
1✔
2392
                serializer.startElement(XMLConstants.NULL_NS_URI, "query", "query", attrs);
1✔
2393
                final String next = i.next();
1✔
2394
                serializer.characters(next.toCharArray(), 0, next.length());
1✔
2395
                serializer.endElement(XMLConstants.NULL_NS_URI, "query", "query");
1✔
2396
            }
2397
            serializer.endElement(XMLConstants.NULL_NS_URI, "history", "history");
1✔
2398
            serializer.endDocument();
1✔
2399
        } catch (final IOException e) {
×
2400
            consoleErr("IO error while writing query history.");
×
2401
        } catch (final SAXException e) {
×
2402
            consoleErr("SAX exception while writing query history.");
×
2403
        } finally {
2404
            SerializerPool.getInstance().returnObject(serializer);
1✔
2405
        }
2406

2407
    }
1✔
2408

2409
    public void readlineInputLoop() {
2410
        String line;
2411
        boolean cont = true;
×
2412
        while (cont) {
×
2413
            try {
2414
                if ("true".equals(properties.getProperty(COLORS))) {
×
2415
                    line = console.readLine(ANSI_CYAN + "elemental:" + path + "> "
×
2416
                            + ANSI_WHITE);
2417
                } else {
×
2418
                    line = console.readLine("elemental:" + path + "> ");
×
2419
                }
2420
                if (line != null) {
×
2421
                    cont = process(line);
×
2422
                }
2423

2424
            } catch (final EndOfFileException e) {
×
2425
                break;
×
2426
            } catch (final Exception e) {
×
2427
                e.printStackTrace();
×
2428
            }
2429
        }
2430

2431
        try {
2432
            console.getHistory().save();
×
2433
        } catch (final IOException e) {
×
2434
            consoleErr("Could not write history File to " + historyFile.toAbsolutePath().toString());
×
2435
        }
2436
        shutdown(false);
×
2437
        messageln("quit.");
×
2438
    }
×
2439

2440
    protected final void shutdown(final boolean force) {
2441
        lazyTraceWriter.ifPresent(writer -> {
×
2442
            try {
2443
                writer.write("</query-log>");
×
2444
                writer.close();
×
2445
            } catch (final IOException e1) {
×
2446
            }
2447
        });
×
2448

2449
        try {
2450
            final DatabaseInstanceManager mgr = current.getService(DatabaseInstanceManager.class);
×
2451
            if (mgr == null) {
×
2452
                consoleErr("service is not available");
×
2453
            } else if (mgr.isLocalInstance() || force) {
×
2454
                consoleOut("shutting down database...");
×
2455
                mgr.shutdown();
×
2456
            }
2457
        } catch (final XMLDBException e) {
×
2458
            consoleErr("database shutdown failed: " + e.getMessage());
×
2459
            e.printStackTrace();
×
2460
        } finally {
2461
            try {
2462
                current.close();
×
2463
                current = null;
×
2464

2465
                DatabaseManager.deregisterDatabase(database);
×
2466
                database = null;
×
2467
            } catch (final XMLDBException e) {
×
2468
                consoleErr("unable to close collection: " + e.getMessage());
×
2469
                e.printStackTrace();
×
2470
            }
2471
        }
2472
    }
×
2473

2474
    /**
2475
     * print copyright notice - after parsing command line options, or it can't be silenced!
2476
     */
2477
    public void printNotice() {
2478
        if (!options.quiet) {
1!
2479
            messageln(getNotice());
1✔
2480
        }
2481
    }
1✔
2482

2483
    public String getNotice() {
2484
        return getNotice(SystemProperties.getInstance()::getSystemProperty);
1✔
2485
    }
2486

2487
    String getNotice(BinaryOperator<String> propertyAction) {
2488
        final StringBuilder builder = new StringBuilder();
1✔
2489
        builder.append(propertyAction.apply("product-name", "Elemental"));
1✔
2490
        builder.append(" version ");
1✔
2491
        builder.append(propertyAction.apply("product-version", "unknown"));
1✔
2492
        final String gitCommitId = propertyAction.apply("git-commit", "");
1✔
2493
        if (!gitCommitId.isEmpty()) {
1!
2494
            builder.append(" (").append(gitCommitId).append(")");
1✔
2495
        }
2496
        builder.append(", Copyright (C) 2024-");
1✔
2497
        builder.append(Calendar.getInstance().get(Calendar.YEAR));
1✔
2498
        builder.append(" Evolved Binary Ltd");
1✔
2499
        builder.append(EOL);
1✔
2500
        builder.append("Elemental comes with ABSOLUTELY NO WARRANTY.");
1✔
2501
        builder.append(EOL);
1✔
2502
        builder.append("This is free software, and you are welcome to redistribute it");
1✔
2503
        builder.append(EOL);
1✔
2504
        builder.append("under certain conditions; for details read the license file.");
1✔
2505
        builder.append(EOL);
1✔
2506
        return builder.toString();
1✔
2507
    }
2508

2509
    final void message(final String msg) {
2510
        if (options.quiet) {
1!
2511
            return;
×
2512
        }
2513
        if (options.startGUI && frame != null) {
1!
2514
            frame.display(msg);
1✔
2515
        } else {
1✔
2516
            consoleOut(msg);
1✔
2517
        }
2518
    }
1✔
2519

2520
    final void messageln(final String msg) {
2521
        if (options.quiet) {
1!
2522
            return;
×
2523
        }
2524
        if (options.startGUI && frame != null) {
1!
2525
            frame.display(msg + EOL);
1✔
2526
        } else {
1✔
2527
            consoleOut(msg);
1✔
2528
        }
2529
    }
1✔
2530

2531
    static final void consoleOut(final String msg) {
2532
        System.out.println(msg); //NOSONAR this has to go to the console
1✔
2533
    }
1✔
2534

2535
    final void errorln(final String msg) {
2536
        if (options.startGUI && frame != null) {
1!
2537
            frame.display(msg + EOL);
1✔
2538
        } else {
1✔
2539
            consoleErr(msg);
1✔
2540
        }
2541
    }
1✔
2542

2543
    static final void consoleErr(final String msg) {
2544
        System.err.println(msg); //NOSONAR this has to go to the console
1✔
2545
    }
1✔
2546

2547
    private Collection resolveCollection(final XmldbURI path) throws XMLDBException {
2548
        return DatabaseManager.getCollection(
×
2549
                properties.getProperty(URI) + path,
×
2550
                properties.getProperty(USER),
×
2551
                properties.getProperty(PASSWORD));
×
2552
    }
2553

2554
    private Resource resolveResource(final XmldbURI path) throws XMLDBException {
2555
        try {
2556
            final XmldbURI collectionPath =
×
2557
                    path.numSegments() == 1 ?
×
2558
                            XmldbURI.xmldbUriFor(current.getName()) : path.removeLastSegment();
×
2559

2560
            final XmldbURI resourceName = path.lastSegment();
×
2561

2562
            final Collection collection = resolveCollection(collectionPath);
×
2563

2564
            if (collection == null) {
×
2565
                messageln("Collection " + collectionPath + " not found.");
×
2566
                return null;
×
2567
            }
2568

2569
            messageln("Locating resource " + resourceName + " in collection " + collection.getName());
×
2570

2571
            return collection.getResource(resourceName.toString());
×
2572
        } catch (final URISyntaxException e) {
×
2573
            errorln("could not parse collection name into a valid URI: " + e.getMessage());
×
2574
        }
2575
        return null;
×
2576
    }
2577

2578
    private class CollectionCompleter implements Completer {
1✔
2579

2580
        @Override
2581
        public void complete(final LineReader lineReader, final ParsedLine parsedLine, final List<Candidate> candidates) {
2582
            final String buffer = parsedLine.line();
×
2583
            int p = buffer.lastIndexOf(' ');
×
2584
            final String toComplete;
2585
            if (p > -1 && ++p < buffer.length()) {
×
2586
                toComplete = buffer.substring(p);
×
2587
            } else {
×
2588
                toComplete = buffer;
×
2589
            }
2590
            final Set<String> set = completions.tailSet(toComplete);
×
2591
            if (set != null && !set.isEmpty()) {
×
2592
                for (final String next : completions.tailSet(toComplete)) {
×
2593
                    if (next.startsWith(toComplete)) {
×
2594
                        candidates.add(new Candidate(next, next, null, null, null, null, true));
×
2595
                    }
2596
                }
2597
            }
2598
        }
×
2599
    }
2600

2601
    public static class ProgressObserver implements Observer {
×
2602

2603
        final ProgressBar elementsProgress = new ProgressBar("storing elements");
×
2604
        Observable lastObservable = null;
×
2605
        final ProgressBar parseProgress = new ProgressBar("storing nodes   ");
×
2606

2607
        @Override
2608
        public void update(final Observable o, final Object obj) {
2609
            final ProgressIndicator ind = (ProgressIndicator) obj;
×
2610
            if (lastObservable == null || o != lastObservable) {
×
2611
                consoleOut("");
×
2612
            }
2613

2614
            if (o instanceof ElementIndex) {
×
2615
                elementsProgress.set(ind.getValue(), ind.getMax());
×
2616
            } else {
×
2617
                parseProgress.set(ind.getValue(), ind.getMax());
×
2618
            }
2619

2620
            lastObservable = o;
×
2621
        }
×
2622
    }
2623

2624
    private void writeOutputFile(final Path file, final Object data) throws IOException {
2625
        try (final OutputStream os = new BufferedOutputStream(Files.newOutputStream(file))) {
×
2626
            if (data instanceof byte[]) {
×
2627
                os.write((byte[]) data);
×
2628
            } else {
×
2629
                try (final Writer writer = new OutputStreamWriter(os, Charset.forName(properties.getProperty(ENCODING)))) {
×
2630
                    writer.write(data.toString());
×
2631
                }
2632
            }
2633
        }
2634
    }
×
2635

2636
    private static String formatString(String s1, final String s2, final int width) {
2637
        final StringBuilder buf = new StringBuilder(width);
×
2638
        if (s1.length() > width) {
×
2639
            s1 = s1.substring(0, width - 1);
×
2640
        }
2641
        buf.append(s1);
×
2642
        final int fill = width - (s1.length() + s2.length());
×
2643
        for (int i = 0; i < fill; i++) {
×
2644
            buf.append(' ');
×
2645
        }
2646
        buf.append(s2);
×
2647
        return buf.toString();
×
2648
    }
2649

2650
    public static Properties getSystemProperties() {
2651
        final Properties sysProperties = new Properties();
×
2652
        try {
2653
            sysProperties.load(InteractiveClient.class.getClassLoader().getResourceAsStream("org/exist/system.properties"));
×
2654
        } catch (final IOException e) {
×
2655
            consoleErr("Unable to load system.properties from class loader");
×
2656
        }
2657

2658
        return sysProperties;
×
2659
    }
2660

2661
    public static ImageIcon getElementalIcon(final Class clazz) {
2662
        return new javax.swing.ImageIcon(clazz.getResource("/org/exist/client/icons/elemental-device.png"));
×
2663
    }
2664
}
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