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

future-architect / uroborosql / #700

pending completion
#700

push

HidekiSugimoto189
fix java8 coding style

285 of 285 new or added lines in 36 files covered. (100.0%)

7721 of 8533 relevant lines covered (90.48%)

0.9 hits per line

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

87.21
/src/main/java/jp/co/future/uroborosql/store/SqlResourceManagerImpl.java
1
/**
2
 * Copyright (c) 2017-present, Future Corporation
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
package jp.co.future.uroborosql.store;
8

9
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
10
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
11
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
12
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
13

14
import java.io.BufferedReader;
15
import java.io.IOException;
16
import java.io.InputStreamReader;
17
import java.net.JarURLConnection;
18
import java.net.URISyntaxException;
19
import java.net.URL;
20
import java.nio.charset.Charset;
21
import java.nio.file.FileSystem;
22
import java.nio.file.FileSystemNotFoundException;
23
import java.nio.file.FileSystems;
24
import java.nio.file.Files;
25
import java.nio.file.Path;
26
import java.nio.file.Paths;
27
import java.nio.file.WatchEvent;
28
import java.nio.file.WatchKey;
29
import java.nio.file.WatchService;
30
import java.nio.file.attribute.FileTime;
31
import java.util.ArrayList;
32
import java.util.Arrays;
33
import java.util.Collections;
34
import java.util.List;
35
import java.util.Map;
36
import java.util.ServiceLoader;
37
import java.util.Set;
38
import java.util.concurrent.ConcurrentHashMap;
39
import java.util.concurrent.ExecutorService;
40
import java.util.concurrent.Executors;
41
import java.util.concurrent.TimeUnit;
42
import java.util.stream.Collectors;
43
import java.util.stream.StreamSupport;
44

45
import org.slf4j.Logger;
46
import org.slf4j.LoggerFactory;
47

48
import jp.co.future.uroborosql.dialect.Dialect;
49
import jp.co.future.uroborosql.exception.UroborosqlRuntimeException;
50
import jp.co.future.uroborosql.utils.StringUtils;
51

52
/**
53
 * SQLリソース管理実装クラス
54
 *
55
 * @author H.Sugimoto
56
 */
57
public class SqlResourceManagerImpl implements SqlResourceManager {
58
        /** ロガー */
59
        private static final Logger LOG = LoggerFactory.getLogger("jp.co.future.uroborosql.log");
1✔
60

61
        /** zip, jar内のファイルのscheme */
62
        private static final String SCHEME_JAR = "jar";
63
        /** ファイルシステム上のファイルのscheme */
64
        private static final String SCHEME_FILE = "file";
65

66
        /** SQLファイルロードのデフォルトルートパス */
67
        private static final String DEFAULT_LOAD_PATH = "sql";
68

69
        /** 有効なDialectのSet */
70
        private static final Set<String> dialects = StreamSupport
1✔
71
                        .stream(ServiceLoader.load(Dialect.class).spliterator(), false)
1✔
72
                        .map(Dialect::getDatabaseType)
1✔
73
                        .collect(Collectors.toSet());
1✔
74

75
        /** SQLファイルをロードするルートパスのリスト */
76
        private final List<Path> loadPaths;
77

78
        /** SQLファイルをロードするルートパスの各階層を保持する配列(ルートパス特定用) */
79
        private final List<String[]> loadPathPartsList;
80

81
        /** SQLファイル拡張子 */
82
        private final String fileExtension;
83

84
        /** SQLファイルエンコーディング */
85
        private final Charset charset;
86

87
        /** SQLファイルの変更を検知するかどうか */
88
        private final boolean detectChanges;
89

90
        /** Dialect */
91
        private Dialect dialect;
92

93
        /** SQLファイル監視サービス */
94
        private WatchService watcher;
95

96
        /** SQLファイル監視サービスの実行サービス */
97
        private ExecutorService es;
98

99
        /** sqlNameとそれに対するSqlInfoの紐付きを持つMap */
100
        private final ConcurrentHashMap<String, SqlInfo> sqlInfos = new ConcurrentHashMap<>();
1✔
101

102
        /** WatchKeyに対するディレクトリPathを取得するためのMap */
103
        private final ConcurrentHashMap<WatchKey, Path> watchDirs = new ConcurrentHashMap<>();
1✔
104

105
        /**
106
         * コンストラクタ
107
         */
108
        public SqlResourceManagerImpl() {
109
                this(DEFAULT_LOAD_PATH);
1✔
110
        }
1✔
111

112
        /**
113
         * コンストラクタ
114
         *
115
         * @param detectChanges SQLファイルの変更を検知するかどうか
116
         */
117
        public SqlResourceManagerImpl(final boolean detectChanges) {
118
                this(DEFAULT_LOAD_PATH, null, null, detectChanges);
1✔
119
        }
1✔
120

121
        /**
122
         * コンストラクタ
123
         *
124
         * @param loadPath SQLファイルをロードするルートパス
125
         */
126
        public SqlResourceManagerImpl(final String loadPath) {
127
                this(loadPath, null);
1✔
128
        }
1✔
129

130
        /**
131
         * コンストラクタ
132
         *
133
         * @param loadPath SQLファイルをロードするルートパス
134
         * @param fileExtension SQLファイル拡張子
135
         */
136
        public SqlResourceManagerImpl(final String loadPath, final String fileExtension) {
137
                this(loadPath, fileExtension, null);
1✔
138
        }
1✔
139

140
        /**
141
         * コンストラクタ
142
         *
143
         * @param loadPath SQLファイルをロードするルートパス
144
         * @param fileExtension SQLファイル拡張子
145
         * @param charset SQLファイルエンコーディング
146
         */
147
        public SqlResourceManagerImpl(final String loadPath, final String fileExtension, final Charset charset) {
148
                this(loadPath, fileExtension, charset, false);
1✔
149
        }
1✔
150

151
        /**
152
         * コンストラクタ
153
         *
154
         * @param loadPath SQLファイルをロードするルートパス
155
         * @param fileExtension SQLファイル拡張子
156
         * @param charset SQLファイルエンコーディング
157
         * @param detectChanges SQLファイルの変更を検知するかどうか
158
         */
159
        public SqlResourceManagerImpl(final String loadPath, final String fileExtension, final Charset charset,
160
                        final boolean detectChanges) {
161
                this(Arrays.asList(loadPath), fileExtension, charset, detectChanges);
1✔
162
        }
1✔
163

164
        /**
165
         * コンストラクタ
166
         *
167
         * @param loadPaths SQLファイルをロードするルートパスのリスト
168
         */
169
        public SqlResourceManagerImpl(final List<String> loadPaths) {
170
                this(loadPaths, null);
1✔
171
        }
1✔
172

173
        /**
174
         * コンストラクタ
175
         *
176
         * @param loadPaths SQLファイルをロードするルートパスのリスト
177
         * @param fileExtension SQLファイル拡張子
178
         */
179
        public SqlResourceManagerImpl(final List<String> loadPaths, final String fileExtension) {
180
                this(loadPaths, fileExtension, null);
1✔
181
        }
1✔
182

183
        /**
184
         * コンストラクタ
185
         *
186
         * @param loadPaths SQLファイルをロードするルートパスのリスト
187
         * @param fileExtension SQLファイル拡張子
188
         * @param charset SQLファイルエンコーディング
189
         */
190
        public SqlResourceManagerImpl(final List<String> loadPaths, final String fileExtension, final Charset charset) {
191
                this(loadPaths, fileExtension, charset, false);
1✔
192
        }
1✔
193

194
        /**
195
         * コンストラクタ
196
         *
197
         * @param loadPaths SQLファイルをロードするルートパスのリスト
198
         * @param fileExtension SQLファイル拡張子
199
         * @param charset SQLファイルエンコーディング
200
         * @param detectChanges SQLファイルの変更を検知するかどうか
201
         *
202
         * @throws IllegalArgumentException loadPathsに<code>null</code>が含まれる場合
203
         */
204
        public SqlResourceManagerImpl(final List<String> loadPaths, final String fileExtension, final Charset charset,
205
                        final boolean detectChanges) {
1✔
206
                this.loadPaths = new ArrayList<>();
1✔
207
                for (var loadPath : loadPaths) {
1✔
208
                        if (loadPath == null) {
1✔
209
                                throw new IllegalArgumentException("loadPath is required.");
1✔
210
                        }
211
                        this.loadPaths.add(Paths.get(loadPath));
1✔
212
                }
1✔
213
                this.loadPathPartsList = new ArrayList<>();
1✔
214
                for (var loadPath : this.loadPaths) {
1✔
215
                        var pathList = new ArrayList<String>();
1✔
216
                        for (var part : loadPath) {
1✔
217
                                pathList.add(part.toString());
1✔
218
                        }
1✔
219
                        this.loadPathPartsList.add(pathList.toArray(new String[pathList.size()]));
1✔
220
                }
1✔
221
                this.fileExtension = fileExtension != null ? fileExtension : ".sql";
1✔
222
                this.charset = charset != null ? charset : Charset.forName(Charset.defaultCharset().displayName());
1✔
223
                this.detectChanges = detectChanges;
1✔
224
        }
1✔
225

226
        /**
227
         * {@inheritDoc}
228
         *
229
         * @see jp.co.future.uroborosql.store.SqlResourceManager#initialize()
230
         */
231
        @Override
232
        public void initialize() {
233
                if (detectChanges) {
1✔
234
                        try {
235
                                watcher = FileSystems.getDefault().newWatchService();
1✔
236
                        } catch (IOException e) {
×
237
                                LOG.error("Can't start watcher service.", e);
×
238
                                return;
×
239
                        }
1✔
240
                }
241

242
                generateSqlInfos();
1✔
243

244
                if (detectChanges) {
1✔
245
                        // Path監視用のスレッド実行
246
                        es = Executors.newSingleThreadExecutor();
1✔
247
                        es.execute(this::watchPath);
1✔
248
                }
249
        }
1✔
250

251
        /**
252
         * {@inheritDoc}
253
         *
254
         * @see jp.co.future.uroborosql.store.SqlResourceManager#shutdown()
255
         */
256
        @Override
257
        public void shutdown() {
258
                if (detectChanges) {
1✔
259
                        es.shutdown();
1✔
260
                        try {
261
                                if (!es.awaitTermination(1000, TimeUnit.MILLISECONDS)) {
1✔
262
                                        es.shutdownNow();
1✔
263
                                }
264
                        } catch (InterruptedException e) {
×
265
                                es.shutdownNow();
×
266
                        }
1✔
267
                }
268
        }
1✔
269

270
        /**
271
         * Pathの監視
272
         */
273
        private void watchPath() {
274
                for (;;) {
275
                        //監視キーの送信を待機
276
                        WatchKey key;
277
                        try {
278
                                key = watcher.take();
1✔
279
                        } catch (InterruptedException ex) {
1✔
280
                                LOG.debug("WatchService catched InterruptedException.");
1✔
281
                                break;
1✔
282
                        } catch (Throwable ex) {
×
283
                                LOG.error("Unexpected exception occurred.", ex);
×
284
                                break;
×
285
                        }
1✔
286

287
                        for (var event : key.pollEvents()) {
1✔
288
                                var kind = event.kind();
1✔
289

290
                                if (kind == OVERFLOW) {
1✔
291
                                        continue;
×
292
                                }
293

294
                                //ファイル名はイベントのコンテキストです。
295
                                @SuppressWarnings("unchecked")
296
                                var evt = (WatchEvent<Path>) event;
1✔
297
                                var dir = watchDirs.get(key);
1✔
298
                                var path = dir.resolve(evt.context());
1✔
299

300
                                LOG.debug("file changed.({}). path={}", kind.name(), path);
1✔
301
                                var isSqlFile = path.toString().endsWith(fileExtension);
1✔
302
                                if (Files.isDirectory(path) || !isSqlFile) {
1✔
303
                                        // ENTRY_DELETEの時はFiles.isDirectory()がfalseになるので拡張子での判定も行う
304
                                        if (kind == ENTRY_CREATE) {
1✔
305
                                                traverseFile(path, true, false);
1✔
306
                                        } else if (kind == ENTRY_DELETE) {
1✔
307
                                                key.cancel();
1✔
308
                                                watchDirs.remove(key);
1✔
309
                                                continue;
1✔
310
                                        }
311
                                } else if (isSqlFile) {
1✔
312
                                        if (kind == ENTRY_CREATE) {
1✔
313
                                                traverseFile(path, true, false);
1✔
314
                                        } else if (kind == ENTRY_MODIFY || kind == ENTRY_DELETE) {
1✔
315
                                                var sqlName = getSqlName(path);
1✔
316
                                                sqlInfos.computeIfPresent(sqlName, (k, v) -> v.computePath(path, kind == ENTRY_DELETE));
1✔
317
                                        }
318
                                }
319
                        }
1✔
320
                        key.reset();
1✔
321
                }
1✔
322
                try {
323
                        watcher.close();
1✔
324
                } catch (IOException e) {
×
325
                        // do nothing
326
                }
1✔
327
        }
1✔
328

329
        /**
330
         * charset を取得します。
331
         *
332
         * @return charset
333
         */
334
        public Charset getCharset() {
335
                return charset;
1✔
336
        }
337

338
        /**
339
         * {@inheritDoc}
340
         *
341
         * @see jp.co.future.uroborosql.store.SqlResourceManager#getDialect()
342
         */
343
        @Override
344
        public Dialect getDialect() {
345
                return dialect;
1✔
346
        }
347

348
        /**
349
         * {@inheritDoc}
350
         *
351
         * @see jp.co.future.uroborosql.store.SqlResourceManager#setDialect(jp.co.future.uroborosql.dialect.Dialect)
352
         */
353
        @Override
354
        public void setDialect(final Dialect dialect) {
355
                this.dialect = dialect;
1✔
356
        }
1✔
357

358
        /**
359
         * {@inheritDoc}
360
         *
361
         * @see jp.co.future.uroborosql.store.SqlResourceManager#getSqlPathList()
362
         */
363
        @Override
364
        public List<String> getSqlPathList() {
365
                var list = Collections.list(this.sqlInfos.keys());
1✔
366
                Collections.sort(list);
1✔
367
                return list;
1✔
368
        }
369

370
        /**
371
         * {@inheritDoc}
372
         *
373
         * @see jp.co.future.uroborosql.store.SqlResourceManager#existSql(java.lang.String)
374
         */
375
        @Override
376
        public boolean existSql(final String sqlName) {
377
                return sqlInfos.containsKey(sqlName);
1✔
378
        }
379

380
        /**
381
         * {@inheritDoc}
382
         *
383
         * @see jp.co.future.uroborosql.store.SqlResourceManager#getSql(java.lang.String)
384
         */
385
        @Override
386
        public String getSql(final String sqlName) {
387
                if (existSql(sqlName)) {
1✔
388
                        return sqlInfos.get(sqlName).getSqlBody();
1✔
389
                } else {
390
                        throw new UroborosqlRuntimeException("sql file not found. sqlName : " + sqlName);
1✔
391
                }
392
        }
393

394
        /**
395
         * sqlNameとそれに対するSqlInfoのMapを生成する
396
         */
397
        private void generateSqlInfos() {
398
                try {
399
                        for (var loadPath : this.loadPaths) {
1✔
400
                                var loadPathSlash = loadPath.toString().replaceAll("\\\\", "/");
1✔
401
                                var root = Thread.currentThread().getContextClassLoader().getResources(loadPathSlash);
1✔
402

403
                                while (root.hasMoreElements()) {
1✔
404
                                        var url = root.nextElement();
1✔
405
                                        var scheme = url.toURI().getScheme();
1✔
406
                                        if (SCHEME_FILE.equalsIgnoreCase(scheme)) {
1✔
407
                                                traverseFile(Paths.get(url.toURI()), detectChanges && true, false);
1✔
408
                                        } else if (SCHEME_JAR.equalsIgnoreCase(scheme)) {
1✔
409
                                                traverseJar(url, loadPathSlash);
1✔
410
                                        } else {
411
                                                LOG.warn("Unsupported scheme. scheme : {}, url : {}", scheme, url);
×
412
                                        }
413
                                }
1✔
414
                        }
1✔
415
                } catch (IOException | URISyntaxException e) {
×
416
                        LOG.error("Can't load sql files.", e);
×
417
                }
1✔
418
        }
1✔
419

420
        /**
421
         *
422
         * {@inheritDoc}
423
         *
424
         * @see jp.co.future.uroborosql.store.SqlResourceManager#getSqlName(java.nio.file.Path)
425
         */
426
        @Override
427
        public String getSqlName(final Path path) {
428
                var builder = new StringBuilder();
1✔
429

430
                var dialectFlag = true;
1✔
431
                for (var part : relativePath(path)) {
1✔
432
                        var s = part.toString();
1✔
433
                        if (dialectFlag) {
1✔
434
                                // loadPathの直下がdialectと一致する場合はその下のフォルダから名前を付ける
435
                                dialectFlag = false;
1✔
436
                                if (dialects.contains(s.toLowerCase())) {
1✔
437
                                        continue;
1✔
438
                                }
439
                        }
440
                        builder.append(s).append("/");
1✔
441
                }
1✔
442

443
                return builder.substring(0, builder.length() - (fileExtension.length() + 1));
1✔
444
        }
445

446
        /**
447
         *
448
         * {@inheritDoc}
449
         *
450
         * @see jp.co.future.uroborosql.store.SqlResourceManager#getSqlPath(java.lang.String)
451
         */
452
        @Override
453
        public Path getSqlPath(final String sqlName) {
454
                if (existSql(sqlName)) {
1✔
455
                        return sqlInfos.get(sqlName).getPath();
1✔
456
                } else {
457
                        throw new UroborosqlRuntimeException("sql file not found. sqlName : " + sqlName);
×
458
                }
459
        }
460

461
        /**
462
         * loadPathからの相対パスを取得する.
463
         * loadPathと一致する部分がなかった場合は、引数のpathの値をそのまま返却する
464
         *
465
         * @param path 相対パスを取得する元のパス
466
         * @return 相対パス
467
         */
468
        private Path relativePath(final Path path) {
469
                var pathList = new ArrayList<Path>();
1✔
470
                for (var part : path) {
1✔
471
                        pathList.add(part);
1✔
472
                }
1✔
473

474
                for (var loadPathParts : this.loadPathPartsList) {
1✔
475
                        var loadPathSize = loadPathParts.length;
1✔
476

477
                        // loadPathのフォルダの並びと一致する場所を特定し、その下を相対パスとして返却する
478
                        for (var i = 0; i < pathList.size() - loadPathSize; i++) {
1✔
479
                                var paths = pathList.subList(i, i + loadPathSize).stream()
1✔
480
                                                .map(Path::toString)
1✔
481
                                                .toArray(String[]::new);
1✔
482
                                if (Arrays.equals(loadPathParts, paths)) {
1✔
483
                                        return path.subpath(i + loadPathSize, path.getNameCount());
1✔
484
                                }
485
                        }
486
                }
1✔
487
                // loadPathと一致しなかった場合は元のpathを返却する
488
                return path;
1✔
489
        }
490

491
        /**
492
         * 引数で指定したパスがDialect指定なし、または現在のDialect指定に対応するパスかどうかを判定する
493
         * <pre>
494
         *  Dialect=postgresqlの場合
495
         *
496
         *  - example/test.sql            : true
497
         *  - postgresql/example/test.sql : true
498
         *  - oracle/example/test.sql     : false
499
         *  - example                     : true
500
         *  - postgresql                  : true
501
         *  - oracle                      : false
502
         * </pre>
503
         *
504
         *
505
         * @param path 検査対象のPath
506
         * @return 妥当なPathの場合<code>true</code>
507
         */
508
        private boolean validPath(final Path path) {
509
                var relativePath = relativePath(path);
1✔
510
                if (relativePath.equals(path)) {
1✔
511
                        return true;
1✔
512
                }
513

514
                var d = relativePath.getName(0).toString().toLowerCase();
1✔
515
                // loadPathの直下が現在のdialect以外と一致する場合は無効なパスと判定する
516
                return !dialects.contains(d) || this.dialect.getDatabaseType().equals(d);
1✔
517

518
        }
519

520
        /**
521
         * 指定されたPath配下のファイルを順次追跡し、sqlInfosに格納、または削除を行う。<br>
522
         * また、監視対象指定があり、Pathがディレクトリの場合は、監視サービスに登録する。
523
         *
524
         * @param path 追跡を行うディレクトリ、またはファイルのPath
525
         * @param watch 監視対象指定。<code>true</code>の場合監視対象
526
         * @param remove 削除指定。<code>true</code>の場合、指定のPathを除外する。<code>false</code>の場合は格納する
527
         */
528
        private void traverseFile(final Path path, final boolean watch, final boolean remove) {
529
                LOG.trace("traverseFile start. path : {}, watch : {}, remove : {}.", path, watch, remove);
1✔
530
                if (Files.notExists(path)) {
1✔
531
                        return;
×
532
                }
533
                if (Files.isDirectory(path)) {
1✔
534
                        if (validPath(path)) {
1✔
535
                                try (var ds = Files.newDirectoryStream(path)) {
1✔
536
                                        if (watch) {
1✔
537
                                                var key = path.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
1✔
538
                                                watchDirs.put(key, path);
1✔
539
                                        }
540
                                        for (var child : ds) {
1✔
541
                                                traverseFile(child, watch, remove);
1✔
542
                                        }
1✔
543
                                } catch (IOException ex) {
×
544
                                        throw new UroborosqlRuntimeException("I/O error occurred.", ex);
×
545
                                }
1✔
546
                        }
547
                } else if (path.toString().endsWith(fileExtension)) {
1✔
548
                        var sqlName = getSqlName(path);
1✔
549
                        this.sqlInfos.compute(sqlName, (k, v) -> v == null ? new SqlInfo(sqlName, path, loadPaths, dialect, charset)
1✔
550
                                        : v.computePath(path, remove));
1✔
551
                }
552
        }
1✔
553

554
        /**
555
         * 指定されたjarのURL配下のファイルを順次追跡し、sqlInfosに格納を行う。<br>
556
         *
557
         * @param url 追跡を行うディレクトリ、またはファイルのURL
558
         * @param loadPath SQLファイルをロードするパス
559
         */
560
        @SuppressWarnings("resource")
561
        private void traverseJar(final URL url, final String loadPath) {
562
                LOG.trace("traverseJar start. url : {}, loadPath : {}.", url, loadPath);
1✔
563

564
                FileSystem fs = null;
1✔
565
                try {
566
                        var uri = url.toURI();
1✔
567
                        try {
568
                                fs = FileSystems.getFileSystem(uri);
×
569
                        } catch (FileSystemNotFoundException ex) {
1✔
570
                                fs = FileSystems.newFileSystem(uri, Map.of("create", "false"));
1✔
571
                        }
×
572

573
                        var conn = (JarURLConnection) url.openConnection();
1✔
574
                        try (var jarFile = conn.getJarFile()) {
1✔
575
                                var jarEntries = jarFile.entries();
1✔
576
                                while (jarEntries.hasMoreElements()) {
1✔
577
                                        var jarEntry = jarEntries.nextElement();
1✔
578
                                        var name = jarEntry.getName();
1✔
579
                                        if (!jarEntry.isDirectory() && name.startsWith(loadPath) && name.endsWith(fileExtension)) {
1✔
580
                                                var path = fs.getPath(name);
1✔
581
                                                var sqlName = getSqlName(path);
1✔
582
                                                this.sqlInfos.compute(sqlName, (k, v) -> v == null
1✔
583
                                                                ? new SqlInfo(sqlName, path, loadPaths, dialect, charset)
1✔
584
                                                                : v.computePath(path, false));
1✔
585
                                        }
586
                                }
1✔
587
                        }
588
                } catch (IOException | URISyntaxException ex) {
×
589
                        throw new UroborosqlRuntimeException("I/O error occurred.", ex);
×
590
                } finally {
591
                        if (fs != null) {
1✔
592
                                try {
593
                                        fs.close();
1✔
594
                                } catch (IOException ex) {
×
595
                                        throw new UroborosqlRuntimeException("I/O error occurred.", ex);
×
596
                                }
1✔
597
                        }
598
                }
599
        }
1✔
600

601
        /**
602
         * SQLファイルの情報を保持するオブジェクト
603
         */
604
        public static class SqlInfo {
605
                /** キーとなるsqlName */
606
                private final String sqlName;
607
                /** 対象のDialect */
608
                private final Dialect dialect;
609
                /** Sqlファイルの文字コード */
610
                private final Charset charset;
611
                /** sqlNameに対応するPathのList. ソートされて優先度が高いものから順に並んでいる. 適用されるのは先頭のPathになる. */
612
                private final List<Path> pathList = new ArrayList<>();
1✔
613
                /** SQLファイルをロードするルートパスのリスト */
614
                private final List<Path> loadPaths;
615
                /** SQLファイルの内容. <code>null</code>の場合、getSqlBody()が呼び出された段階でロードして格納する. */
616
                private String sqlBody;
617
                /** 適用されたPathの最終更新日時。SQLファイルが更新されたかどうかの判定に利用する */
618
                private FileTime lastModified;
619

620
                /**
621
                 * コンストラクタ
622
                 * @param sqlName sqlName
623
                 * @param path path
624
                 * @param dialect dialect
625
                 * @param charset charset
626
                 */
627
                SqlInfo(final String sqlName,
628
                                final Path path,
629
                                final List<Path> loadPaths,
630
                                final Dialect dialect,
631
                                final Charset charset) {
1✔
632
                        LOG.trace("SqlInfo - sqlName : {}, path : {}, dialect : {}, charset : {}.",
1✔
633
                                        sqlName, path, dialect, charset);
634
                        this.sqlName = sqlName;
1✔
635
                        this.dialect = dialect;
1✔
636
                        this.charset = charset;
1✔
637
                        this.pathList.add(path);
1✔
638
                        this.loadPaths = loadPaths;
1✔
639
                        this.lastModified = getLastModifiedTime(path);
1✔
640
                        this.sqlBody = null;
1✔
641
                }
1✔
642

643
                /**
644
                 * 指定されたPathの最終更新日時を取得する
645
                 * @param path 対象のPath
646
                 * @return 最終更新日時
647
                 */
648
                private static FileTime getLastModifiedTime(final Path path) {
649
                        if (SCHEME_FILE.equalsIgnoreCase(path.toUri().getScheme())) {
1✔
650
                                try {
651
                                        return Files.getLastModifiedTime(path);
1✔
652
                                } catch (IOException e) {
×
653
                                        LOG.warn("Can't get lastModifiedTime. path:{}", path, e);
×
654
                                }
655
                        }
656
                        return FileTime.fromMillis(0L);
1✔
657
                }
658

659
                /**
660
                 * 指定されたPathがDialect指定のPathかどうかを判定する
661
                 * @param path 判定対象Path
662
                 * @return Dialect指定Pathの場合<code>true</code>
663
                 */
664
                private boolean hasDialect(final Path path) {
665
                        for (var p : path) {
1✔
666
                                if (this.dialect.getDatabaseType().equals(p.toString())) {
1✔
667
                                        return true;
1✔
668
                                }
669
                        }
1✔
670
                        return false;
1✔
671
                }
672

673
                /**
674
                 * sqlName を取得します。
675
                 *
676
                 * @return sqlName
677
                 */
678
                public String getSqlName() {
679
                        return sqlName;
×
680
                }
681

682
                /**
683
                 * 現在有効な path を取得します。
684
                 *
685
                 * @return path 現在有効なPath
686
                 */
687
                public Path getPath() {
688
                        return pathList.get(0);
1✔
689
                }
690

691
                /**
692
                 * sql文字列を取得します。
693
                 * sqlBodyが<code>null</code>の場合、現在有効なPathからファイルの内容をロードし、sqlBodyに格納したうえ返却します。
694
                 *
695
                 * @return sqlBody sql文字列
696
                 */
697
                private String getSqlBody() {
698
                        if (sqlBody == null) {
1✔
699
                                var path = getPath();
1✔
700
                                var scheme = path.toUri().getScheme();
1✔
701

702
                                if (SCHEME_FILE.equalsIgnoreCase(scheme)) {
1✔
703
                                        // ファイルパスの場合
704
                                        if (Files.notExists(path)) {
1✔
705
                                                throw new UroborosqlRuntimeException("SQL template could not found.["
×
706
                                                                + path.toAbsolutePath().toString() + "]");
×
707
                                        }
708
                                        synchronized (sqlName) {
1✔
709
                                                try {
710
                                                        var body = new String(Files.readAllBytes(path), charset);
1✔
711
                                                        sqlBody = formatSqlBody(body);
1✔
712
                                                        LOG.debug("Loaded SQL template.[{}]", path);
1✔
713
                                                } catch (IOException e) {
×
714
                                                        throw new UroborosqlRuntimeException("Failed to load SQL template["
×
715
                                                                        + path.toAbsolutePath().toString() + "].", e);
×
716
                                                }
1✔
717
                                        }
1✔
718
                                } else {
719
                                        // jarパスの場合
720
                                        var url = Thread.currentThread().getContextClassLoader().getResource(getResourcePath(path));
1✔
721
                                        if (url == null) {
1✔
722
                                                throw new UroborosqlRuntimeException("SQL template could not found.["
×
723
                                                                + path.toAbsolutePath().toString() + "]");
×
724
                                        }
725
                                        synchronized (sqlName) {
1✔
726
                                                try {
727
                                                        var conn = url.openConnection();
1✔
728
                                                        try (var reader = new BufferedReader(
1✔
729
                                                                        new InputStreamReader(conn.getInputStream(), charset))) {
1✔
730
                                                                var body = reader.lines()
1✔
731
                                                                                .collect(Collectors.joining(System.lineSeparator()));
1✔
732
                                                                sqlBody = formatSqlBody(body);
1✔
733
                                                                LOG.debug("Loaded SQL template.[{}]", path);
1✔
734
                                                        }
735
                                                } catch (IOException e) {
×
736
                                                        throw new UroborosqlRuntimeException("Failed to load SQL template["
×
737
                                                                        + path.toAbsolutePath().toString() + "].", e);
×
738
                                                }
1✔
739
                                        }
1✔
740
                                }
741
                        }
742
                        return sqlBody;
1✔
743
                }
744

745
                /**
746
                 * SQL文の不要な文字削除と末尾の改行文字付与を行う.
747
                 *
748
                 * @param sqlBody 元となるSQL文
749
                 * @return 整形後のSQL文
750
                 */
751
                protected String formatSqlBody(final String sqlBody) {
752
                        var newBody = sqlBody.trim();
1✔
753
                        if (newBody.endsWith("/") && !newBody.endsWith("*/")) {
1✔
754
                                newBody = StringUtils.removeEnd(newBody, "/");
1✔
755
                        } else {
756
                                newBody = newBody + System.lineSeparator();
1✔
757
                        }
758
                        return newBody;
1✔
759
                }
760

761
                /**
762
                 * 同じSqlNameになるPathの優先度判定を行い、優先度が高いPathが指定された場合保持しているPathの置き換えを行う
763
                 *
764
                 * @param newPath 判定用Path
765
                 * @param remove 指定した判定用Pathを削除する場合に<code>true</code>を指定
766
                 *
767
                 * @return 判定後のSqlInfo
768
                 */
769
                private SqlInfo computePath(final Path newPath, final boolean remove) {
770
                        synchronized (sqlName) {
1✔
771
                                // 変更前の有効Pathを保持しておく
772
                                var oldPath = getPath();
1✔
773

774
                                // 引数で渡された判定用PathをpathListへ追加、またはpathListから削除する
775
                                if (!pathList.contains(newPath)) {
1✔
776
                                        if (!remove) {
1✔
777
                                                pathList.add(newPath);
1✔
778
                                        }
779
                                } else {
780
                                        if (remove) {
1✔
781
                                                pathList.remove(newPath);
1✔
782
                                                if (pathList.isEmpty()) {
1✔
783
                                                        // pathListが空になった場合はこのSqlInfoをsqlInfosから除外するためにnullを返す
784
                                                        return null;
1✔
785
                                                }
786
                                        }
787
                                }
788

789
                                if (pathList.size() > 1) {
1✔
790
                                        // 優先度が高いPathが先頭に来るようにソートを行う
791
                                        // 1. Dialect付Pathを優先
792
                                        // 2. file を jar よりも優先
793
                                        // 3. Path同士のcompare
794
                                        pathList.sort((p1, p2) -> {
1✔
795
                                                if (p1 == null && p2 == null) {
1✔
796
                                                        return 0;
×
797
                                                } else if (p1 != null && p2 == null) {
1✔
798
                                                        return -1;
×
799
                                                } else if (p1 == null && p2 != null) {
1✔
800
                                                        return 1;
×
801
                                                }
802

803
                                                // DialectPathの比較
804
                                                var p1HasDialect = hasDialect(p1);
1✔
805
                                                var p2HasDialect = hasDialect(p2);
1✔
806

807
                                                if (p1HasDialect && !p2HasDialect) {
1✔
808
                                                        return -1;
1✔
809
                                                } else if (!p1HasDialect && p2HasDialect) {
1✔
810
                                                        return 1;
1✔
811
                                                }
812

813
                                                // schemeの比較
814
                                                var p1Scheme = p1.toUri().getScheme();
1✔
815
                                                var p2Scheme = p2.toUri().getScheme();
1✔
816

817
                                                if (!p1Scheme.equals(p2Scheme)) {
1✔
818
                                                        if (SCHEME_FILE.equals(p1Scheme)) {
1✔
819
                                                                return -1;
×
820
                                                        } else {
821
                                                                return 1;
1✔
822
                                                        }
823
                                                }
824

825
                                                // LoadPathsの並び順にソート
826
                                                var p1Pos = 0;
1✔
827
                                                var p2Pos = 0;
1✔
828
                                                for (var pos = 0; pos < this.loadPaths.size(); pos++) {
1✔
829
                                                        var loadPath = this.loadPaths.get(pos);
1✔
830
                                                        var loadPathSize = loadPath.getNameCount();
1✔
831

832
                                                        for (var i = 0; i < p1.getNameCount() - loadPathSize; i++) {
1✔
833
                                                                var p1SubPath = p1.subpath(i, i + loadPathSize);
1✔
834
                                                                if (p1SubPath.equals(loadPath)) {
1✔
835
                                                                        p1Pos = pos + 1;
1✔
836
                                                                        break;
1✔
837
                                                                }
838
                                                        }
839
                                                        for (var i = 0; i < p2.getNameCount() - loadPathSize; i++) {
1✔
840
                                                                var p2SubPath = p2.subpath(i, i + loadPathSize);
1✔
841
                                                                if (p2SubPath.equals(loadPath)) {
1✔
842
                                                                        p2Pos = pos + 1;
1✔
843
                                                                        break;
1✔
844
                                                                }
845
                                                        }
846
                                                        if (p1Pos > 0 && p2Pos > 0) {
1✔
847
                                                                break;
1✔
848
                                                        }
849
                                                }
850
                                                if (p1Pos != p2Pos) {
1✔
851
                                                        return p1Pos - p2Pos;
1✔
852
                                                }
853

854
                                                return p1.compareTo(p2);
1✔
855
                                        });
856
                                }
857

858
                                var replaceFlag = false;
1✔
859
                                // ソートによる再計算後の有効Pathを取得する
860
                                var currentPath = getPath();
1✔
861
                                var currentTimeStamp = getLastModifiedTime(currentPath);
1✔
862
                                if (!oldPath.equals(currentPath)) {
1✔
863
                                        replaceFlag = true;
1✔
864
                                        LOG.debug("sql file switched. sqlName={}, oldPath={}, newPath={}, lastModified={}", sqlName,
1✔
865
                                                        oldPath, currentPath, currentTimeStamp.toString());
1✔
866
                                } else {
867
                                        if (!this.lastModified.equals(currentTimeStamp)) {
1✔
868
                                                replaceFlag = true;
×
869
                                                LOG.debug("sql file changed. sqlName={}, path={}, lastModified={}", sqlName, currentPath,
×
870
                                                                currentTimeStamp.toString());
×
871
                                        }
872
                                }
873

874
                                if (replaceFlag) {
1✔
875
                                        this.lastModified = currentTimeStamp;
1✔
876
                                        this.sqlBody = null;
1✔
877
                                }
878
                                return this;
1✔
879
                        }
880
                }
881

882
                /**
883
                 * クラスローダーから取得する際のリソースパス(loadPathで始まるパス)を取得する.<br>
884
                 * loadPathと一致する部分がなかった場合は、引数のpathの値をそのまま返却する
885
                 *
886
                 * @param path 計算対象のパス
887
                 * @return クラスローダーから取得する際のリソースパス
888
                 */
889
                private String getResourcePath(final Path path) {
890
                        var pathSize = path.getNameCount();
1✔
891

892
                        for (var loadPath : this.loadPaths) {
1✔
893
                                var loadPathSize = loadPath.getNameCount();
1✔
894

895
                                for (var i = 0; i < pathSize - loadPathSize; i++) {
1✔
896
                                        var subPath = path.subpath(i, i + loadPathSize);
1✔
897
                                        if (loadPath.equals(subPath)) {
1✔
898
                                                return path.subpath(i, pathSize).toString();
×
899
                                        }
900
                                }
901
                        }
1✔
902
                        return path.toString(); // loadPathと一致しなかった場合はパスをそのまま返却する。
1✔
903
                }
904
        }
905
}
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