• 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

84.82
/exist-core/src/main/java/org/exist/backup/restore/AppRestoreUtils.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.backup.restore;
50

51
import org.apache.logging.log4j.LogManager;
52
import org.apache.logging.log4j.Logger;
53
import org.exist.Namespaces;
54
import org.exist.backup.BackupDescriptor;
55
import org.exist.repo.Deployment;
56
import org.exist.repo.ExistRepository;
57
import org.exist.storage.DBBroker;
58
import org.exist.util.XMLReaderPool;
59
import org.exist.xmldb.XmldbURI;
60
import org.expath.pkg.repo.Package;
61
import org.expath.pkg.repo.PackageException;
62
import org.expath.pkg.repo.Packages;
63
import org.expath.pkg.repo.deps.Semver;
64
import org.xml.sax.Attributes;
65
import org.xml.sax.InputSource;
66
import org.xml.sax.SAXException;
67
import org.xml.sax.XMLReader;
68
import org.xml.sax.helpers.DefaultHandler;
69

70
import java.io.IOException;
71
import java.util.*;
72

73
import static org.exist.util.StringUtil.isNullOrEmpty;
74

75
/**
76
 * Utility to compare the applications contained in a backup with the already
77
 * installed applications in the package repo.
78
 *
79
 * @author Wolfgang
80
 */
81
public class AppRestoreUtils {
×
82

83
    private final static Logger LOG = LogManager.getLogger(AppRestoreUtils.class);
1✔
84

85
    private final static String PKG_NAMESPACE = "http://expath.org/ns/pkg";
1✔
86

87
    /**
88
     * Inspects the apps contained in the backup against installed apps in the database
89
     * and return a set of symbolic backup paths pointing to the collection of those
90
     * apps for which newer versions are installed within the database. The returned
91
     * paths may then be ignored during a restore.
92
     *
93
     * The method attempts to be fail safe to make sure even bad backups can be restored. Errors
94
     * reading package descriptors are thus only logged and should not abort the process.
95
     *
96
     * @param broker the broker used for reading the backup and retrieving the expath repo
97
     * @param descriptors a queue of backup descriptors to inspect
98
     * @return a set of paths for which newer versions exist in the database. may be empty.
99
     */
100
    public static Set<String> checkApps(final DBBroker broker, final Deque<BackupDescriptor> descriptors) {
101
        final List<AppDetail> apps = getAppsFromBackup(broker, descriptors);
1✔
102
        final Set<String> paths = new HashSet<>();
1✔
103
        final Optional<ExistRepository> repo = broker.getBrokerPool().getExpathRepo();
1✔
104
        if (repo.isPresent()) {
1!
105
            for (final AppDetail app: apps) {
1✔
106
                final Packages packages = repo.get().getParentRepo().getPackages(app.name);
1✔
107
                if (packages != null) {
1!
108
                    final Package latest = packages.latest();
1✔
109
                    try {
110
                        final Semver version = Semver.parse(latest.getVersion());
1✔
111
                        if (version.compareTo(app.version) > 0) {
1✔
112
                            paths.add(app.path);
1✔
113
                        }
114
                    } catch (PackageException e) {
1✔
115
                        LOG.warn("Invalid semver in expath repository for {}", app.name, e);
×
116
                    }
117
                }
118
            }
119
        }
120
        return paths;
1✔
121
    }
122

123
    /**
124
     * Inspect all collections which may belong to apps in the backup descriptor. Return a list
125
     * of {@link AppDetail} objects containing the symbolic path, name and version of every app
126
     * found.
127
     *
128
     * The method attempts to be fail safe to make sure even bad backups can be restored. Errors
129
     * reading package descriptors are thus only logged and should not abort the process.
130
     *
131
     * @param broker the broker to use for parsing the descriptor and obtaining the app root
132
     * @param descriptors a queue of backup descriptors to inspect
133
     * @return list of application details
134
     */
135
    private static List<AppDetail> getAppsFromBackup(final DBBroker broker, final Deque<BackupDescriptor> descriptors) {
136
        final String appRoot = getAppRoot(broker);
1✔
137
        final List<AppDetail> result = new ArrayList<>();
1✔
138
        final XMLReaderPool parserPool = broker.getBrokerPool().getParserPool();
1✔
139
        for (final BackupDescriptor descriptor : descriptors) {
1✔
140
            final BackupDescriptor apps = descriptor.getChildBackupDescriptor(appRoot);
1✔
141
            if (apps != null) {
1✔
142
                getAppsFromSubColl(result, parserPool, apps);
1✔
143
            }
144

145
            final BackupDescriptor system = descriptor.getChildBackupDescriptor("system");
1✔
146
            if (system != null) {
1✔
147
                final BackupDescriptor repo = system.getChildBackupDescriptor("repo");
1✔
148
                if (repo != null) {
1✔
149
                    getAppsFromSubColl(result, parserPool, repo);
1✔
150
                }
151
            }
152
        }
153
        return result;
1✔
154
    }
155

156
    private static void getAppsFromSubColl(final List<AppDetail> result, final XMLReaderPool parserPool, final BackupDescriptor descriptor) {
157
        final List<String> collections = getSubcollectionNames(parserPool, descriptor);
1✔
158

159
        for (final String collection : collections) {
1✔
160
            final BackupDescriptor app = descriptor.getChildBackupDescriptor(collection);
1✔
161
            final InputSource is = app.getInputSource("expath-pkg.xml");
1✔
162
            if (is != null) {
1!
163
                XMLReader reader = null;
1✔
164
                try {
165
                    reader = parserPool.borrowXMLReader();
1✔
166
                    reader.setContentHandler(new DefaultHandler() {
1✔
167
                        @Override
168
                        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
169
                            if (PKG_NAMESPACE.equals(uri) && "package".equals(localName)) {
1!
170
                                final String version = attributes.getValue("version");
1✔
171
                                final String name = attributes.getValue("name");
1✔
172
                                if (isNullOrEmpty(version) || isNullOrEmpty(name)) {
1!
173
                                    LOG.warn("Invalid package descriptor for {}", app.getSymbolicPath());
×
174
                                    return;
×
175
                                }
176
                                try {
177
                                    final AppDetail detail = new AppDetail(app.getSymbolicPath(), name, Semver.parse(version));
1✔
178
                                    result.add(detail);
1✔
179
                                } catch (PackageException e) {
1✔
180
                                    LOG.warn("Invalid semver found while parsing {}", app.getSymbolicPath());
×
181
                                }
182
                            }
183
                        }
1✔
184
                    });
185
                    reader.parse(is);
1✔
186
                } catch (IOException | SAXException e) {
1✔
187
                    LOG.warn("Parse exception while parsing {}", app.getSymbolicPath("expath-pkg.xml", false));
×
188
                } finally {
189
                    if (reader != null) {
1!
190
                        parserPool.returnXMLReader(reader);
1✔
191
                    }
192
                }
193
            }
194
        }
195
    }
1✔
196

197
    private static List<String> getSubcollectionNames(final XMLReaderPool parserPool, final BackupDescriptor apps) {
198
        final List<String> collections = new ArrayList<>();
1✔
199
        try {
200
            apps.parse(parserPool, new DefaultHandler() {
1✔
201
                @Override
202
                public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
203
                    if (Namespaces.EXIST_NS.equals(uri) && "subcollection".equals(localName)) {
1!
204
                        collections.add(attributes.getValue("filename"));
1✔
205
                    }
206
                }
1✔
207
            });
208
        } catch (IOException | SAXException e) {
1✔
209
            LOG.warn("SAX error while parsing backup descriptor {}", apps.getSymbolicPath(), e);
×
210
        }
211
        return collections;
1✔
212
    }
213

214
    /**
215
     * Get the database root path for applications, removing /db and trailing slash.
216
     * @param broker the broker to get the configuration from
217
     * @return the root path for applications
218
     */
219
    private static String getAppRoot(final DBBroker broker) {
220
        String appRoot = (String) broker.getConfiguration().getProperty(Deployment.PROPERTY_APP_ROOT);
1✔
221
        if (appRoot.startsWith(XmldbURI.ROOT_COLLECTION + '/')) {
1!
222
            appRoot = appRoot.substring(XmldbURI.ROOT_COLLECTION.length() + 1);
1✔
223
        }
224
        if (appRoot.endsWith("/")) {
1!
225
            appRoot = appRoot.substring(0, appRoot.length() - 1);
1✔
226
        }
227
        return appRoot;
1✔
228
    }
229

230
    final static class AppDetail {
231
        protected final String path;
232
        protected final String name;
233
        protected final Semver version;
234

235
        AppDetail(String path, String name, Semver version) {
1✔
236
            this.path = path;
1✔
237
            this.name = name;
1✔
238
            this.version = version;
1✔
239
        }
1✔
240
    }
241
}
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