View Javadoc
1   /*
2    *    Copyright 2010-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.migration;
17  
18  import java.io.Reader;
19  import java.io.StringReader;
20  import java.lang.reflect.Modifier;
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Set;
24  
25  import org.apache.ibatis.migration.io.ResolverUtil;
26  
27  public class JavaMigrationLoader implements MigrationLoader {
28  
29    private String[] packageNames;
30  
31    private ClassLoader classLoader;
32  
33    public JavaMigrationLoader(String... packageNames) {
34      this(null, packageNames);
35    }
36  
37    public JavaMigrationLoader(ClassLoader classLoader, String... packageNames) {
38      this.classLoader = classLoader;
39      this.packageNames = packageNames;
40    }
41  
42    @Override
43    public List<Change> getMigrations() {
44      List<Change> migrations = new ArrayList<>();
45      ResolverUtil<MigrationScript> resolver = getResolver();
46      resolver.findImplementations(MigrationScript.class, packageNames);
47      Set<Class<? extends MigrationScript>> classes = resolver.getClasses();
48      for (Class<? extends MigrationScript> clazz : classes) {
49        try {
50          if (!Modifier.isAbstract(clazz.getModifiers())) {
51            MigrationScript script = clazz.getDeclaredConstructor().newInstance();
52            Change change = parseChangeFromMigrationScript(script);
53            migrations.add(change);
54          }
55        } catch (Exception e) {
56          throw new MigrationException("Could not instanciate MigrationScript: " + clazz.getName(), e);
57        }
58      }
59      return migrations;
60    }
61  
62    private Change parseChangeFromMigrationScript(MigrationScript script) {
63      Change change = new Change();
64      change.setId(script.getId());
65      change.setDescription(script.getDescription());
66      change.setFilename(script.getClass().getName());
67      return change;
68    }
69  
70    @Override
71    public Reader getScriptReader(Change change, boolean undo) {
72      ResolverUtil<MigrationScript> resolver = getResolver();
73      final String className = change.getFilename();
74      for (String pkg : packageNames) {
75        resolver.find(
76            type -> type != null && MigrationScript.class.isAssignableFrom(type) && type.getName().equals(className),
77            pkg);
78      }
79      Set<Class<? extends MigrationScript>> classes = resolver.getClasses();
80      // There should be only one script.
81      for (Class<? extends MigrationScript> clazz : classes) {
82        try {
83          MigrationScript script = clazz.getDeclaredConstructor().newInstance();
84          return new StringReader(undo ? script.getDownScript() : script.getUpScript());
85        } catch (Exception e) {
86          throw new MigrationException("Could not instanciate MigrationScript: " + clazz.getName(), e);
87        }
88      }
89      return null;
90    }
91  
92    @Override
93    public Reader getBootstrapReader() {
94      return getSoleScriptReader(BootstrapScript.class);
95    }
96  
97    @Override
98    public Reader getOnAbortReader() {
99      return getSoleScriptReader(OnAbortScript.class);
100   }
101 
102   public <T extends SimpleScript> Reader getSoleScriptReader(Class<T> scriptClass) {
103     ResolverUtil<T> resolver = getResolver();
104     resolver.findImplementations(scriptClass, packageNames);
105     Set<Class<? extends T>> classes = resolver.getClasses();
106     if (classes == null || classes.isEmpty()) {
107       return null;
108     }
109     if (classes.size() > 1) {
110       throw new MigrationException("There can be only one implementation of " + scriptClass.getName());
111     }
112     Class<? extends T> clazz = classes.iterator().next();
113     try {
114       T script = clazz.getDeclaredConstructor().newInstance();
115       return new StringReader(script.getScript());
116     } catch (Exception e) {
117       throw new MigrationException("Could not instanciate script class: " + clazz.getName(), e);
118     }
119   }
120 
121   private <T> ResolverUtil<T> getResolver() {
122     ResolverUtil<T> resolver = new ResolverUtil<>();
123     if (classLoader != null) {
124       resolver.setClassLoader(classLoader);
125     }
126     return resolver;
127   }
128 }