├── .gitignore
├── README.md
├── agent
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── io
│ │ └── github
│ │ └── jhipster
│ │ └── loaded
│ │ └── instrument
│ │ ├── JHipsterLoadtimeInstrumentationPlugin.java
│ │ └── package-info.java
│ └── resources
│ └── META-INF
│ └── MANIFEST.MF
├── common
├── pom.xml
└── src
│ └── main
│ └── java
│ └── io
│ └── github
│ └── jhipster
│ └── loaded
│ ├── hibernate
│ ├── JHipsterEntityManagerFactoryWrapper.java
│ ├── JHipsterPersistenceProvider.java
│ └── package-info.java
│ └── patch
│ ├── liquibase
│ ├── JHipsterTableSnapshotGenerator.java
│ ├── JhipsterHibernateSpringDatabase.java
│ └── package-info.java
│ └── package-info.java
├── core
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── io
│ │ └── github
│ │ └── jhipster
│ │ └── loaded
│ │ ├── FileSystemWatcher.java
│ │ ├── JHipsterFileSystemWatcher.java
│ │ ├── JHipsterPluginManagerReloadPlugin.java
│ │ ├── JHipsterReloaderAutoConfiguration.java
│ │ ├── JHipsterReloaderThread.java
│ │ ├── condition
│ │ ├── ConditionalOnSpringLoaded.java
│ │ ├── OnSpringLoadedCondition.java
│ │ └── package-info.java
│ │ ├── listener
│ │ ├── filewatcher
│ │ │ ├── FileWatcherListener.java
│ │ │ ├── NewClassLoaderListener.java
│ │ │ └── package-info.java
│ │ └── package-info.java
│ │ ├── package-info.java
│ │ └── reloader
│ │ ├── JacksonReloader.java
│ │ ├── LiquibaseReloader.java
│ │ ├── Reloader.java
│ │ ├── ReloaderUtils.java
│ │ ├── SpringReloader.java
│ │ ├── liquibase
│ │ └── CustomXMLChangeLogSerializer.java
│ │ ├── listener
│ │ ├── JHipsterHandlerMappingListener.java
│ │ ├── SpringListener.java
│ │ └── package-info.java
│ │ ├── loader
│ │ ├── DefaultSpringLoader.java
│ │ ├── JpaSpringLoader.java
│ │ ├── MongoSpringLoader.java
│ │ ├── SpringLoader.java
│ │ └── package-info.java
│ │ ├── package-info.java
│ │ └── type
│ │ ├── ComponentReloaderType.java
│ │ ├── ControllerReloaderType.java
│ │ ├── EntityReloaderType.java
│ │ ├── ReloaderType.java
│ │ ├── RepositoryReloaderType.java
│ │ ├── RestDtoReloaderType.java
│ │ ├── ServiceReloaderType.java
│ │ └── package-info.java
│ └── resources
│ └── META-INF
│ └── spring.factories
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | agent/*.iml
3 | common/*.iml
4 | reloader/*.iml
5 | target/
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | http://jhipster.github.io/
2 |
3 | Deployment process
4 | ==================
5 | See the online documentation (http://central.sonatype.org/pages/apache-maven.html)
6 |
7 | 1. Performing a Snapshot Deployment
8 | 1. mvn clean deploy
9 |
10 | 1. Performing a Release Deployment
11 | 1. mvn versions:set -DnewVersion=1.2.3
12 | 1. mvn clean deploy -P release
13 |
14 | 1. Releasing the Deployment to the Central Repository
15 | 1. mvn nexus-staging:release -Prelease
16 | 1. mvn versions:set -DnewVersion=1.2.3-SNAPSHOT
17 |
18 |
--------------------------------------------------------------------------------
/agent/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | io.github.jhipster.loaded
5 | jhipster
6 | 0.13-SNAPSHOT
7 |
8 |
9 | agent
10 | jar
11 |
12 |
13 | springloaded-jhipster
14 |
15 |
16 |
17 | maven-compiler-plugin
18 | 3.1
19 |
20 | ${java.version}
21 | ${java.version}
22 |
23 |
24 |
25 | maven-source-plugin
26 | 2.2.1
27 |
28 |
29 | attach-sources
30 | verify
31 |
32 | jar
33 |
34 |
35 |
36 |
37 |
38 | org.apache.maven.plugins
39 | maven-dependency-plugin
40 |
41 |
42 | unpack
43 | generate-resources
44 |
45 | unpack
46 |
47 |
48 |
49 |
50 | org.javassist
51 | javassist
52 | ${javassist.version}
53 | jar
54 | true
55 | ${project.build.directory}/classes
56 |
57 |
58 | io.github.jhipster.loaded
59 | common
60 | ${project.version}
61 | jar
62 | true
63 | ${project.build.directory}/classes
64 |
65 |
66 | org.springframework
67 | springloaded
68 | ${springloaded.version}
69 | jar
70 | true
71 | ${project.build.directory}/classes
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | org.apache.maven.plugins
80 | maven-jar-plugin
81 | 2.4
82 |
83 |
84 | ${project.build.directory}/classes/META-INF/MANIFEST.MF
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/agent/src/main/java/io/github/jhipster/loaded/instrument/JHipsterLoadtimeInstrumentationPlugin.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded.instrument;
2 |
3 | import javassist.*;
4 | import org.springsource.loaded.LoadtimeInstrumentationPlugin;
5 |
6 | import java.security.ProtectionDomain;
7 | import java.util.logging.Level;
8 | import java.util.logging.Logger;
9 |
10 | /**
11 | * Instrument the classes loaded at runtime.
12 | * Be able to change the default behavior of a class before adding it in the ClassLoader
13 | */
14 | public class JHipsterLoadtimeInstrumentationPlugin implements LoadtimeInstrumentationPlugin {
15 |
16 | private final Logger log = Logger.getLogger(JHipsterLoadtimeInstrumentationPlugin.class.getName());
17 |
18 | @Override
19 | public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
20 | return slashedTypeName != null && (slashedTypeName.equals("org/springframework/security/access/method/DelegatingMethodSecurityMetadataSource") ||
21 | slashedTypeName.equals("org/springframework/aop/framework/AdvisedSupport") ||
22 | slashedTypeName.equals("liquibase/ext/hibernate/snapshot/TableSnapshotGenerator") ||
23 | slashedTypeName.equals("org/hibernate/jpa/HibernatePersistenceProvider") ||
24 | slashedTypeName.equals("org/hibernate/engine/internal/CacheHelper") ||
25 | slashedTypeName.equals("org/springframework/data/repository/core/support/TransactionalRepositoryProxyPostProcessor") ||
26 | slashedTypeName.equals("org/springframework/core/LocalVariableTableParameterNameDiscoverer"));
27 | }
28 |
29 | @Override
30 | public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
31 | ClassPool classPool = ClassPool.getDefault();
32 | classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
33 | classPool.appendClassPath(new LoaderClassPath(classLoader));
34 |
35 | try {
36 | // Remove final from a class definition to be able to proxy it. @See JHipsterReloadWebSecurityConfig class
37 | if (slashedClassName.equals("org/springframework/security/access/method/DelegatingMethodSecurityMetadataSource")) {
38 | CtClass ctClass = classPool.get("org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource");
39 | CtMethod ctMethod = ctClass.getDeclaredMethod("getAttributes");
40 | ctMethod.insertBefore("{synchronized (attributeCache) { attributeCache.clear();} }");
41 | bytes = ctClass.toBytecode();
42 | ctClass.defrost();
43 |
44 | log.fine("Patch - Rewrite org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource.getAttributes() method");
45 | }
46 |
47 | // The AdvisedSupport is in charge to manage the advised associated to a method.
48 | // By default, it used a cache which avoid to reload any advises like @RolesAllowed, @Timed etc...
49 | // So if a method has @Timed when the application is started and wants to add a @RolesAllowed,
50 | // the last added annotation is not advised because the cache is used.
51 | // The call to the method adviceChanged will clear the cache
52 | if (slashedClassName.equals("org/springframework/aop/framework/AdvisedSupport")) {
53 | CtClass ctClass = classPool.get("org.springframework.aop.framework.AdvisedSupport");
54 | CtMethod ctMethod = ctClass.getDeclaredMethod("getInterceptorsAndDynamicInterceptionAdvice");
55 | ctMethod.insertBefore("{ adviceChanged(); }");
56 | bytes = ctClass.toBytecode();
57 | ctClass.defrost();
58 |
59 | log.fine("Patch - Rewrite org.springframework.aop.framework.AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice() method");
60 | }
61 |
62 | // Change the super class from TableSnapshotGenerator to JHipsterTableSnapshotGenerator.
63 | // Quick fix for a NPE. @see JHipsterTableSnapshotGenerator
64 | if (slashedClassName.equals("liquibase/ext/hibernate/snapshot/TableSnapshotGenerator")) {
65 | CtClass ctClass = classPool.get("liquibase.ext.hibernate.snapshot.TableSnapshotGenerator");
66 | ctClass.setSuperclass(classPool.get("io.github.jhipster.loaded.patch.liquibase.JHipsterTableSnapshotGenerator"));
67 | CtMethod ctMethod = ctClass.getDeclaredMethod("snapshotObject");
68 | ctMethod.setBody("{ return super.snapshotObject($1, $2);}");
69 | bytes = ctClass.toBytecode();
70 | ctClass.defrost();
71 |
72 | log.fine("Patch - Rewrite liquibase.ext.hibernate.snapshot.TableSnapshotGenerator.snapshotObject() method");
73 | }
74 |
75 | // Add JHipsterPersistenceProvider class as the super class.
76 | // It will wrap the Hibernate entityManagerFactory to be able to reload it.
77 | if (slashedClassName.equals("org/hibernate/jpa/HibernatePersistenceProvider")) {
78 | CtClass ctClass = classPool.get("org.hibernate.jpa.HibernatePersistenceProvider");
79 | ctClass.setSuperclass(classPool.get("io.github.jhipster.loaded.hibernate.JHipsterPersistenceProvider"));
80 | CtMethod ctMethod = ctClass.getDeclaredMethod("createContainerEntityManagerFactory");
81 | ctMethod.setBody("{ return super.createContainerEntityManagerFactory($1, $2); }");
82 | bytes = ctClass.toBytecode();
83 | ctClass.defrost();
84 |
85 | log.fine("Patch - Rewrite org.hibernate.jpa.HibernatePersistenceProvider.createContainerEntityManagerFactory() method");
86 | }
87 |
88 |
89 | // JHipster used second level caching so by default every entity is cached.
90 | // The second level caching is managed by the class @see org.hibernate.engine.internal.CacheHelper
91 | // So when the second level caching is enabled and if an entity is updated (add or remove or update a new field)
92 | // the cached entity is returned and the code doesn't work.
93 | if (slashedClassName.equals("org/hibernate/engine/internal/CacheHelper")) {
94 | CtClass ctClass = classPool.get("org.hibernate.engine.internal.CacheHelper");
95 | CtClass sessionClass = classPool.get("org.hibernate.engine.spi.SessionImplementor");
96 | CtClass cacheKeyClass = classPool.get("org.hibernate.cache.spi.CacheKey");
97 | CtClass regionAccessStrategyClass = classPool.get("org.hibernate.cache.spi.access.RegionAccessStrategy");
98 | CtMethod ctMethod = ctClass.getDeclaredMethod("fromSharedCache", new CtClass[]{sessionClass, cacheKeyClass, regionAccessStrategyClass});
99 | ctMethod.setBody("{ return null; }");
100 | bytes = ctClass.toBytecode();
101 | ctClass.defrost();
102 |
103 | log.fine("Patch - Rewrite org.hibernate.engine.internal.CacheHelper.fromSharedCache() method");
104 | }
105 |
106 | // Make TransactionalRepositoryProxyPostProcessor public to use by SpringLoader to initialize
107 | // the Jpa repository factory.
108 | if (slashedClassName.equals("org/springframework/data/repository/core/support/TransactionalRepositoryProxyPostProcessor")) {
109 | CtClass ctClass = classPool.get("org.springframework.data.repository.core.support.TransactionalRepositoryProxyPostProcessor");
110 | ctClass.setModifiers(Modifier.PUBLIC);
111 | bytes = ctClass.toBytecode();
112 | ctClass.defrost();
113 |
114 | log.fine("Patch - Make org.springframework.data.repository.core.support.TransactionalRepositoryProxyPostProcessor class PUBLIC");
115 | }
116 |
117 | // The parameters are cached and when a class is reloaded, the map used for the caching is not able to
118 | // return the cached parameters for a class or a method or a constructor.
119 | // So the cache will be clear everytime the getParameterNames method is called
120 | if (slashedClassName.equals("org/springframework/core/LocalVariableTableParameterNameDiscoverer")) {
121 | CtClass ctClass = classPool.get("org.springframework.core.LocalVariableTableParameterNameDiscoverer");
122 | CtMethod ctMethod = ctClass.getDeclaredMethod("getParameterNames", new CtClass[]{classPool.get("java.lang.reflect.Method")});
123 | ctMethod.insertBefore("{ this.parameterNamesCache.clear(); }");
124 | ctMethod = ctClass.getDeclaredMethod("getParameterNames", new CtClass[]{classPool.get("java.lang.reflect.Constructor")});
125 | ctMethod.insertBefore("{ this.parameterNamesCache.clear(); }");
126 | bytes = ctClass.toBytecode();
127 | ctClass.defrost();
128 |
129 | log.fine("Patch - Clear cache org.springframework.core.LocalVariableTableParameterNameDiscoverer");
130 | }
131 | } catch (Exception e) {
132 | log.log(Level.SEVERE, "Failed to modify the DelegatingMethodSecurityMetadataSource class", e);
133 | }
134 | return bytes;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/agent/src/main/java/io/github/jhipster/loaded/instrument/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Hot reloading instrument with Spring Loaded.
3 | */
4 | package io.github.jhipster.loaded.instrument;
5 |
--------------------------------------------------------------------------------
/agent/src/main/resources/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Premain-Class: org.springsource.loaded.agent.SpringLoadedAgent
2 | Agent-Class: org.springsource.loaded.agent.SpringLoadedAgent
3 |
--------------------------------------------------------------------------------
/common/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | jhipster
5 | io.github.jhipster.loaded
6 | 0.13-SNAPSHOT
7 |
8 | 4.0.0
9 |
10 | common
11 | jar
12 |
13 |
--------------------------------------------------------------------------------
/common/src/main/java/io/github/jhipster/loaded/hibernate/JHipsterEntityManagerFactoryWrapper.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded.hibernate;
2 |
3 | import org.hibernate.jpa.boot.spi.Bootstrap;
4 | import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
5 |
6 | import javax.persistence.*;
7 | import javax.persistence.criteria.CriteriaBuilder;
8 | import javax.persistence.metamodel.Metamodel;
9 | import javax.persistence.spi.PersistenceUnitInfo;
10 | import java.util.Collection;
11 | import java.util.Map;
12 |
13 | /**
14 | * Wrapper around the Hibernate EntityManagerFactory. This will be used to reload the entity manager
15 | * when an Entity is reloaded.
16 | */
17 | public class JHipsterEntityManagerFactoryWrapper implements EntityManagerFactory {
18 |
19 | private PersistenceUnitInfo info;
20 | private Map properties;
21 | private EntityManagerFactory entityManagerFactory;
22 | private static JHipsterEntityManagerFactoryWrapper instance;
23 |
24 | public JHipsterEntityManagerFactoryWrapper(PersistenceUnitInfo info, Map properties) {
25 | this.info = info;
26 | this.properties = properties;
27 | instance = this;
28 | build(null);
29 | }
30 |
31 | /**
32 | * Reload the Entity manager factory
33 | * @param entities list of entities to load
34 | */
35 | public static void reload(Collection entities) {
36 | instance.build(entities);
37 | }
38 |
39 | private void build(Collection entities) {
40 | // Add new entities if not exists
41 | if (entities != null) {
42 | MutablePersistenceUnitInfo mutablePersistenceUnitInfo = (MutablePersistenceUnitInfo) info;
43 | for (Class entity : entities) {
44 | mutablePersistenceUnitInfo.addManagedClassName(entity.getName());
45 | }
46 | }
47 | entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder(info, properties).build();
48 | }
49 |
50 | public EntityManager createEntityManager() {
51 | return entityManagerFactory.createEntityManager();
52 | }
53 |
54 | public EntityManager createEntityManager(Map map) {
55 | return entityManagerFactory.createEntityManager(map);
56 | }
57 |
58 | public EntityManager createEntityManager(SynchronizationType synchronizationType) {
59 | return entityManagerFactory.createEntityManager(synchronizationType);
60 | }
61 |
62 | public EntityManager createEntityManager(SynchronizationType synchronizationType, Map map) {
63 | return entityManagerFactory.createEntityManager(synchronizationType, map);
64 | }
65 |
66 | public CriteriaBuilder getCriteriaBuilder() {
67 | return entityManagerFactory.getCriteriaBuilder();
68 | }
69 |
70 | public Metamodel getMetamodel() {
71 | return entityManagerFactory.getMetamodel();
72 | }
73 |
74 | public boolean isOpen() {
75 | return entityManagerFactory.isOpen();
76 | }
77 |
78 | public void close() {
79 | entityManagerFactory.close();
80 | }
81 |
82 | public Map getProperties() {
83 | return entityManagerFactory.getProperties();
84 | }
85 |
86 | public Cache getCache() {
87 | return entityManagerFactory.getCache();
88 | }
89 |
90 | public PersistenceUnitUtil getPersistenceUnitUtil() {
91 | return entityManagerFactory.getPersistenceUnitUtil();
92 | }
93 |
94 | public void addNamedQuery(String name, Query query) {
95 | entityManagerFactory.addNamedQuery(name, query);
96 | }
97 |
98 | public T unwrap(Class cls) {
99 | return entityManagerFactory.unwrap(cls);
100 | }
101 |
102 | public void addNamedEntityGraph(String graphName, EntityGraph entityGraph) {
103 | entityManagerFactory.addNamedEntityGraph(graphName, entityGraph);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/common/src/main/java/io/github/jhipster/loaded/hibernate/JHipsterPersistenceProvider.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded.hibernate;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import javax.persistence.EntityManagerFactory;
7 | import javax.persistence.spi.PersistenceProvider;
8 | import javax.persistence.spi.PersistenceUnitInfo;
9 | import java.util.Map;
10 |
11 | /**
12 | * Used to instrument the HibernatePersistenceProvider class
13 | * @see io.github.jhipster.loaded.config.reload.instrument.JHipsterLoadtimeInstrumentationPlugin
14 | */
15 | public abstract class JHipsterPersistenceProvider implements PersistenceProvider {
16 |
17 | private Logger log = LoggerFactory.getLogger(JHipsterPersistenceProvider.class);
18 |
19 | public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
20 | log.trace( "Starting createContainerEntityManagerFactory : {}", info.getPersistenceUnitName() );
21 |
22 | return new JHipsterEntityManagerFactoryWrapper(info, properties);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/common/src/main/java/io/github/jhipster/loaded/hibernate/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Hot reloading Hibernate instrumentation with Spring Loaded.
3 | */
4 | package io.github.jhipster.loaded.hibernate;
5 |
--------------------------------------------------------------------------------
/common/src/main/java/io/github/jhipster/loaded/patch/liquibase/JHipsterTableSnapshotGenerator.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded.patch.liquibase;
2 |
3 | import liquibase.exception.DatabaseException;
4 | import liquibase.ext.hibernate.database.HibernateDatabase;
5 | import liquibase.ext.hibernate.snapshot.HibernateSnapshotGenerator;
6 | import liquibase.snapshot.DatabaseSnapshot;
7 | import liquibase.snapshot.InvalidExampleException;
8 | import liquibase.structure.DatabaseObject;
9 | import liquibase.structure.core.*;
10 | import liquibase.util.StringUtils;
11 | import org.hibernate.cfg.Configuration;
12 | import org.hibernate.dialect.Dialect;
13 | import org.hibernate.engine.spi.Mapping;
14 | import org.hibernate.id.IdentityGenerator;
15 | import org.hibernate.mapping.SimpleValue;
16 |
17 | import java.util.Iterator;
18 | import java.util.regex.Matcher;
19 | import java.util.regex.Pattern;
20 |
21 | /**
22 | * This patch will fix a NPE when the primaryKey is set.
23 | * https://github.com/liquibase/liquibase-hibernate/
24 | *
25 | * Wait until the version 3.4 will be released
26 | */
27 |
28 | public class JHipsterTableSnapshotGenerator extends HibernateSnapshotGenerator {
29 |
30 | private final static Pattern pattern = Pattern.compile("([^\\(]*)\\s*\\(?\\s*(\\d*)?\\s*,?\\s*(\\d*)?\\s*([^\\(]*?)\\)?");
31 |
32 | protected JHipsterTableSnapshotGenerator(Class extends DatabaseObject> defaultFor, Class extends DatabaseObject>[] addsTo) {
33 | super(defaultFor, addsTo);
34 | }
35 |
36 | @Override
37 | protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException {
38 | HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase();
39 | Configuration cfg = database.getConfiguration();
40 |
41 | Dialect dialect = database.getDialect();
42 | Mapping mapping = cfg.buildMapping();
43 |
44 | org.hibernate.mapping.Table hibernateTable = findHibernateTable(example, snapshot);
45 | if (hibernateTable == null) {
46 | return example;
47 | }
48 |
49 | Table table = new Table().setName(hibernateTable.getName());
50 | PrimaryKey primaryKey = null;
51 | int pkColumnPosition = 0;
52 | LOG.info("Found table " + table.getName());
53 |
54 | table.setSchema(example.getSchema());
55 |
56 | Iterator columnIterator = hibernateTable.getColumnIterator();
57 | while (columnIterator.hasNext()) {
58 | org.hibernate.mapping.Column hibernateColumn = (org.hibernate.mapping.Column) columnIterator.next();
59 | Column column = new Column();
60 | column.setName(hibernateColumn.getName());
61 |
62 | String hibernateType = hibernateColumn.getSqlType(dialect, mapping);
63 | DataType dataType = toDataType(hibernateType, hibernateColumn.getSqlTypeCode());
64 | if (dataType == null) {
65 | throw new DatabaseException("Unable to find column data type for column " + hibernateColumn.getName());
66 | }
67 |
68 | column.setType(dataType);
69 | LOG.info("Found column " + column.getName() + " " + column.getType().toString());
70 |
71 | column.setRemarks(hibernateColumn.getComment());
72 | column.setDefaultValue(hibernateColumn.getDefaultValue());
73 | column.setNullable(hibernateColumn.isNullable());
74 | column.setCertainDataType(false);
75 |
76 | org.hibernate.mapping.PrimaryKey hibernatePrimaryKey = hibernateTable.getPrimaryKey();
77 | if (hibernatePrimaryKey != null) {
78 | boolean isPrimaryKeyColumn = false;
79 | for (org.hibernate.mapping.Column pkColumn : (java.util.List) hibernatePrimaryKey.getColumns()) {
80 | if (pkColumn.getName().equals(hibernateColumn.getName())) {
81 | isPrimaryKeyColumn = true;
82 | break;
83 | }
84 | }
85 |
86 | if (isPrimaryKeyColumn) {
87 | if (primaryKey == null) {
88 | primaryKey = new PrimaryKey();
89 | primaryKey.setName(hibernatePrimaryKey.getName());
90 | }
91 | primaryKey.addColumnName(pkColumnPosition++, column.getName());
92 |
93 | String identifierGeneratorStrategy = hibernateColumn.getValue().isSimpleValue() ?
94 | ((SimpleValue) hibernateColumn.getValue()).getIdentifierGeneratorStrategy() : null;
95 | if (("native".equals(identifierGeneratorStrategy) || "identity".equals(identifierGeneratorStrategy)) &&
96 | dialect.getNativeIdentifierGeneratorClass().equals(IdentityGenerator.class)) {
97 | column.setAutoIncrementInformation(new Column.AutoIncrementInformation());
98 | }
99 | }
100 | }
101 | column.setRelation(table);
102 |
103 | table.setPrimaryKey(primaryKey);
104 | table.getColumns().add(column);
105 |
106 | }
107 |
108 | return table;
109 | }
110 |
111 | protected DataType toDataType(String hibernateType, Integer sqlTypeCode) throws DatabaseException {
112 | Matcher matcher = pattern.matcher(hibernateType);
113 | if (!matcher.matches()) {
114 | return null;
115 | }
116 | DataType dataType = new DataType(matcher.group(1));
117 | if (matcher.group(3).isEmpty()) {
118 | if (!matcher.group(2).isEmpty())
119 | dataType.setColumnSize(Integer.parseInt(matcher.group(2)));
120 | } else {
121 | dataType.setColumnSize(Integer.parseInt(matcher.group(2)));
122 | dataType.setDecimalDigits(Integer.parseInt(matcher.group(3)));
123 | }
124 |
125 | String extra = StringUtils.trimToNull(matcher.group(4));
126 | if (extra != null) {
127 | if (extra.equalsIgnoreCase("char")) {
128 | dataType.setColumnSizeUnit(DataType.ColumnSizeUnit.CHAR);
129 | }
130 | }
131 |
132 | dataType.setDataTypeId(sqlTypeCode);
133 | return dataType;
134 | }
135 |
136 | @Override
137 | protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException {
138 | if (!snapshot.getSnapshotControl().shouldInclude(Table.class)) {
139 | return;
140 | }
141 |
142 | if (foundObject instanceof Schema) {
143 |
144 | Schema schema = (Schema) foundObject;
145 | HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase();
146 | Configuration cfg = database.getConfiguration();
147 |
148 | Iterator tableMappings = cfg.getTableMappings();
149 | while (tableMappings.hasNext()) {
150 | org.hibernate.mapping.Table hibernateTable = tableMappings.next();
151 | if (hibernateTable.isPhysicalTable()) {
152 | Table table = new Table().setName(hibernateTable.getName());
153 | table.setSchema(schema);
154 | LOG.info("Found table " + table.getName());
155 | schema.addDatabaseObject(table);
156 | }
157 | }
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/common/src/main/java/io/github/jhipster/loaded/patch/liquibase/JhipsterHibernateSpringDatabase.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded.patch.liquibase;
2 |
3 | import liquibase.ext.hibernate.database.HibernateSpringDatabase;
4 | import liquibase.ext.hibernate.database.connection.HibernateConnection;
5 | import org.hibernate.cfg.Configuration;
6 | import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
7 | import org.hibernate.jpa.boot.spi.Bootstrap;
8 | import org.hibernate.service.ServiceRegistry;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
12 | import org.springframework.orm.jpa.persistenceunit.SmartPersistenceUnitInfo;
13 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
14 |
15 | import javax.persistence.spi.PersistenceUnitInfo;
16 | import java.util.Map;
17 |
18 | /**
19 | * Support for Spring 4.0.
20 | *
21 | * Actually we are not able to use the version 3.4 because liquibase version 3.2.0
22 | * doesn't work with SpringBoot
23 | */
24 | public class JhipsterHibernateSpringDatabase extends HibernateSpringDatabase {
25 |
26 | private Logger log = LoggerFactory.getLogger(JhipsterHibernateSpringDatabase.class);
27 |
28 | public JhipsterHibernateSpringDatabase(String catalog, String schema) {
29 | setDefaultCatalogName(catalog);
30 | setDefaultSchemaName(schema);
31 | }
32 |
33 | @Override
34 | public Configuration buildConfigurationFromScanning(HibernateConnection connection) {
35 | String[] packagesToScan = connection.getPath().split(",");
36 |
37 | for (String packageName : packagesToScan) {
38 | log.info("Found package {}", packageName);
39 | }
40 |
41 | DefaultPersistenceUnitManager internalPersistenceUnitManager = new DefaultPersistenceUnitManager();
42 |
43 | internalPersistenceUnitManager.setPackagesToScan(packagesToScan);
44 |
45 | String dialectName = connection.getProperties().getProperty("dialect", null);
46 | if (dialectName == null) {
47 | throw new IllegalArgumentException("A 'dialect' has to be specified.");
48 | }
49 | log.info("Found dialect {}", dialectName);
50 |
51 | internalPersistenceUnitManager.preparePersistenceUnitInfos();
52 | PersistenceUnitInfo persistenceUnitInfo = internalPersistenceUnitManager.obtainDefaultPersistenceUnitInfo();
53 | HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
54 | jpaVendorAdapter.setDatabasePlatform(dialectName);
55 |
56 | Map jpaPropertyMap = jpaVendorAdapter.getJpaPropertyMap();
57 | jpaPropertyMap.put("hibernate.archive.autodetection", "false");
58 |
59 | if (persistenceUnitInfo instanceof SmartPersistenceUnitInfo) {
60 | ((SmartPersistenceUnitInfo) persistenceUnitInfo).setPersistenceProviderPackageName(jpaVendorAdapter.getPersistenceProviderRootPackage());
61 | }
62 |
63 | EntityManagerFactoryBuilderImpl builder = (EntityManagerFactoryBuilderImpl) Bootstrap.getEntityManagerFactoryBuilder(persistenceUnitInfo,
64 | jpaPropertyMap);
65 |
66 | ServiceRegistry serviceRegistry = builder.buildServiceRegistry();
67 | return builder.buildHibernateConfiguration(serviceRegistry);
68 | }
69 |
70 | @Override
71 | public boolean supportsCatalogs() {
72 | return true;
73 | }
74 |
75 | @Override
76 | public boolean supportsSchemas() {
77 | return true;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/common/src/main/java/io/github/jhipster/loaded/patch/liquibase/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Hot reloading patch Liquibase with Spring Loaded.
3 | */
4 | package io.github.jhipster.loaded.patch.liquibase;
5 |
--------------------------------------------------------------------------------
/common/src/main/java/io/github/jhipster/loaded/patch/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Hot reloading patch with Spring Loaded.
3 | */
4 | package io.github.jhipster.loaded.patch;
5 |
--------------------------------------------------------------------------------
/core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | io.github.jhipster.loaded
5 | jhipster
6 | 0.13-SNAPSHOT
7 |
8 |
9 | core
10 | jar
11 |
12 |
13 |
14 | io.github.jhipster.loaded
15 | common
16 | ${project.version}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | maven-compiler-plugin
25 | 3.1
26 |
27 | ${java.version}
28 | ${java.version}
29 |
30 |
31 |
32 | maven-source-plugin
33 | 2.2.1
34 |
35 |
36 | attach-sources
37 | verify
38 |
39 | jar
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/FileSystemWatcher.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded;
2 |
3 | import org.springframework.context.ConfigurableApplicationContext;
4 |
5 | import java.nio.file.Path;
6 | import java.util.List;
7 |
8 | public interface FileSystemWatcher {
9 |
10 | ClassLoader getClassLoader();
11 |
12 | ConfigurableApplicationContext getConfigurableApplicationContext();
13 |
14 | List getWatchFolders();
15 |
16 | /**
17 | * Register the given directory, and all its sub-directories, with the
18 | * WatchService.
19 | */
20 | void watchDirectory(final Path start);
21 | }
22 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/JHipsterFileSystemWatcher.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded;
2 |
3 | import io.github.jhipster.loaded.listener.filewatcher.FileWatcherListener;
4 | import io.github.jhipster.loaded.listener.filewatcher.NewClassLoaderListener;
5 | import com.sun.nio.file.SensitivityWatchEventModifier;
6 | import org.apache.commons.io.filefilter.SuffixFileFilter;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.context.ConfigurableApplicationContext;
10 | import org.springframework.core.env.Environment;
11 |
12 | import java.io.File;
13 | import java.io.FileFilter;
14 | import java.io.IOException;
15 | import java.nio.file.*;
16 | import java.nio.file.attribute.BasicFileAttributes;
17 | import java.util.ArrayList;
18 | import java.util.HashMap;
19 | import java.util.List;
20 | import java.util.Map;
21 |
22 | import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
23 | import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
24 | import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
25 |
26 | /**
27 | * A watcher for the target class folder.
28 | *
29 | * The watcher will monitor all folders and sub-folders to check if a new class
30 | * is created. If so, the new class will be loaded and managed by Spring-Loaded.
31 | */
32 | public class JHipsterFileSystemWatcher implements FileSystemWatcher, Runnable {
33 |
34 | private static Logger log = LoggerFactory.getLogger(JHipsterFileSystemWatcher.class);
35 |
36 | private static boolean isStarted;
37 | private final WatchService watcher;
38 | private final Map keys = new HashMap<>();
39 | private final List fileWatcherListeners = new ArrayList<>();
40 | private final List watchFolders;
41 | private final ConfigurableApplicationContext ctx;
42 | private final ClassLoader classLoader;
43 |
44 |
45 | public JHipsterFileSystemWatcher(List watchFolders, ConfigurableApplicationContext ctx, ClassLoader classLoader) throws Exception {
46 | this.watchFolders = watchFolders;
47 | this.ctx = ctx;
48 | this.classLoader = classLoader;
49 | watcher = FileSystems.getDefault().newWatchService();
50 |
51 | // Register all folders
52 | for (String watchFolder : watchFolders) {
53 | final Path classesFolderPath = FileSystems.getDefault().getPath(watchFolder);
54 | watchDirectory(classesFolderPath);
55 | }
56 |
57 | registerFileWatcherListeners();
58 |
59 | isStarted = true;
60 | }
61 |
62 | /**
63 | * Register the classLoader and start a thread that will be used to monitor folders where classes can be created.
64 | *
65 | * @param classLoader the classLoader of the application
66 | * @param ctx the spring application context
67 | */
68 | public static void register(ClassLoader classLoader, ConfigurableApplicationContext ctx) {
69 | try {
70 | Environment env = ctx.getEnvironment();
71 |
72 | // Load from env the list of folders to watch
73 | List watchFolders = getWatchFolders(env);
74 |
75 | if (watchFolders.size() == 0) {
76 | log.warn("SpringLoaded - No watched folders have been defined in the application-{profile}.yml. " +
77 | "We will use the default target/classes");
78 | watchFolders.add("target/classes");
79 | }
80 |
81 | final Thread thread = new Thread(new JHipsterFileSystemWatcher(watchFolders, ctx, classLoader));
82 | thread.setDaemon(true);
83 | thread.start();
84 |
85 | Runtime.getRuntime().addShutdownHook(new Thread() {
86 | public void run() {
87 | JHipsterFileSystemWatcher.isStarted = false;
88 | try {
89 | // Interrupt the thread which should interrupt the blocking wait calls
90 | // on the watcher queue.
91 | thread.interrupt();
92 | thread.join();
93 | } catch (InterruptedException e) {
94 | log.error("Failed during the JVM shutdown", e);
95 | }
96 | }
97 | });
98 | } catch (Exception e) {
99 | log.error("Failed to start the watcher. New class will not be loaded.", e);
100 | }
101 | }
102 |
103 | @Override
104 | public ClassLoader getClassLoader() {
105 | return classLoader;
106 | }
107 |
108 | @Override
109 | public ConfigurableApplicationContext getConfigurableApplicationContext() {
110 | return ctx;
111 | }
112 |
113 | @Override
114 | public List getWatchFolders() {
115 | return watchFolders;
116 | }
117 |
118 | /**
119 | * Register the given directory, and all its sub-directories, with the
120 | * WatchService.
121 | */
122 | public void watchDirectory(final Path start) {
123 | // register directory and sub-directories
124 | try {
125 | Files.walkFileTree(start, new SimpleFileVisitor() {
126 | @Override
127 | public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
128 | throws IOException {
129 | register(dir);
130 | return FileVisitResult.CONTINUE;
131 | }
132 | });
133 | } catch (IOException e) {
134 | log.error("Failed to register the directory '{}'", start);
135 | }
136 | }
137 |
138 | /**
139 | * Register the given directory with the WatchService.
140 | */
141 | private void register(Path dir) throws IOException {
142 | WatchKey key = dir.register(watcher, new WatchEvent.Kind[]{ENTRY_CREATE, ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH);
143 | Path prev = keys.get(key);
144 | if (prev == null) {
145 | log.debug("Directory : '{}' will be monitored for changes", dir);
146 | }
147 | keys.put(key, dir);
148 | }
149 |
150 | /**
151 | * Process all events for keys queued to the watcher.
152 | *
153 | * When the event is a ENTRY_CREATE or ENTRY_MODIFY, the folders will be added to the watcher,
154 | * the classes will be loaded by SpringLoaded
155 | */
156 | public void run() {
157 | while (isStarted) {
158 | // wait for key to be signalled
159 | WatchKey key;
160 | try {
161 | key = watcher.take();
162 | } catch (InterruptedException x) {
163 | return;
164 | }
165 |
166 | Path dir = keys.get(key);
167 | if (dir == null) {
168 | continue;
169 | }
170 |
171 | for (WatchEvent> event : key.pollEvents()) {
172 | WatchEvent.Kind kind = event.kind();
173 |
174 | // Context for directory entry event is the file name of entry
175 | // noinspection unchecked
176 | WatchEvent ev = (WatchEvent) event;
177 | Path name = ev.context();
178 | Path child = dir.resolve(name);
179 |
180 | // if directory is created, and watching recursively, then
181 | // register it and its sub-directories
182 | if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
183 | watchDirectory(child);
184 | // load the classes that have been copied
185 | final File[] classes = child.toFile().listFiles((FileFilter) new SuffixFileFilter(".class"));
186 | for (File aFile : classes) {
187 | final String parentFolder = aFile.getParent();
188 | callFileWatcherListerners(parentFolder, aFile.toPath(), kind);
189 | }
190 | } else {
191 | callFileWatcherListerners(dir.toString().replace(File.separator,"/"), child, kind);
192 | }
193 | }
194 |
195 | // reset key and remove from set if directory no longer accessible
196 | boolean valid = key.reset();
197 | if (!valid) {
198 | keys.remove(key);
199 |
200 | // all directories are inaccessible
201 | if (keys.isEmpty()) {
202 | break;
203 | }
204 | }
205 | }
206 | }
207 |
208 | /**
209 | * The definition of the folders to watch must be defined in the application-dev.yml as follow
210 | * hotReload:
211 | * enabled: true
212 | * watchdir:
213 | * - /Users/jhipster/demo-jhipster/target/classes
214 | * - /Users/jhipster/demo-jhipster/target/classes1
215 |
216 | * @param env the environment used to retrieve the list of folders
217 | * @return the list of folders
218 | */
219 | private static List getWatchFolders(Environment env) {
220 | List results = new ArrayList<>();
221 |
222 | int i=0;
223 |
224 | String folder = env.getProperty("hotReload.watchdir[" + i + "]");
225 |
226 | while(folder != null) {
227 | results.add(folder);
228 | i++;
229 | folder = env.getProperty("hotReload.watchdir[" + i + "]");
230 | }
231 |
232 | return results;
233 | }
234 |
235 |
236 | /**
237 | * Register the list of listeners
238 | */
239 | private void registerFileWatcherListeners() {
240 | fileWatcherListeners.add(new NewClassLoaderListener());
241 |
242 | for (FileWatcherListener fileWatcherListener : fileWatcherListeners) {
243 | fileWatcherListener.setFileSystemWatcher(this);
244 | }
245 | }
246 |
247 | /**
248 | * Call all listeners on changed file
249 | */
250 | private void callFileWatcherListerners(String parentFolder, Path child, WatchEvent.Kind kind) {
251 | for (FileWatcherListener fileWatcherListener : fileWatcherListeners) {
252 | if (fileWatcherListener.support(child, kind)) {
253 | fileWatcherListener.onChange(parentFolder, child, kind);
254 | }
255 | }
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/JHipsterPluginManagerReloadPlugin.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded;
2 |
3 | import io.github.jhipster.loaded.reloader.Reloader;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.context.ConfigurableApplicationContext;
7 | import org.springsource.loaded.Plugins;
8 | import org.springsource.loaded.ReloadEventProcessorPlugin;
9 |
10 | import java.util.Collection;
11 |
12 | /**
13 | * Automatically re-configures classes when Spring Loaded triggers a hot reload event.
14 | *
15 | *
16 | * Supported technologies are
17 | *
18 | *
Spring: dependency injection and the post-construct hook are triggered
19 | *
Jackson: the serializer and deserializer caches are invalidated on JPA beans and DTOs
20 | *
21 | *
22 | *
23 | * To have Spring Loaded working, run your Application class with these VM options:
24 | * "-javaagent:spring_loaded/springloaded-jhipster.jar -noverify "
25 | *
26 | */
27 | public class JHipsterPluginManagerReloadPlugin implements ReloadEventProcessorPlugin {
28 |
29 | private final Logger log = LoggerFactory.getLogger(JHipsterPluginManagerReloadPlugin.class);
30 |
31 | private static JHipsterReloaderThread jHipsterReloaderThread;
32 |
33 | private String projectPackageName;
34 |
35 | public JHipsterPluginManagerReloadPlugin(ConfigurableApplicationContext ctx) {
36 | projectPackageName = ctx.getEnvironment().getProperty("hotReload.package.project");
37 | }
38 |
39 | @Override
40 | public boolean shouldRerunStaticInitializer(String typename, Class> aClass, String encodedTimestamp) {
41 | return true;
42 | }
43 |
44 | public void reloadEvent(String typename, Class> clazz, String encodedTimestamp) {
45 | if (!typename.startsWith(projectPackageName)) {
46 | log.trace("This class is not in the application package, nothing to do");
47 | return;
48 | }
49 | if (typename.contains("$$EnhancerBy") || typename.contains("$$FastClassBy")) {
50 | log.trace("This is a CGLIB proxy, nothing to do");
51 | return;
52 | }
53 | jHipsterReloaderThread.reloadEvent(typename, clazz);
54 | }
55 |
56 | public static void register(ConfigurableApplicationContext ctx, Collection reloaders, ClassLoader classLoader) {
57 | jHipsterReloaderThread = new JHipsterReloaderThread(ctx, reloaders);
58 | JHipsterReloaderThread.register(jHipsterReloaderThread);
59 | JHipsterFileSystemWatcher.register(classLoader, ctx);
60 | Plugins.registerGlobalPlugin(new JHipsterPluginManagerReloadPlugin(ctx));
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/JHipsterReloaderAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded;
2 |
3 | import io.github.jhipster.loaded.condition.ConditionalOnSpringLoaded;
4 | import io.github.jhipster.loaded.reloader.Reloader;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.BeansException;
8 | import org.springframework.context.ApplicationContext;
9 | import org.springframework.context.ApplicationContextAware;
10 | import org.springframework.context.ConfigurableApplicationContext;
11 | import org.springframework.context.annotation.ComponentScan;
12 | import org.springframework.context.annotation.Configuration;
13 | import org.springframework.core.annotation.AnnotationAwareOrderComparator;
14 | import org.springframework.core.env.Environment;
15 | import org.springsource.loaded.agent.SpringLoadedAgent;
16 |
17 | import java.util.ArrayList;
18 | import java.util.Collections;
19 | import java.util.List;
20 | import java.util.Map;
21 |
22 | @Configuration
23 | @ConditionalOnSpringLoaded
24 | @ComponentScan("io.github.jhipster")
25 | public class JHipsterReloaderAutoConfiguration implements ApplicationContextAware {
26 |
27 | private final Logger log = LoggerFactory.getLogger(JHipsterReloaderAutoConfiguration.class);
28 |
29 | @Override
30 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
31 | try {
32 | Environment env = applicationContext.getEnvironment();
33 | final ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
34 |
35 | if (env.getProperty("hotReload.enabled", Boolean.class, false)) {
36 | SpringLoadedAgent.getInstrumentation();
37 | log.info("Spring Loaded is running, registering hot reloading features");
38 |
39 | final Map allReloaderClasses = applicationContext.getBeansOfType(Reloader.class);
40 |
41 | for (Reloader reloader : allReloaderClasses.values()) {
42 | reloader.init(configurableApplicationContext);
43 | }
44 |
45 | List orderedReloaders = new ArrayList<>();
46 | orderedReloaders.addAll(allReloaderClasses.values());
47 | Collections.sort(orderedReloaders, new AnnotationAwareOrderComparator());
48 |
49 | JHipsterPluginManagerReloadPlugin.register(configurableApplicationContext, orderedReloaders,
50 | JHipsterReloaderAutoConfiguration.class.getClassLoader());
51 |
52 | }
53 | } catch (UnsupportedOperationException uoe) {
54 | log.info("Spring Loaded is not running, hot reloading is not enabled");
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/JHipsterReloaderThread.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded;
2 |
3 | import io.github.jhipster.loaded.reloader.Reloader;
4 | import io.github.jhipster.loaded.reloader.type.*;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.context.ConfigurableApplicationContext;
8 | import org.springframework.core.annotation.AnnotationUtils;
9 | import org.springframework.data.repository.NoRepositoryBean;
10 | import org.springframework.stereotype.Component;
11 | import org.springframework.stereotype.Controller;
12 | import org.springframework.stereotype.Repository;
13 | import org.springframework.stereotype.Service;
14 | import org.springframework.util.ClassUtils;
15 | import org.springframework.web.bind.annotation.RestController;
16 | import org.springframework.data.mongodb.core.mapping.Document;
17 |
18 | import javax.persistence.Entity;
19 | import java.util.ArrayList;
20 | import java.util.Collection;
21 | import java.util.List;
22 |
23 | /**
24 | * This thread stores classes to reload, to reload them all in one batch.
25 | */
26 | public class JHipsterReloaderThread implements Runnable {
27 |
28 | private static final boolean jpaPresent =
29 | ClassUtils.isPresent("javax.persistence.Entity", JHipsterReloaderThread.class.getClassLoader());
30 |
31 | private static final boolean mongoDBPresent =
32 | ClassUtils.isPresent("com.mongodb.Mongo", JHipsterReloaderThread.class.getClassLoader());
33 |
34 | private static Logger log = LoggerFactory.getLogger(JHipsterReloaderThread.class);
35 |
36 | private static final Object lock = new Object();
37 |
38 | public static boolean isStarted;
39 |
40 | private static boolean hotReloadTriggered = false;
41 |
42 | private static boolean isWaitingForNewClasses = false;
43 |
44 | private String domainPackageName;
45 | private String dtoPackageName;
46 |
47 | /**
48 | * How long does the thread wait until running a new batch.
49 | */
50 | private static final int BATCH_DELAY = 250;
51 |
52 | /**
53 | * The list of reloaders called when a spring class has been compiled
54 | * and needs to be reloaded
55 | */
56 | private Collection reloaders;
57 |
58 | /**
59 | * Stores the Spring controllers reloaded in the batch.
60 | */
61 | private List controllers = new ArrayList<>();
62 |
63 | /**
64 | * Stores the Spring services reloaded in the batch.
65 | */
66 | private List services = new ArrayList<>();
67 |
68 | /**
69 | * Stores the Spring repositories reloaded in the batch.
70 | */
71 | private List repositories = new ArrayList<>();
72 |
73 | /**
74 | * Stores the Spring components reloaded in the batch.
75 | */
76 | private List components = new ArrayList<>();
77 |
78 | /**
79 | * Stores the JPA entities reloaded in the batch.
80 | */
81 | private List entities = new ArrayList<>();
82 |
83 | /**
84 | * Stores the DTOs reloaded in the batch.
85 | */
86 | private List dtos = new ArrayList<>();
87 |
88 | public JHipsterReloaderThread(ConfigurableApplicationContext applicationContext, Collection reloaders) {
89 | this.reloaders = reloaders;
90 | domainPackageName = applicationContext.getEnvironment().getProperty("hotReload.package.domain");
91 | dtoPackageName = applicationContext.getEnvironment().getProperty("hotReload.package.restdto");
92 | isStarted = true;
93 | }
94 |
95 | public void reloadEvent(String typename, Class> clazz) {
96 | synchronized (lock) {
97 | log.trace("Hot reloading - checking if this is a Spring bean: {}", typename);
98 |
99 | boolean startReloading = false;
100 | if (AnnotationUtils.findAnnotation(clazz, Repository.class) != null ||
101 | AnnotationUtils.findAnnotation(clazz, NoRepositoryBean.class) != null ||
102 | ClassUtils.isAssignable(clazz, org.springframework.data.repository.Repository.class)) {
103 | log.trace("{} is a Spring Repository", typename);
104 | repositories.add(clazz);
105 | startReloading = true;
106 | } else if (AnnotationUtils.findAnnotation(clazz, Service.class) != null) {
107 | log.trace("{} is a Spring Service", typename);
108 | services.add(clazz);
109 | startReloading = true;
110 | } else if (AnnotationUtils.findAnnotation(clazz, Controller.class) != null ||
111 | AnnotationUtils.findAnnotation(clazz, RestController.class) != null) {
112 | log.trace("{} is a Spring Controller", typename);
113 | controllers.add(clazz);
114 | startReloading = true;
115 | } else if (AnnotationUtils.findAnnotation(clazz, Component.class) != null) {
116 | log.trace("{} is a Spring Component", typename);
117 | components.add(clazz);
118 | startReloading = true;
119 | } else if (typename.startsWith(domainPackageName)) {
120 | log.trace("{} is in the JPA package, checking if it is an entity", typename);
121 | if (jpaPresent && AnnotationUtils.findAnnotation(clazz, Entity.class) != null) {
122 | log.trace("{} is a JPA Entity", typename);
123 | entities.add(clazz);
124 | startReloading = true;
125 | }
126 | if (mongoDBPresent && AnnotationUtils.findAnnotation(clazz, Document.class) != null) {
127 | log.trace("{} is a MongoDB Entity", typename);
128 | entities.add(clazz);
129 | startReloading = true;
130 | }
131 | } else if (typename.startsWith(dtoPackageName)) {
132 | log.debug("{} is a REST DTO", typename);
133 | dtos.add(clazz);
134 | startReloading = true;
135 | }
136 |
137 | if (startReloading) {
138 | hotReloadTriggered = true;
139 | isWaitingForNewClasses = true;
140 | }
141 | }
142 | }
143 |
144 | public void run() {
145 | while (isStarted) {
146 | try {
147 | Thread.sleep(BATCH_DELAY);
148 | if (hotReloadTriggered) {
149 | if (isWaitingForNewClasses) {
150 | log.info("Batch reload has been triggered, waiting for new classes for {} ms", BATCH_DELAY);
151 | isWaitingForNewClasses = false;
152 | } else {
153 | hotReloadTriggered = batchReload();
154 | }
155 | } else {
156 | log.trace("Waiting for batch reload");
157 | }
158 | } catch (InterruptedException e) {
159 | log.error("JHipsterReloaderThread was awaken", e);
160 | }
161 | }
162 | }
163 |
164 | private boolean batchReload() {
165 | boolean hasBeansToReload = false;
166 | synchronized (lock) {
167 | log.info("Batch reload in progress...");
168 |
169 | for (Reloader reloader : reloaders) {
170 | boolean reload = false;
171 | reloader.prepare();
172 |
173 | // reload entities
174 | if (reloader.supports(EntityReloaderType.class) && !entities.isEmpty()) {
175 | reload = true;
176 | addSpringBeans(reloader, EntityReloaderType.instance, entities);
177 | }
178 | // reload dtos
179 | if (reloader.supports(RestDtoReloaderType.class) && !dtos.isEmpty()) {
180 | reload = true;
181 | addSpringBeans(reloader, RestDtoReloaderType.instance, dtos);
182 | }
183 | // reload repositories
184 | if (reloader.supports(RepositoryReloaderType.class) && !repositories.isEmpty()) {
185 | reload = true;
186 | addSpringBeans(reloader, RepositoryReloaderType.instance, repositories);
187 | }
188 | // reload services
189 | if (reloader.supports(ServiceReloaderType.class) && !services.isEmpty()) {
190 | reload = true;
191 | addSpringBeans(reloader, ServiceReloaderType.instance, services);
192 | }
193 | // reload components
194 | if (reloader.supports(ComponentReloaderType.class) && !components.isEmpty()) {
195 | reload = true;
196 | addSpringBeans(reloader, ComponentReloaderType.instance, components);
197 | }
198 | // reload controllers
199 | if (reloader.supports(ControllerReloaderType.class) && !controllers.isEmpty()) {
200 | reload = true;
201 | addSpringBeans(reloader, ControllerReloaderType.instance, controllers);
202 | }
203 |
204 | // Reload the spring beans
205 | if (reload || reloader.hasBeansToReload()) {
206 | reloader.reload();
207 | }
208 |
209 | if (reloader.hasBeansToReload()) {
210 | hasBeansToReload = true;
211 | }
212 | }
213 |
214 | // clear all lists
215 | entities.clear();
216 | dtos.clear();
217 | repositories.clear();
218 | services.clear();
219 | components.clear();
220 | controllers.clear();
221 | }
222 |
223 | return hasBeansToReload;
224 | }
225 |
226 | private void addSpringBeans(Reloader reloader, ReloaderType type, Collection classes) {
227 | if (classes.size() > 0) {
228 | log.debug("There are {} Spring {} updated, adding them to be reloaded", classes.size(), type.getName());
229 | reloader.addBeansToReload(classes, type.getClass());
230 | }
231 | }
232 |
233 | /**
234 | * Register the thread and starts it.
235 | */
236 | public static void register(JHipsterReloaderThread jHipsterReloaderThread) {
237 | try {
238 | final Thread thread = new Thread(jHipsterReloaderThread);
239 | thread.setDaemon(true);
240 | thread.start();
241 |
242 | Runtime.getRuntime().addShutdownHook(new Thread() {
243 | public void run() {
244 | JHipsterReloaderThread.isStarted = false;
245 | try {
246 | thread.join();
247 | } catch (InterruptedException e) {
248 | log.error("Failed during the JVM shutdown", e);
249 | }
250 | }
251 | });
252 | } catch (Exception e) {
253 | log.error("Failed to start the reloader thread. Classes will not be reloaded correctly.", e);
254 | }
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/condition/ConditionalOnSpringLoaded.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded.condition;
2 |
3 | import org.springframework.context.annotation.Conditional;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | /**
11 | * Configuration annotation for a conditional element that depends on the JVM property springLoaded.
12 | */
13 | @Conditional(OnSpringLoadedCondition.class)
14 | @Retention(RetentionPolicy.RUNTIME)
15 | @Target({ ElementType.TYPE, ElementType.METHOD })
16 | public @interface ConditionalOnSpringLoaded {
17 |
18 | /**
19 | * Expression should return {@code true} if the springLoaded JVM property has been set
20 | * or {@code false} if it has not been set.
21 | */
22 | String value() default "true";
23 | }
24 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/condition/OnSpringLoadedCondition.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded.condition;
2 |
3 | import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
4 | import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
5 | import org.springframework.context.annotation.ConditionContext;
6 | import org.springframework.core.type.AnnotatedTypeMetadata;
7 |
8 | /**
9 | * A Condition that evaluates if the JHipsterLoadtimeInstrumentation plugin has been set.
10 | */
11 | public class OnSpringLoadedCondition extends SpringBootCondition {
12 |
13 | @Override
14 | public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
15 |
16 | if (System.getProperty("springloaded") != null) {
17 | return ConditionOutcome.match();
18 | }
19 |
20 | return ConditionOutcome.noMatch("No JHipsterLoadtimeInstrumentation plugin is set");
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/condition/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Hot reloading condition with Spring Loaded.
3 | */
4 | package io.github.jhipster.loaded.condition;
5 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/listener/filewatcher/FileWatcherListener.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded.listener.filewatcher;
2 |
3 | import io.github.jhipster.loaded.FileSystemWatcher;
4 |
5 | import java.nio.file.Path;
6 | import java.nio.file.WatchEvent;
7 |
8 | /**
9 | * All classes that implement this class will be called when a file changes (create or new).
10 | */
11 | public interface FileWatcherListener {
12 |
13 | void setFileSystemWatcher(FileSystemWatcher fileSystemWatcher);
14 |
15 | boolean support(Path file, WatchEvent.Kind kind);
16 |
17 | void onChange(String parentFolder, Path file, WatchEvent.Kind kind);
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/listener/filewatcher/NewClassLoaderListener.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded.listener.filewatcher;
2 |
3 | import io.github.jhipster.loaded.FileSystemWatcher;
4 | import org.apache.commons.io.FilenameUtils;
5 | import org.apache.commons.lang.StringUtils;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.springsource.loaded.ReloadableType;
9 | import org.springsource.loaded.TypeRegistry;
10 | import org.springsource.loaded.Utils;
11 |
12 | import java.io.File;
13 | import java.net.MalformedURLException;
14 | import java.net.URL;
15 | import java.net.URLClassLoader;
16 | import java.nio.file.Path;
17 | import java.nio.file.StandardWatchEventKinds;
18 | import java.nio.file.WatchEvent;
19 | import java.util.HashMap;
20 | import java.util.Map;
21 |
22 | /**
23 | * Support only new class.
24 | * The class will be loaded from the fileSystem and let Spring Loaded to handle it.
25 | */
26 | public class NewClassLoaderListener implements FileWatcherListener {
27 |
28 | private Logger log = LoggerFactory.getLogger(NewClassLoaderListener.class);
29 |
30 | private ClassLoader parentClassLoader;
31 | private Map urlClassLoaderMap = new HashMap<>();
32 |
33 |
34 | @Override
35 | public void setFileSystemWatcher(FileSystemWatcher fileSystemWatcher) {
36 | parentClassLoader = fileSystemWatcher.getClassLoader();
37 |
38 | for (String watchFolder : fileSystemWatcher.getWatchFolders()) {
39 | try {
40 | URLClassLoader urlClassLoader = new URLClassLoader(new URL[] {new File(watchFolder).toURI().toURL()}, parentClassLoader);
41 | urlClassLoaderMap.put(watchFolder, urlClassLoader);
42 | } catch (MalformedURLException e) {
43 | log.error("Failed to register the URL classLoader for the folder '{}'", watchFolder);
44 | }
45 | }
46 | }
47 |
48 | @Override
49 | public boolean support(Path file, WatchEvent.Kind kind) {
50 | return kind == StandardWatchEventKinds.ENTRY_CREATE &&
51 | StringUtils.equals(FilenameUtils.getExtension(file.toFile().getName()), "class");
52 | }
53 |
54 | @Override
55 | public void onChange(String parentFolder, Path file, WatchEvent.Kind kind) {
56 | loadClassFromPath(parentFolder, file.toFile().getName(), file.toFile());
57 | }
58 |
59 | private void loadClassFromPath(String parentDir, String fileName, File theFile) {
60 |
61 | log.debug("JHipster reload - Start to reload the new class '{}'", theFile.getAbsolutePath());
62 | // A class has been added, so it needs to be added to the classloader
63 | try {
64 | Map.Entry urlLoaderEntry = selectUrlLoaderEntry(parentDir);
65 |
66 | if (urlLoaderEntry == null) {
67 | log.error("Failed to find a watched folder for the directory: {}", parentDir);
68 | return;
69 | }
70 | final String classesFolder = urlLoaderEntry.getKey();
71 | final URLClassLoader urlClassLoader = urlLoaderEntry.getValue();
72 | // Try to load the new class
73 | // First we need to remove the global classesFolder from the child path
74 | String slashedClassPath = StringUtils.substringAfter(parentDir, classesFolder);
75 | if (slashedClassPath.startsWith("/")) {
76 | slashedClassPath = slashedClassPath.substring(1);
77 | }
78 | // Replace / by . to create the dottedClassName
79 | String dottedClassPath = slashedClassPath.replace("/", ".");
80 |
81 | String slashedClassName = slashedClassPath + "/" + StringUtils.substringBefore(fileName, ".");
82 | String dottedClassName = dottedClassPath + "." + StringUtils.substringBefore(fileName, ".");
83 |
84 | // Retrieve the Spring Loaded registry.
85 | // We will use to validate the class has not been already loaded
86 | TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(parentClassLoader);
87 |
88 | // Load the class
89 | urlClassLoader.loadClass(dottedClassName);
90 |
91 | // Force SpringLoaded to instrument the class
92 | if (typeRegistry != null) {
93 | String versionstamp = Utils.encode(theFile.lastModified());
94 | ReloadableType rtype = typeRegistry.getReloadableType(slashedClassName);
95 | typeRegistry.fireReloadEvent(rtype, versionstamp);
96 | }
97 | } catch (Exception e) {
98 | log.error("Failed to load the class named: {}", fileName, e);
99 | }
100 | }
101 |
102 | private Map.Entry selectUrlLoaderEntry(String dir) {
103 | for (Map.Entry urlClassLoaderEntry : urlClassLoaderMap.entrySet()) {
104 | final String key = urlClassLoaderEntry.getKey();
105 | if (StringUtils.contains(dir, key)) {
106 | return urlClassLoaderEntry;
107 | }
108 | }
109 | return null;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/listener/filewatcher/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Hot reloading listener with File watcher.
3 | */
4 | package io.github.jhipster.loaded.listener.filewatcher;
5 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/listener/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Hot reloading listener with Spring Loaded.
3 | */
4 | package io.github.jhipster.loaded.listener;
5 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Hot reloading with Spring Loaded.
3 | */
4 | package io.github.jhipster.loaded;
5 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/reloader/JacksonReloader.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded.reloader;
2 |
3 | import com.fasterxml.jackson.databind.DeserializationContext;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.fasterxml.jackson.databind.SerializerProvider;
6 | import com.fasterxml.jackson.databind.deser.DeserializerCache;
7 | import com.fasterxml.jackson.databind.ser.SerializerCache;
8 | import io.github.jhipster.loaded.reloader.type.EntityReloaderType;
9 | import io.github.jhipster.loaded.reloader.type.ReloaderType;
10 | import io.github.jhipster.loaded.reloader.type.RestDtoReloaderType;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 | import org.springframework.beans.factory.BeanFactoryUtils;
14 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
15 | import org.springframework.context.ConfigurableApplicationContext;
16 | import org.springframework.core.annotation.Order;
17 | import org.springframework.stereotype.Component;
18 | import org.springframework.util.ReflectionUtils;
19 |
20 | import java.lang.reflect.Field;
21 | import java.lang.reflect.Method;
22 | import java.util.Collection;
23 | import java.util.concurrent.ConcurrentHashMap;
24 |
25 | /**
26 | * Reloads Jackson classes.
27 | */
28 | @Component
29 | @Order(80)
30 | public class JacksonReloader implements Reloader {
31 |
32 | private final Logger log = LoggerFactory.getLogger(JacksonReloader.class);
33 |
34 | private ConfigurableApplicationContext applicationContext;
35 |
36 | @Override
37 | public void init(ConfigurableApplicationContext applicationContext) {
38 | log.debug("Hot reloading Jackson enabled");
39 | this.applicationContext = applicationContext;
40 | }
41 |
42 | @Override
43 | public boolean supports(Class extends ReloaderType> reloaderType) {
44 | return reloaderType.equals(EntityReloaderType.class) || reloaderType.equals(RestDtoReloaderType.class);
45 | }
46 |
47 | @Override
48 | public void prepare() {}
49 |
50 | @Override
51 | public boolean hasBeansToReload() {
52 | return false;
53 | }
54 |
55 | @Override
56 | public void addBeansToReload(Collection classes, Class extends ReloaderType> reloaderType) {
57 | // Do nothing. We just need to know that an Entity or a RestDTO class have been compiled
58 | // So we need to reload the Jackson classes
59 | }
60 |
61 | @Override
62 | public void reload() {
63 | log.debug("Hot reloading Jackson classes");
64 | try {
65 | ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
66 | Collection mappers = BeanFactoryUtils
67 | .beansOfTypeIncludingAncestors(beanFactory, ObjectMapper.class)
68 | .values();
69 |
70 | for (ObjectMapper mapper : mappers) {
71 | log.trace("Flushing Jackson root deserializer cache");
72 | final Field rootDeserializersField = ReflectionUtils.findField(mapper.getClass(), "_rootDeserializers");
73 | ReflectionUtils.makeAccessible(rootDeserializersField);
74 | ((ConcurrentHashMap) ReflectionUtils.getField(rootDeserializersField, mapper)).clear();
75 |
76 | log.trace("Flushing Jackson serializer cache");
77 | SerializerProvider serializerProvider = mapper.getSerializerProvider();
78 | Field serializerCacheField = serializerProvider.getClass().getSuperclass().getSuperclass().getDeclaredField("_serializerCache");
79 | ReflectionUtils.makeAccessible(serializerCacheField);
80 | SerializerCache serializerCache = (SerializerCache) serializerCacheField.get(serializerProvider);
81 | Method serializerCacheFlushMethod = SerializerCache.class.getDeclaredMethod("flush");
82 | serializerCacheFlushMethod.invoke(serializerCache);
83 |
84 | log.trace("Flushing Jackson deserializer cache");
85 | DeserializationContext deserializationContext = mapper.getDeserializationContext();
86 | Field deSerializerCacheField = deserializationContext.getClass().getSuperclass().getSuperclass().getDeclaredField("_cache");
87 | ReflectionUtils.makeAccessible(deSerializerCacheField);
88 | DeserializerCache deSerializerCache = (DeserializerCache) deSerializerCacheField.get(deserializationContext);
89 | Method deSerializerCacheFlushMethod = DeserializerCache.class.getDeclaredMethod("flushCachedDeserializers");
90 | deSerializerCacheFlushMethod.invoke(deSerializerCache);
91 | }
92 | } catch (Exception e) {
93 | log.warn("Could not hot reload Jackson class!", e);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/core/src/main/java/io/github/jhipster/loaded/reloader/LiquibaseReloader.java:
--------------------------------------------------------------------------------
1 | package io.github.jhipster.loaded.reloader;
2 |
3 | import io.github.jhipster.loaded.hibernate.JHipsterEntityManagerFactoryWrapper;
4 | import io.github.jhipster.loaded.patch.liquibase.JhipsterHibernateSpringDatabase;
5 | import io.github.jhipster.loaded.reloader.liquibase.CustomXMLChangeLogSerializer;
6 | import io.github.jhipster.loaded.reloader.type.EntityReloaderType;
7 | import io.github.jhipster.loaded.reloader.type.ReloaderType;
8 | import liquibase.Liquibase;
9 | import liquibase.changelog.DatabaseChangeLog;
10 | import liquibase.database.Database;
11 | import liquibase.database.ObjectQuotingStrategy;
12 | import liquibase.database.jvm.JdbcConnection;
13 | import liquibase.diff.DiffResult;
14 | import liquibase.diff.compare.CompareControl;
15 | import liquibase.diff.output.DiffOutputControl;
16 | import liquibase.diff.output.changelog.DiffToChangeLog;
17 | import liquibase.exception.DatabaseException;
18 | import liquibase.exception.LiquibaseException;
19 | import liquibase.ext.hibernate.database.connection.HibernateConnection;
20 | import liquibase.integration.spring.SpringLiquibase;
21 | import liquibase.resource.ClassLoaderResourceAccessor;
22 | import liquibase.structure.DatabaseObject;
23 | import liquibase.structure.core.*;
24 | import org.apache.commons.io.FilenameUtils;
25 | import org.apache.commons.io.IOUtils;
26 | import org.apache.commons.io.filefilter.SuffixFileFilter;
27 | import org.apache.commons.lang.StringUtils;
28 | import org.slf4j.Logger;
29 | import org.slf4j.LoggerFactory;
30 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
31 | import org.springframework.context.ConfigurableApplicationContext;
32 | import org.springframework.core.annotation.Order;
33 | import org.springframework.core.io.FileSystemResourceLoader;
34 | import org.springframework.stereotype.Component;
35 | import javax.sql.DataSource;
36 | import java.io.*;
37 | import java.nio.file.FileSystems;
38 | import java.util.*;
39 |
40 | /**
41 | * Compare the Hibernate Entity JPA and the current database.
42 | * If changes have been done, a new db-changelog-[SEQUENCE].xml file will be generated and the database will be updated
43 | */
44 | @Component
45 | @Order(90)
46 | @ConditionalOnClass(Liquibase.class)
47 | public class LiquibaseReloader implements Reloader {
48 |
49 | private final Logger log = LoggerFactory.getLogger(LiquibaseReloader.class);
50 |
51 | public static final String MASTER_FILE = "src/main/resources/config/liquibase/master.xml";
52 | public static final String CHANGELOG_FOLER = "src/main/resources/config/liquibase/changelog/";
53 | public static final String RELATIVE_CHANGELOG_FOLER = "classpath:config/liquibase/changelog/";
54 |
55 | private Collection entitiesToReload = new LinkedHashSet<>();
56 |
57 | private ConfigurableApplicationContext applicationContext;
58 |
59 | private CompareControl compareControl;
60 |
61 | @Override
62 | public void init(ConfigurableApplicationContext applicationContext) {
63 | log.debug("Hot reloading JPA & Liquibase enabled");
64 | this.applicationContext = applicationContext;
65 | initCompareControl();
66 | }
67 |
68 | @Override
69 | public boolean supports(Class extends ReloaderType> reloaderType) {
70 | return reloaderType.equals(EntityReloaderType.class);
71 | }
72 |
73 | @Override
74 | public void prepare() {}
75 |
76 | @Override
77 | public boolean hasBeansToReload() {
78 | return false;
79 | }
80 |
81 | @Override
82 | public void addBeansToReload(Collection classes, Class extends ReloaderType> reloaderType) {
83 | entitiesToReload.addAll(classes);
84 | }
85 |
86 | @Override
87 | public void reload() {
88 | log.debug("Hot reloading JPA & Liquibase classes");
89 | Database hibernateDatabase = null;
90 | Database sourceDatabase = null;
91 |
92 | try {
93 | final String packagesToScan = applicationContext.getEnvironment().getProperty("hotReload.package.domain");
94 |
95 | // Build source datasource
96 | DataSource dataSource = applicationContext.getBean(DataSource.class);
97 | sourceDatabase = getSourceDatabase(dataSource);
98 | sourceDatabase.setObjectQuotingStrategy(ObjectQuotingStrategy.QUOTE_ALL_OBJECTS);
99 |
100 | // Build hibernate datasource - used as a reference
101 | hibernateDatabase = new JhipsterHibernateSpringDatabase(sourceDatabase.getDefaultCatalogName(), sourceDatabase.getLiquibaseSchemaName());
102 | hibernateDatabase.setObjectQuotingStrategy(ObjectQuotingStrategy.QUOTE_ALL_OBJECTS);
103 | hibernateDatabase.setConnection(new JdbcConnection(
104 | new HibernateConnection("hibernate:spring:" + packagesToScan + "?dialect=" + applicationContext.getEnvironment().getProperty("spring.jpa.database-platform"))));
105 |
106 | // Use liquibase to do a difference of schema between hibernate and database
107 | Liquibase liquibase = new Liquibase(new DatabaseChangeLog(), new ClassLoaderResourceAccessor(), sourceDatabase);
108 |
109 | // Retrieve the difference
110 | DiffResult diffResult = liquibase.diff(hibernateDatabase, sourceDatabase, compareControl);
111 |
112 | // Build the changelogs if any changes
113 | DiffToChangeLog diffToChangeLog = new DiffToChangeLog(diffResult, new DiffOutputControl(false, false, true));
114 |
115 | // Ignore the database changeLog table
116 | ignoreDatabaseChangeLogTable(diffResult);
117 | ignoreDatabaseJHipsterTables(diffResult);
118 |
119 | // If no changes do nothing
120 | if (diffToChangeLog.generateChangeSets().size() == 0) {
121 | log.debug("JHipster reload - No database change");
122 | return;
123 | }
124 |
125 | // Write the db-changelog-[SEQUENCE].xml file
126 | String changeLogString = toChangeLog(diffToChangeLog);
127 | String changeLogName = "db-changelog-" + calculateNextSequence() + ".xml";
128 | final File changelogFile = FileSystems.getDefault().getPath(CHANGELOG_FOLER + changeLogName).toFile();
129 | final FileOutputStream out = new FileOutputStream(changelogFile);
130 | IOUtils.write(changeLogString, out);
131 | IOUtils.closeQuietly(out);
132 | log.debug("JHipster reload - the db-changelog file '{}' has been generated", changelogFile.getAbsolutePath());
133 |
134 | // Re-write the master.xml files
135 | rewriteMasterFiles();
136 |
137 | // Execute the new changelog on the database
138 | SpringLiquibase springLiquibase = new SpringLiquibase() {
139 | @Override
140 | protected void performUpdate(Liquibase liquibase) throws LiquibaseException {
141 | // Override to be able to add
142 | liquibase.setChangeLogParameter("logicalFilePath", "none");
143 | super.performUpdate(liquibase);
144 | }
145 | };
146 | springLiquibase.setResourceLoader(new FileSystemResourceLoader());
147 | springLiquibase.setDataSource(dataSource);
148 | springLiquibase.setChangeLog("file:" + changelogFile.getAbsolutePath());
149 | springLiquibase.setContexts("development");
150 | try {
151 | springLiquibase.afterPropertiesSet();
152 | log.debug("JHipster reload - Successful database update");
153 | } catch (LiquibaseException e) {
154 | log.error("Failed to reload the database", e);
155 | }
156 |
157 | // Ask to reload the EntityManager
158 | JHipsterEntityManagerFactoryWrapper.reload(entitiesToReload);
159 | entitiesToReload.clear();
160 | } catch (Exception e) {
161 | log.error("Failed to generate the db-changelog.xml file", e);
162 | } finally {
163 | // close the database
164 | if (sourceDatabase != null) {
165 | try {
166 | sourceDatabase.close();
167 | } catch (DatabaseException e) {
168 | log.error("Failed to close the source database", e);
169 | }
170 | }
171 |
172 | if (hibernateDatabase != null) {
173 | try {
174 | hibernateDatabase.close();
175 | } catch (DatabaseException e) {
176 | log.error("Failed to close the reference database", e);
177 | }
178 | }
179 | }
180 | }
181 |
182 | private void ignoreDatabaseChangeLogTable(DiffResult diffResult)
183 | throws Exception {
184 |
185 | Set