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

openmrs / openmrs-core / 17208175973

25 Aug 2025 12:00PM UTC coverage: 63.742% (+0.07%) from 63.671%
17208175973

push

github

ibacher
TRUNK-6395: Saner scheme for copying properties from the installation script (#5260)

0 of 2 new or added lines in 1 file covered. (0.0%)

697 existing lines in 13 files now uncovered.

22147 of 34745 relevant lines covered (63.74%)

0.64 hits per line

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

73.97
/api/src/main/java/org/openmrs/api/storage/LocalStorageService.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.api.storage;
11

12
import javax.activation.MimetypesFileTypeMap;
13
import java.io.File;
14
import java.io.IOException;
15
import java.io.InputStream;
16
import java.nio.file.Files;
17
import java.nio.file.Path;
18
import java.nio.file.Paths;
19
import java.nio.file.attribute.BasicFileAttributes;
20
import java.util.stream.Stream;
21

22
import org.apache.commons.lang.StringUtils;
23
import org.openmrs.api.StorageService;
24
import org.openmrs.api.stream.StreamDataService;
25
import org.openmrs.util.OpenmrsUtil;
26
import org.slf4j.Logger;
27
import org.slf4j.LoggerFactory;
28
import org.springframework.beans.factory.annotation.Autowired;
29
import org.springframework.beans.factory.annotation.Qualifier;
30
import org.springframework.beans.factory.annotation.Value;
31
import org.springframework.context.annotation.Conditional;
32
import org.springframework.stereotype.Service;
33

34

35
/**
36
 * Used to persist data in a local file system or volumes.
37
 * <p>
38
 * It is the default implementation of StorageService.
39
 * 
40
 * @since 2.8.0, 2.7.5, 2.6.16, 2.5.15
41
 */
42
@Service
43
@Conditional(StorageServiceCondition.class)
44
@Qualifier("local")
45
public class LocalStorageService extends BaseStorageService implements StorageService {
46
        
47
        protected static final Logger log = LoggerFactory.getLogger(LocalStorageService.class);
1✔
48
        
49
        private final Path storageDir;
50
        
51
        private final MimetypesFileTypeMap mimetypes = new MimetypesFileTypeMap();
1✔
52
        
53
        public LocalStorageService(@Value("${storage.local.dir:}") String storageDir, @Autowired StreamDataService streamService) {
54
                super(streamService);
1✔
55
                this.storageDir = StringUtils.isBlank(storageDir) ? Paths.get(OpenmrsUtil.getApplicationDataDirectory(), 
1✔
56
                        "storage").toAbsolutePath() : Paths.get(storageDir).toAbsolutePath();
1✔
57
        }
1✔
58
        
59
        @Override
60
        public InputStream getData(final String key) throws IOException {
61
                return Files.newInputStream(getPath(key));
1✔
62
        }
63

64
        /**
65
         * It needs to be evaluated each time as it changes over time in tests...
66
         * <p>
67
         * It's only added to support legacy storage location, which will be removed in some later version.
68
         * 
69
         * @return the legacy storage dir
70
         */
71
        private Path getLegacyStorageDir() {
72
                return Paths.get(OpenmrsUtil.getApplicationDataDirectory());
1✔
73
        }
74

75
        @Override
76
        public ObjectMetadata getMetadata(final String key) throws IOException {
UNCOV
77
                Path path = getPath(key);
×
78

UNCOV
79
                BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
×
UNCOV
80
                String filename = decodeKey(path.getFileName().toString());
×
81
                
UNCOV
82
                return ObjectMetadata.builder()
×
UNCOV
83
                                .setLength(attributes.size())
×
UNCOV
84
                                .setMimeType(mimetypes.getContentType(filename))
×
UNCOV
85
                                .setFilename(filename)
×
86
                                .setCreationTime(attributes.creationTime().toInstant()).build();
×
87
        }
88

89
        Path getPath(String key) {
90
                Path legacyStorageDir = getLegacyStorageDir();
1✔
91
                Path legacyPath = legacyStorageDir.resolve(key);
1✔
92
                if (Files.exists(legacyPath)) {
1✔
93
                        if (!legacyPath.normalize().startsWith(legacyStorageDir)) {
1✔
94
                                throw new IllegalArgumentException("Key must not point outside legacy storage dir. Wrong key: " + key);
1✔
95
                        }
96
                        return legacyPath;
1✔
97
                } else {
98
                        Path path = storageDir.resolve(encodeKey(key));
1✔
99
                        assertKeyInStorageDir(path, key);
1✔
100
                        return path;
1✔
101
                }
102
        }
103
        
104
        @Override
105
        public Stream<String> getKeys(final String moduleIdOrGroup, final String keyPrefix) throws IOException {
106
                String key = encodeKey(newKey(moduleIdOrGroup, keyPrefix, null));
1✔
107

108
                int lastDirIndex = key.lastIndexOf("/");
1✔
109
                String lastDir = "";
1✔
110
                if (lastDirIndex != -1) {
1✔
111
                        lastDir = key.substring(0, lastDirIndex + 1);
1✔
112
                }
113

114
                Path searchDir = storageDir.resolve(lastDir);
1✔
115

116
                if (!searchDir.toFile().isDirectory()) {
1✔
UNCOV
117
                        return Stream.empty();
×
118
                }
119
                
120
                @SuppressWarnings("resource")
121
                Stream<Path> stream = Files.list(searchDir);
1✔
122
                // Filter out files that start with dot (hidden files)
123
                return stream.filter(
1✔
124
                    path -> !path.getFileName().toString().startsWith("."))
1✔
125
                        .map(path -> {
1✔
126
                                        String foundKey = storageDir.relativize(path).toString();
1✔
127
                                        foundKey = decodeKey(foundKey);
1✔
128
                                        foundKey += (Files.isDirectory(path)) ? File.separator : "";
1✔
129
                                        foundKey = foundKey.replace(File.separatorChar, '/'); //MS Windows support
1✔
130
                                        return foundKey;
1✔
131
                                }).filter(foundKey -> foundKey.startsWith(key));
1✔
132
        }
133

134
        Path newPath(String key) throws IOException {
135
                key = encodeKey(key);
1✔
136
                key = key.replace('/', File.separatorChar);
1✔
137
                Path newPath = storageDir.resolve(key);
1✔
138
                assertKeyInStorageDir(newPath, key);
1✔
139
                
140
                Files.createDirectories(newPath.getParent());
1✔
141
                
142
                return newPath;
1✔
143
        }
144
        
145
        void assertKeyInStorageDir(Path path, String key) {
146
                if (!path.normalize().startsWith(storageDir)) {
1✔
147
                        throw new IllegalArgumentException("Key must not point outside storage dir. Wrong key: " + key);
1✔
148
                }
149
        }
1✔
150

151
        @Override
152
        public String saveData(InputStream inputStream, ObjectMetadata metadata, String moduleIdOrGroup, String keySuffix) throws IOException {
153
                String key = newKey(moduleIdOrGroup, keySuffix, metadata != null ? metadata.getFilename() : null);
1✔
154
                Path target = newPath(key);
1✔
155
                try {
156
                        Files.copy(inputStream, target);
1✔
157
                } catch (IOException e) {
1✔
158
                        purgeData(key);
1✔
159
                        throw e;
1✔
160
                }
1✔
161
                return key;
1✔
162
        }
163
        
164
        @Override
165
        public boolean purgeData(String key) throws IOException {
166
                if (key == null) return false;
1✔
167
                
168
                try {
169
                        return Files.deleteIfExists(getPath(key));
1✔
170
                }
UNCOV
171
                catch (Exception e) {
×
UNCOV
172
                        log.error("Error deleting key: {}", key, e);
×
173
                        try {
UNCOV
174
                                File file = getPath(key).toFile();
×
UNCOV
175
                                if (file.exists()) {
×
UNCOV
176
                                        file.deleteOnExit();
×
UNCOV
177
                                        return true;
×
178
                                } else {
UNCOV
179
                                        return false;
×
180
                                }
UNCOV
181
                        } catch (Exception deleteException) {
×
UNCOV
182
                                log.error("Error marking key for deletion: {}", key, deleteException);
×
183
                        }
UNCOV
184
                        return false;
×
185
                }
186
        }
187
        
188
        @Override
189
        public boolean exists(String key) {
190
                return Files.exists(getPath(key));
1✔
191
        }
192
}
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

© 2026 Coveralls, Inc