├── .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 defaultFor, Class[] 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 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 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 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 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 unexpectedTables = diffResult 186 | .getUnexpectedObjects(Table.class); 187 | 188 | for (Table table : unexpectedTables) { 189 | if ("DATABASECHANGELOGLOCK".equalsIgnoreCase(table.getName()) 190 | || "DATABASECHANGELOG".equalsIgnoreCase(table.getName())) { 191 | 192 | diffResult.getUnexpectedObjects().remove(table); 193 | } 194 | } 195 | Set
missingTables = diffResult 196 | .getMissingObjects(Table.class); 197 | 198 | for (Table table : missingTables) { 199 | if ("DATABASECHANGELOGLOCK".equalsIgnoreCase(table.getName()) 200 | || "DATABASECHANGELOG".equalsIgnoreCase(table.getName())) { 201 | 202 | diffResult.getMissingObjects().remove(table); 203 | } 204 | } 205 | Set unexpectedColumns = diffResult.getUnexpectedObjects(Column.class); 206 | for (Column column : unexpectedColumns) { 207 | if ("DATABASECHANGELOGLOCK".equalsIgnoreCase(column.getRelation().getName()) 208 | || "DATABASECHANGELOG".equalsIgnoreCase(column.getRelation().getName())) { 209 | 210 | diffResult.getUnexpectedObjects().remove(column); 211 | } 212 | } 213 | Set missingColumns = diffResult.getMissingObjects(Column.class); 214 | for (Column column : missingColumns) { 215 | if ("DATABASECHANGELOGLOCK".equalsIgnoreCase(column.getRelation().getName()) 216 | || "DATABASECHANGELOG".equalsIgnoreCase(column.getRelation().getName())) { 217 | diffResult.getMissingObjects().remove(column); 218 | } 219 | } 220 | Set unexpectedIndexes = diffResult.getUnexpectedObjects(Index.class); 221 | for (Index index : unexpectedIndexes) { 222 | if ("DATABASECHANGELOGLOCK".equalsIgnoreCase(index.getTable().getName()) 223 | || "DATABASECHANGELOG".equalsIgnoreCase(index.getTable().getName())) { 224 | 225 | diffResult.getUnexpectedObjects().remove(index); 226 | } 227 | } 228 | Set missingIndexes = diffResult.getMissingObjects(Index.class); 229 | for (Index index : missingIndexes) { 230 | if ("DATABASECHANGELOGLOCK".equalsIgnoreCase(index.getTable().getName()) 231 | || "DATABASECHANGELOG".equalsIgnoreCase(index.getTable().getName())) { 232 | 233 | diffResult.getMissingObjects().remove(index); 234 | } 235 | } 236 | Set unexpectedPrimaryKeys = diffResult.getUnexpectedObjects(PrimaryKey.class); 237 | for (PrimaryKey primaryKey : unexpectedPrimaryKeys) { 238 | if ("DATABASECHANGELOGLOCK".equalsIgnoreCase(primaryKey.getTable().getName()) 239 | || "DATABASECHANGELOG".equalsIgnoreCase(primaryKey.getTable().getName())) { 240 | 241 | diffResult.getUnexpectedObjects().remove(primaryKey); 242 | } 243 | } 244 | Set missingPrimaryKeys = diffResult.getMissingObjects(PrimaryKey.class); 245 | for (PrimaryKey primaryKey : missingPrimaryKeys) { 246 | if ("DATABASECHANGELOGLOCK".equalsIgnoreCase(primaryKey.getTable().getName()) 247 | || "DATABASECHANGELOG".equalsIgnoreCase(primaryKey.getTable().getName())) { 248 | 249 | diffResult.getMissingObjects().remove(primaryKey); 250 | } 251 | } 252 | } 253 | 254 | private void ignoreDatabaseJHipsterTables(DiffResult diffResult) 255 | throws Exception { 256 | 257 | List jhipsterTables = new ArrayList<>(); 258 | jhipsterTables.add("HIBERNATE_SEQUENCES"); 259 | 260 | final String excludeTables = applicationContext.getEnvironment().getProperty("hotReload.liquibase.excludeTables", ""); 261 | 262 | if (StringUtils.isNotEmpty(excludeTables)) { 263 | jhipsterTables.addAll(Arrays.asList(excludeTables.toUpperCase().split(","))); 264 | } 265 | 266 | Set
unexpectedTables = diffResult 267 | .getUnexpectedObjects(Table.class); 268 | 269 | for (Table table : unexpectedTables) { 270 | if (jhipsterTables.contains(table.getName().toUpperCase().toUpperCase())) { 271 | diffResult.getUnexpectedObjects().remove(table); 272 | } 273 | } 274 | Set
missingTables = diffResult 275 | .getMissingObjects(Table.class); 276 | 277 | for (Table table : missingTables) { 278 | if (jhipsterTables.contains(table.getName().toUpperCase())) { 279 | diffResult.getMissingObjects().remove(table); 280 | } 281 | } 282 | Set unexpectedColumns = diffResult.getUnexpectedObjects(Column.class); 283 | for (Column column : unexpectedColumns) { 284 | if (jhipsterTables.contains(column.getRelation().getName().toUpperCase())) { 285 | diffResult.getUnexpectedObjects().remove(column); 286 | } 287 | } 288 | Set missingColumns = diffResult.getMissingObjects(Column.class); 289 | for (Column column : missingColumns) { 290 | if (jhipsterTables.contains(column.getRelation().getName().toUpperCase())) { 291 | diffResult.getMissingObjects().remove(column); 292 | } 293 | } 294 | Set unexpectedIndexes = diffResult.getUnexpectedObjects(Index.class); 295 | for (Index index : unexpectedIndexes) { 296 | if (jhipsterTables.contains(index.getTable().getName().toUpperCase())) { 297 | diffResult.getUnexpectedObjects().remove(index); 298 | } 299 | } 300 | Set missingIndexes = diffResult.getMissingObjects(Index.class); 301 | for (Index index : missingIndexes) { 302 | if (jhipsterTables.contains(index.getTable().getName().toUpperCase())) { 303 | diffResult.getMissingObjects().remove(index); 304 | } 305 | } 306 | Set unexpectedPrimaryKeys = diffResult.getUnexpectedObjects(PrimaryKey.class); 307 | for (PrimaryKey primaryKey : unexpectedPrimaryKeys) { 308 | if (jhipsterTables.contains(primaryKey.getTable().getName().toUpperCase())) { 309 | diffResult.getUnexpectedObjects().remove(primaryKey); 310 | } 311 | } 312 | Set missingPrimaryKeys = diffResult.getMissingObjects(PrimaryKey.class); 313 | for (PrimaryKey primaryKey : missingPrimaryKeys) { 314 | if (jhipsterTables.contains(primaryKey.getTable().getName().toUpperCase())) { 315 | diffResult.getMissingObjects().remove(primaryKey); 316 | } 317 | } 318 | } 319 | 320 | private String toChangeLog(DiffToChangeLog diffToChangeLog) throws Exception { 321 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 322 | PrintStream printStream = new PrintStream(out, true, "UTF-8"); 323 | diffToChangeLog.setChangeSetAuthor("jhipster"); 324 | CustomXMLChangeLogSerializer customXMLChangeLogSerializer = new CustomXMLChangeLogSerializer(); 325 | diffToChangeLog.print(printStream, customXMLChangeLogSerializer); 326 | printStream.close(); 327 | return out.toString("UTF-8"); 328 | } 329 | 330 | protected void initCompareControl() { 331 | Set> typesToInclude = new HashSet<>(); 332 | typesToInclude.add(Table.class); 333 | typesToInclude.add(Column.class); 334 | compareControl = new CompareControl(typesToInclude); 335 | compareControl.addSuppressedField(Table.class, "remarks"); 336 | compareControl.addSuppressedField(Column.class, "remarks"); 337 | compareControl.addSuppressedField(Column.class, "certainDataType"); 338 | compareControl.addSuppressedField(Column.class, "autoIncrementInformation"); 339 | } 340 | 341 | /** 342 | * Calculate the next sequence used to generate the db-changelog file. 343 | * 344 | * The sequence is formatted as follow: 345 | * leftpad with 0 + number 346 | * @return the next sequence 347 | */ 348 | private String calculateNextSequence() { 349 | final File changeLogFolder = FileSystems.getDefault().getPath(CHANGELOG_FOLER).toFile(); 350 | 351 | final File[] allChangelogs = changeLogFolder.listFiles((FileFilter) new SuffixFileFilter(".xml")); 352 | 353 | Integer sequence = 0; 354 | 355 | for (File changelog : allChangelogs) { 356 | String fileName = FilenameUtils.getBaseName(changelog.getName()); 357 | String currentSequence = StringUtils.substringAfterLast(fileName, "-"); 358 | int cpt = Integer.parseInt(currentSequence); 359 | if (cpt > sequence) { 360 | sequence = cpt; 361 | } 362 | } 363 | sequence++; 364 | return StringUtils.leftPad(sequence.toString(), 3, "0"); 365 | } 366 | 367 | /** 368 | * @return the source database 369 | */ 370 | private Database getSourceDatabase(DataSource dataSource) { 371 | String currentDatabase = applicationContext.getEnvironment().getProperty("spring.jpa.database"); 372 | 373 | String liquibaseDatabase; 374 | 375 | switch (currentDatabase) { 376 | case "MYSQL": 377 | liquibaseDatabase = "liquibase.database.core.MySQLDatabase"; 378 | break; 379 | case "POSTGRESQL": 380 | liquibaseDatabase = "liquibase.database.core.PostgresDatabase"; 381 | break; 382 | case "H2": 383 | liquibaseDatabase = "liquibase.database.core.H2Database"; 384 | break; 385 | default: 386 | throw new IllegalStateException("The database named '" + currentDatabase + "' is not supported"); 387 | } 388 | 389 | try { 390 | Database database = (Database) Class.forName(liquibaseDatabase).newInstance(); 391 | database.setConnection(new JdbcConnection(dataSource.getConnection())); 392 | 393 | return database; 394 | } catch (Exception e) { 395 | throw new IllegalStateException("Failed to instanciate the liquibase database: " + liquibaseDatabase, e); 396 | } 397 | } 398 | 399 | /** 400 | * The master.xml file will be rewritten to include the new changelogs 401 | */ 402 | private void rewriteMasterFiles() { 403 | try { 404 | File masterFile = FileSystems.getDefault().getPath(MASTER_FILE).toFile(); 405 | FileOutputStream fileOutputStream = new FileOutputStream(masterFile); 406 | 407 | final File changeLogFolder = FileSystems.getDefault().getPath(CHANGELOG_FOLER).toFile(); 408 | 409 | final File[] allChangelogs = changeLogFolder.listFiles((FileFilter) new SuffixFileFilter(".xml")); 410 | 411 | // sort files by name as File.list does NOT "guarantee that the name strings in the resulting array will appear in any specific order" 412 | Arrays.sort(allChangelogs, new Comparator() { 413 | @Override 414 | public int compare(File f1, File f2) { 415 | return f1.getName().compareTo(f2.getName()); 416 | } 417 | }); 418 | 419 | String begin = "\n" + 420 | "\n\r"; 424 | String end = ""; 425 | 426 | IOUtils.write(begin, fileOutputStream); 427 | 428 | // Writer the changelogs 429 | StringBuilder sb = new StringBuilder(); 430 | 431 | for (File allChangelog : allChangelogs) { 432 | String fileName = allChangelog.getName(); 433 | sb.append("\t").append("\r\n"); 434 | } 435 | 436 | IOUtils.write(sb.toString(), fileOutputStream); 437 | IOUtils.write(end, fileOutputStream); 438 | IOUtils.closeQuietly(fileOutputStream); 439 | 440 | log.debug("The file '{}' has been updated", MASTER_FILE); 441 | } catch (Exception e) { 442 | log.error("Failed to write the master.xml file. This file must be updated manually"); 443 | 444 | } 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/Reloader.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader; 2 | 3 | import io.github.jhipster.loaded.reloader.type.ReloaderType; 4 | import org.springframework.context.ConfigurableApplicationContext; 5 | import org.springframework.core.annotation.Order; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * Interface will be used by all types of reloader. 11 | * A reloader is special class used to reload a spring bean (spring, spring data, spring rest controller etc...) 12 | * 13 | * @see io.github.jhipster.loaded.reloader.JacksonReloader 14 | */ 15 | @Order 16 | public interface Reloader { 17 | 18 | /** 19 | * Initialize the reloader class 20 | * @param applicationContext the application context 21 | */ 22 | void init(ConfigurableApplicationContext applicationContext); 23 | 24 | /** 25 | * Wheter the reloader type is supported by this reloader 26 | * 27 | * @param reloaderType type of the spring beans to reload 28 | * 29 | * @return true if supported, false otherwise 30 | */ 31 | boolean supports(Class reloaderType); 32 | 33 | /** 34 | * Reloader can keep beans to reload if it is not able to reload the class the fisrt time due 35 | * to missing dependencies for example. 36 | * 37 | * @return true if needed to reload, false otherwise 38 | */ 39 | boolean hasBeansToReload(); 40 | 41 | /** 42 | * Tell the reloader to be prepared to accept new classes to reload 43 | */ 44 | void prepare(); 45 | 46 | /** 47 | * Call when a spring classes have been compiled and 48 | * only this spring classes need to be reloaded 49 | * 50 | * @param classes the list of compiled classes to reload 51 | * @param reloaderType type of the spring beans to reload 52 | */ 53 | void addBeansToReload(Collection classes, Class reloaderType); 54 | 55 | /** 56 | * Tell the reloader to start loaded the list of classes that have been added previously 57 | * in calling the addBeans method. 58 | */ 59 | void reload(); 60 | } 61 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/ReloaderUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader; 2 | 3 | import org.apache.commons.lang.StringUtils; 4 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 5 | import org.springframework.context.annotation.Scope; 6 | import org.springframework.core.annotation.AnnotationUtils; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.stereotype.Repository; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import java.lang.annotation.Annotation; 14 | 15 | /** 16 | * Utility class 17 | */ 18 | public class ReloaderUtils { 19 | 20 | public static Annotation getSpringClassAnnotation(Class clazz) { 21 | Annotation classAnnotation = null; 22 | 23 | if (AnnotationUtils.isAnnotationDeclaredLocally(Controller.class, clazz)) { 24 | classAnnotation = AnnotationUtils.findAnnotation(clazz, Controller.class); 25 | } 26 | if (AnnotationUtils.isAnnotationDeclaredLocally(RestController.class, clazz)) { 27 | classAnnotation = AnnotationUtils.findAnnotation(clazz, RestController.class); 28 | } 29 | if (AnnotationUtils.isAnnotationDeclaredLocally(Service.class, clazz)) { 30 | classAnnotation = AnnotationUtils.findAnnotation(clazz, Service.class); 31 | } 32 | if (AnnotationUtils.isAnnotationDeclaredLocally(Repository.class, clazz)) { 33 | classAnnotation = AnnotationUtils.findAnnotation(clazz, Repository.class); 34 | } 35 | if (AnnotationUtils.isAnnotationDeclaredLocally(Component.class, clazz)) { 36 | classAnnotation = AnnotationUtils.findAnnotation(clazz, Component.class); 37 | } 38 | 39 | return classAnnotation; 40 | } 41 | 42 | 43 | public static String getScope(Class clazz) { 44 | String scope = ConfigurableBeanFactory.SCOPE_SINGLETON; 45 | Annotation scopeAnnotation = AnnotationUtils.findAnnotation(clazz, Scope.class); 46 | if (scopeAnnotation != null) { 47 | scope = (String) AnnotationUtils.getValue(scopeAnnotation); 48 | } 49 | return scope; 50 | } 51 | 52 | public static String constructBeanName(Class clazz) { 53 | Annotation annotation = ReloaderUtils.getSpringClassAnnotation(clazz); 54 | String beanName = (String) AnnotationUtils.getValue(annotation); 55 | if (beanName == null || beanName.isEmpty()) { 56 | beanName = StringUtils.uncapitalize(clazz.getSimpleName()); 57 | } 58 | return beanName; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/SpringReloader.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader; 2 | 3 | import io.github.jhipster.loaded.reloader.listener.JHipsterHandlerMappingListener; 4 | import io.github.jhipster.loaded.reloader.listener.SpringListener; 5 | import io.github.jhipster.loaded.reloader.loader.SpringLoader; 6 | import io.github.jhipster.loaded.reloader.type.*; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.aop.Advisor; 10 | import org.springframework.aop.framework.Advised; 11 | import org.springframework.aop.framework.autoproxy.BeanFactoryAdvisorRetrievalHelper; 12 | import org.springframework.aop.support.AopUtils; 13 | import org.springframework.beans.factory.NoSuchBeanDefinitionException; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.beans.factory.support.AbstractBeanDefinition; 16 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 17 | import org.springframework.beans.factory.support.RootBeanDefinition; 18 | import org.springframework.context.ConfigurableApplicationContext; 19 | import org.springframework.core.annotation.AnnotationAwareOrderComparator; 20 | import org.springframework.core.annotation.AnnotationUtils; 21 | import org.springframework.core.annotation.Order; 22 | import org.springframework.stereotype.Component; 23 | import org.springframework.util.ReflectionUtils; 24 | 25 | import javax.inject.Inject; 26 | import java.lang.reflect.Field; 27 | import java.util.*; 28 | 29 | /** 30 | * Reloads Spring Beans. 31 | */ 32 | @Component 33 | @Order(100) 34 | public class SpringReloader implements Reloader { 35 | 36 | private final Logger log = LoggerFactory.getLogger(SpringReloader.class); 37 | 38 | private ConfigurableApplicationContext applicationContext; 39 | private BeanFactoryAdvisorRetrievalHelper beanFactoryAdvisorRetrievalHelper; 40 | 41 | 42 | private final List springListeners = new ArrayList<>(); 43 | private final List springLoaders = new ArrayList<>(); 44 | 45 | private Set toReloadBeans = new LinkedHashSet<>(); 46 | private List newToWaitFromBeans = new ArrayList<>(); 47 | private Map existingToWaitFromBeans = new HashMap<>(); 48 | 49 | @Override 50 | public void init(ConfigurableApplicationContext applicationContext) { 51 | log.debug("Hot reloading Spring Beans enabled"); 52 | this.applicationContext = applicationContext; 53 | this.beanFactoryAdvisorRetrievalHelper = new BeanFactoryAdvisorRetrievalHelper(applicationContext.getBeanFactory()); 54 | 55 | this.applicationContext.getAutowireCapableBeanFactory().autowireBean(this); 56 | 57 | // register listeners 58 | registerListeners(); 59 | 60 | // register loaders 61 | registerLoaders(); 62 | } 63 | 64 | @Override 65 | public boolean supports(Class reloaderType) { 66 | return reloaderType.equals(EntityReloaderType.class) || reloaderType.equals(RepositoryReloaderType.class) 67 | || reloaderType.equals(ServiceReloaderType.class) || reloaderType.equals(ComponentReloaderType.class) 68 | || reloaderType.equals(ControllerReloaderType.class); 69 | } 70 | 71 | @Override 72 | public void prepare() {} 73 | 74 | @Override 75 | public boolean hasBeansToReload() { 76 | return toReloadBeans.size() > 0 || newToWaitFromBeans.size() > 0; 77 | } 78 | 79 | @Override 80 | public void addBeansToReload(Collection classes, Class reloaderType) { 81 | if (reloaderType.equals(EntityReloaderType.class)) { 82 | List newSpringBeans = new ArrayList<>(); 83 | List existingSpringBeans = new ArrayList<>(); 84 | 85 | newSpringBeans.addAll(newToWaitFromBeans); 86 | newToWaitFromBeans.clear(); 87 | existingSpringBeans.addAll(existingToWaitFromBeans.values()); 88 | existingToWaitFromBeans.clear(); 89 | 90 | start(newSpringBeans, existingSpringBeans); 91 | } else { 92 | toReloadBeans.addAll(classes); 93 | } 94 | } 95 | 96 | @Override 97 | public void reload() { 98 | List newSpringBeans = new ArrayList<>(); 99 | List existingSpringBeans = new ArrayList<>(); 100 | 101 | newSpringBeans.addAll(newToWaitFromBeans); 102 | newToWaitFromBeans.clear(); 103 | existingSpringBeans.addAll(existingToWaitFromBeans.values()); 104 | existingToWaitFromBeans.clear(); 105 | 106 | start(newSpringBeans, existingSpringBeans); 107 | } 108 | 109 | private void start(List newSpringBeans, List existingSpringBeans) { 110 | try { 111 | DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory(); 112 | 113 | //1) Split between new/existing beans 114 | for (Class toReloadBean : toReloadBeans) { 115 | log.trace("Hot reloading Spring bean: {}", toReloadBean.getName()); 116 | String beanName = ReloaderUtils.constructBeanName(toReloadBean); 117 | if (!beanFactory.containsBeanDefinition(beanName)) { 118 | newSpringBeans.add(toReloadBean); 119 | // Check if this new class is a dependent class. 120 | // If so add this dependent class to the newSpringBeans list 121 | if (newToWaitFromBeans.size() > 0) { 122 | newSpringBeans.addAll(newToWaitFromBeans); 123 | newToWaitFromBeans.clear(); 124 | } 125 | } else { 126 | existingSpringBeans.add(toReloadBean); 127 | if (existingToWaitFromBeans.containsKey(toReloadBean.getName())) { 128 | existingSpringBeans.add(existingToWaitFromBeans.get(toReloadBean.getName())); 129 | existingToWaitFromBeans.remove(toReloadBean.getName()); 130 | } 131 | } 132 | } 133 | 134 | //2) Declare new beans prior to instanciation for cross bean references 135 | for (Class clazz : newSpringBeans) { 136 | String beanName = ReloaderUtils.constructBeanName(clazz); 137 | String scope = ReloaderUtils.getScope(clazz); 138 | RootBeanDefinition bd = new RootBeanDefinition(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true); 139 | bd.setScope(scope); 140 | beanFactory.registerBeanDefinition(beanName, bd); 141 | } 142 | 143 | //3) Instanciate new beans 144 | for (Class clazz : newSpringBeans) { 145 | String beanName = ReloaderUtils.constructBeanName(clazz); 146 | try { 147 | processLoader(clazz); 148 | processListener(clazz, true); 149 | toReloadBeans.remove(clazz); 150 | log.info("JHipster reload - New Spring bean '{}' has been reloaded.", clazz); 151 | } catch (Exception e) { 152 | log.trace("The Spring bean can't be loaded at this time. Keep it to reload it later", e); 153 | // remove the registration bean to treat this class as new class 154 | beanFactory.removeBeanDefinition(beanName); 155 | newToWaitFromBeans.add(clazz); 156 | toReloadBeans.remove(clazz); 157 | } 158 | } 159 | 160 | //4) Resolve dependencies for existing beans 161 | for (Class clazz : existingSpringBeans) { 162 | Object beanInstance = applicationContext.getBean(clazz); 163 | 164 | log.trace("Existing bean, autowiring fields"); 165 | if (AopUtils.isCglibProxy(beanInstance)) { 166 | log.trace("This is a CGLIB proxy, getting the real object"); 167 | addAdvisorIfNeeded(clazz, beanInstance); 168 | final Advised advised = (Advised) beanInstance; 169 | beanInstance = advised.getTargetSource().getTarget(); 170 | } else if (AopUtils.isJdkDynamicProxy(beanInstance)) { 171 | log.trace("This is a JDK proxy, getting the real object"); 172 | addAdvisorIfNeeded(clazz, beanInstance); 173 | final Advised advised = (Advised) beanInstance; 174 | beanInstance = advised.getTargetSource().getTarget(); 175 | } else { 176 | log.trace("This is a normal Java object"); 177 | } 178 | boolean failedToUpdate = false; 179 | Field[] fields = beanInstance.getClass().getDeclaredFields(); 180 | for (Field field : fields) { 181 | if (AnnotationUtils.getAnnotation(field, Inject.class) != null || 182 | AnnotationUtils.getAnnotation(field, Autowired.class) != null) { 183 | log.trace("@Inject/@Autowired annotation found on field {}", field.getName()); 184 | ReflectionUtils.makeAccessible(field); 185 | if (ReflectionUtils.getField(field, beanInstance) != null) { 186 | log.trace("Field is already injected, not doing anything"); 187 | } else { 188 | log.trace("Field is null, injecting a Spring bean"); 189 | try { 190 | Object beanToInject = applicationContext.getBean(field.getType()); 191 | ReflectionUtils.setField(field, beanInstance, beanToInject); 192 | } catch (NoSuchBeanDefinitionException bsbde) { 193 | log.debug("JHipster reload - Spring bean '{}' does not exist, " + 194 | "wait until this class will be available.", field.getType()); 195 | failedToUpdate = true; 196 | existingToWaitFromBeans.put(field.getType().getName(), clazz); 197 | } 198 | } 199 | } 200 | } 201 | toReloadBeans.remove(clazz); 202 | if (!failedToUpdate) { 203 | processListener(clazz, false); 204 | } 205 | log.info("JHipster reload - Existing Spring bean '{}' has been reloaded.", clazz); 206 | } 207 | 208 | for (SpringListener springListener : springListeners) { 209 | springListener.process(); 210 | } 211 | } catch (Exception e) { 212 | log.warn("Could not hot reload Spring bean!", e); 213 | } 214 | } 215 | 216 | /** 217 | * AOP uses advisor to intercept any annotations. 218 | */ 219 | private void addAdvisorIfNeeded(Class clazz, Object beanInstance) { 220 | final Advised advised = (Advised) beanInstance; 221 | final List candidateAdvisors = this.beanFactoryAdvisorRetrievalHelper.findAdvisorBeans(); 222 | 223 | final List advisorsThatCanApply = AopUtils.findAdvisorsThatCanApply(candidateAdvisors, clazz); 224 | 225 | for (Advisor advisor : advisorsThatCanApply) { 226 | // Add the advisor to the advised if it doesn't exist 227 | if (advised.indexOf(advisor) == -1) { 228 | advised.addAdvisor(advisor); 229 | } 230 | } 231 | } 232 | 233 | private void processListener(Class clazz, boolean isNewClass) { 234 | for (SpringListener springListener : springListeners) { 235 | if (springListener.support(clazz)) { 236 | springListener.addBeansToProcess(clazz, isNewClass); 237 | } 238 | } 239 | } 240 | 241 | private void processLoader(Class clazz) { 242 | for (SpringLoader springLoader : springLoaders) { 243 | if (springLoader.supports(clazz)) { 244 | springLoader.registerBean(clazz); 245 | } 246 | } 247 | } 248 | 249 | private void registerListeners() { 250 | springListeners.add(new JHipsterHandlerMappingListener()); 251 | 252 | for (SpringListener springListener : springListeners) { 253 | springListener.init(applicationContext); 254 | } 255 | } 256 | 257 | private void registerLoaders() { 258 | final Map beansOfType = applicationContext.getBeansOfType(SpringLoader.class); 259 | 260 | for (SpringLoader springLoader : beansOfType.values()) { 261 | springLoaders.add(springLoader); 262 | springLoader.init(applicationContext); 263 | } 264 | 265 | Collections.sort(springLoaders, new AnnotationAwareOrderComparator()); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/liquibase/CustomXMLChangeLogSerializer.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.liquibase; 2 | 3 | import liquibase.changelog.ChangeSet; 4 | import liquibase.parser.core.xml.LiquibaseEntityResolver; 5 | import liquibase.parser.core.xml.XMLChangeLogSAXParser; 6 | import liquibase.serializer.LiquibaseSerializable; 7 | import liquibase.serializer.core.xml.XMLChangeLogSerializer; 8 | import liquibase.util.xml.DefaultXmlWriter; 9 | import org.w3c.dom.Document; 10 | import org.w3c.dom.Element; 11 | import javax.xml.parsers.DocumentBuilder; 12 | import javax.xml.parsers.DocumentBuilderFactory; 13 | import javax.xml.parsers.ParserConfigurationException; 14 | import java.io.IOException; 15 | import java.io.OutputStream; 16 | import java.util.List; 17 | 18 | /** 19 | * Override the serializer to add the logicalFilePath at the databaseChangeLog and the changeSet elements 20 | * This will fix the issues when the changeSet is running twice during the hotreload and when the application starts 21 | */ 22 | public class CustomXMLChangeLogSerializer extends XMLChangeLogSerializer { 23 | @Override 24 | public void write(List changeSets, OutputStream out) throws IOException { 25 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 26 | DocumentBuilder documentBuilder; 27 | try { 28 | documentBuilder = factory.newDocumentBuilder(); 29 | } catch (ParserConfigurationException e) { 30 | throw new RuntimeException(e); 31 | } 32 | documentBuilder.setEntityResolver(new LiquibaseEntityResolver(new CustomXMLChangeLogSerializer())); 33 | 34 | Document doc = documentBuilder.newDocument(); 35 | Element changeLogElement = doc.createElementNS("http://www.liquibase.org/xml/ns/dbchangelog", "databaseChangeLog"); 36 | 37 | changeLogElement.setAttribute("logicalFilePath", "none"); 38 | changeLogElement.setAttribute("xmlns","http://www.liquibase.org/xml/ns/dbchangelog"); 39 | changeLogElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); 40 | changeLogElement.setAttribute("xsi:schemaLocation", "http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-" + XMLChangeLogSAXParser.getSchemaVersion() + ".xsd"); 41 | 42 | doc.appendChild(changeLogElement); 43 | setCurrentChangeLogFileDOM(doc); 44 | 45 | for (ChangeSet changeSet : changeSets) { 46 | doc.getDocumentElement().appendChild(createNode(changeSet)); 47 | } 48 | 49 | new DefaultXmlWriter().write(doc, out); 50 | } 51 | 52 | @Override 53 | public Element createNode(LiquibaseSerializable object) { 54 | Element node = super.createNode(object); 55 | 56 | if (object instanceof ChangeSet) { 57 | node.setAttribute("logicalFilePath", "none"); 58 | } 59 | 60 | return node; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/listener/JHipsterHandlerMappingListener.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.listener; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.beans.factory.config.BeanDefinition; 7 | import org.springframework.beans.factory.support.RootBeanDefinition; 8 | import org.springframework.context.ConfigurableApplicationContext; 9 | import org.springframework.core.Ordered; 10 | import org.springframework.util.ClassUtils; 11 | import org.springframework.util.ReflectionUtils; 12 | import org.springframework.util.StringUtils; 13 | import org.springframework.web.method.HandlerMethodSelector; 14 | import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; 15 | import org.springframework.web.servlet.mvc.method.RequestMappingInfo; 16 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 17 | 18 | import java.lang.reflect.Field; 19 | import java.lang.reflect.Method; 20 | import java.util.*; 21 | 22 | /** 23 | * This Handler mapping is used to map only new Controller classes. 24 | * The existing controllers are mapped by the default RequestMappingHandlerMapping class. 25 | * 26 | * Each time, a controller is compiled this handler is called and the new controllers will be re-mapped 27 | */ 28 | public class JHipsterHandlerMappingListener extends RequestMappingHandlerMapping implements SpringListener, Ordered { 29 | 30 | private final Logger log = LoggerFactory.getLogger(JHipsterHandlerMappingListener.class); 31 | 32 | private List newControllers = new ArrayList<>(); 33 | 34 | private ConfigurableApplicationContext applicationContext; 35 | 36 | @Override 37 | public void init(ConfigurableApplicationContext applicationContext) { 38 | this.applicationContext = applicationContext; 39 | 40 | // By default the static resource handler mapping is LOWEST_PRECEDENCE - 1 41 | super.setOrder(LOWEST_PRECEDENCE - 3); 42 | 43 | // Register the bean 44 | String beanName = StringUtils.uncapitalize(JHipsterHandlerMappingListener.class.getSimpleName()); 45 | 46 | RootBeanDefinition bd = new RootBeanDefinition(JHipsterHandlerMappingListener.class); 47 | bd.setScope(BeanDefinition.SCOPE_SINGLETON); 48 | applicationContext.getBeanFactory().registerSingleton(beanName, this); 49 | } 50 | 51 | @Override 52 | public boolean support(Class clazz) { 53 | return super.isHandler(clazz); 54 | } 55 | 56 | @Override 57 | public void addBeansToProcess(Class clazz, boolean newClass) { 58 | // Register only new classes - existing classes will be handled by the default RequestMappingHandlerMapping class 59 | if (newClass) { 60 | newControllers.add(ClassUtils.getUserClass(clazz)); 61 | } else { // remove the class from the current list because now the class is managed by the default handler mapping 62 | newControllers.remove(ClassUtils.getUserClass(clazz)); 63 | } 64 | } 65 | 66 | @Override 67 | public void process() { 68 | // Clear existing mapping to register new classes 69 | clearExistingMapping(); 70 | 71 | // Re-map the methods 72 | for (Class clazz : newControllers) { 73 | final Class userType = clazz; 74 | 75 | Set methods = HandlerMethodSelector.selectMethods(userType, new ReflectionUtils.MethodFilter() { 76 | @Override 77 | public boolean matches(Method method) { 78 | return getMappingForMethod(method, userType) != null; 79 | } 80 | }); 81 | 82 | try { 83 | Object handler = applicationContext.getBean(clazz); 84 | for (Method method : methods) { 85 | RequestMappingInfo mapping = getMappingForMethod(method, userType); 86 | try { 87 | registerHandlerMethod(handler, method, mapping); 88 | } catch (Exception e) { 89 | logger.trace("Failed to register the handler for the method '" + method.getName() + "'", e); 90 | } 91 | } 92 | } catch (BeansException e) { 93 | log.debug("JHipster reload - Spring bean '{}' does not exist, " + 94 | "wait until this class will be available.", clazz.getName()); 95 | } 96 | } 97 | } 98 | 99 | /** 100 | * Clear the two maps used to map the urls and the methods. 101 | */ 102 | private void clearExistingMapping() { 103 | try { 104 | final Field urlMapField = ReflectionUtils.findField(AbstractHandlerMethodMapping.class, "urlMap"); 105 | urlMapField.setAccessible(true); 106 | Map urlMap = (Map) urlMapField.get(this); 107 | 108 | for (Object mapping : urlMap.keySet()) { 109 | if (logger.isInfoEnabled()) { 110 | logger.info("Remove Mapped \"" + mapping + "\""); 111 | } 112 | } 113 | 114 | urlMap.clear(); 115 | 116 | final Field handlerMethodsField = ReflectionUtils.findField(AbstractHandlerMethodMapping.class, "handlerMethods"); 117 | handlerMethodsField.setAccessible(true); 118 | Map m = (Map) handlerMethodsField.get(this); 119 | m.clear(); 120 | } catch (Exception e) { 121 | log.error("Failed to clean the urlMap and the handlerMethods objects", e); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/listener/SpringListener.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.listener; 2 | 3 | import org.springframework.context.ConfigurableApplicationContext; 4 | 5 | /** 6 | * Listener used to interact with spring when classes are reloaded. 7 | * 8 | * For example, the JHipsterHandlerMappingListener will create 9 | * the mapping between a request and a method only for new controller 10 | */ 11 | public interface SpringListener { 12 | 13 | /** 14 | * Initialize the listener class 15 | * @param applicationContext the application context 16 | */ 17 | void init(ConfigurableApplicationContext applicationContext); 18 | 19 | /** 20 | * Wheter the class is supported by this listener 21 | * 22 | * @param clazz class of the spring beans to process 23 | * 24 | * @return true if supported, false otherwise 25 | */ 26 | boolean support(Class clazz); 27 | 28 | /** 29 | * Call when a new or existing file has been reloaded 30 | * 31 | * @param clazz class of the spring beans to process 32 | * @param newClazz true if the class is a new class, false otherwise 33 | */ 34 | void addBeansToProcess(Class clazz, boolean newClazz); 35 | 36 | /** 37 | * Process the new or existing spring beans 38 | */ 39 | void process(); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/listener/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Hot reloading listener with Spring Loaded. 3 | */ 4 | package io.github.jhipster.loaded.reloader.listener; 5 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/loader/DefaultSpringLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.loader; 2 | 3 | import io.github.jhipster.loaded.reloader.ReloaderUtils; 4 | import org.apache.commons.lang.ClassUtils; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 7 | import org.springframework.context.ConfigurableApplicationContext; 8 | import org.springframework.core.Ordered; 9 | import org.springframework.core.annotation.Order; 10 | import org.springframework.stereotype.Component; 11 | 12 | /** 13 | * Default loader used to load a new spring bean independent of the type 14 | * 15 | */ 16 | @Component 17 | @Order(Ordered.LOWEST_PRECEDENCE) 18 | public class DefaultSpringLoader implements SpringLoader { 19 | 20 | private DefaultListableBeanFactory beanFactory; 21 | 22 | @Override 23 | public void init(ConfigurableApplicationContext applicationContext) { 24 | beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory(); 25 | } 26 | 27 | @Override 28 | public boolean supports(Class clazz) { 29 | return !ClassUtils.isAssignable(clazz, org.springframework.data.repository.Repository.class); 30 | } 31 | 32 | @Override 33 | public void registerBean(Class clazz) throws BeansException { 34 | String beanName = ReloaderUtils.constructBeanName(clazz); 35 | beanFactory.getBean(beanName); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/loader/JpaSpringLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.loader; 2 | 3 | import io.github.jhipster.loaded.reloader.ReloaderUtils; 4 | import org.apache.commons.lang.ClassUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.BeansException; 8 | import org.springframework.beans.factory.ListableBeanFactory; 9 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 11 | import org.springframework.context.ConfigurableApplicationContext; 12 | import org.springframework.core.Ordered; 13 | import org.springframework.core.annotation.Order; 14 | import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; 15 | import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; 16 | import org.springframework.data.repository.util.TxUtils; 17 | import org.springframework.stereotype.Component; 18 | 19 | import javax.persistence.EntityManager; 20 | import javax.persistence.PersistenceContext; 21 | import java.lang.reflect.Constructor; 22 | 23 | /** 24 | * Default loader used to load a new spring bean independent of the type 25 | * 26 | */ 27 | @Component 28 | @Order(Ordered.LOWEST_PRECEDENCE - 1) 29 | @ConditionalOnClass(EntityManager.class) 30 | public class JpaSpringLoader implements SpringLoader { 31 | 32 | private final Logger log = LoggerFactory.getLogger(JpaSpringLoader.class); 33 | 34 | private DefaultListableBeanFactory beanFactory; 35 | private JpaRepositoryFactory jpaRepositoryFactory; 36 | 37 | @PersistenceContext 38 | private EntityManager entityManager; 39 | 40 | @Override 41 | public void init(ConfigurableApplicationContext applicationContext) { 42 | beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory(); 43 | this.jpaRepositoryFactory = new JpaRepositoryFactory(entityManager); 44 | try { 45 | // Make sure calls to the repository instance are intercepted for annotated transactions 46 | Class transactionalRepositoryProxyPostProcessor = Class.forName("org.springframework.data.repository.core.support.TransactionalRepositoryProxyPostProcessor"); 47 | final Constructor constructor = transactionalRepositoryProxyPostProcessor.getConstructor(ListableBeanFactory.class, String.class); 48 | final RepositoryProxyPostProcessor repositoryProxyPostProcessor = (RepositoryProxyPostProcessor) 49 | constructor.newInstance(applicationContext.getBeanFactory(), TxUtils.DEFAULT_TRANSACTION_MANAGER); 50 | jpaRepositoryFactory.addRepositoryProxyPostProcessor(repositoryProxyPostProcessor); 51 | } catch (Exception e) { 52 | log.error("Failed to initialize the TransactionalRepositoryProxyPostProcessor class", e); 53 | } 54 | } 55 | 56 | @Override 57 | public boolean supports(Class clazz) { 58 | return ClassUtils.isAssignable(clazz, org.springframework.data.repository.Repository.class); 59 | } 60 | 61 | @Override 62 | public void registerBean(Class clazz) throws BeansException { 63 | String beanName = ReloaderUtils.constructBeanName(clazz); 64 | final Object repository = jpaRepositoryFactory.getRepository(clazz); 65 | beanFactory.registerSingleton(beanName, repository); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/loader/MongoSpringLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.loader; 2 | 3 | import com.mongodb.Mongo; 4 | import io.github.jhipster.loaded.reloader.ReloaderUtils; 5 | import org.apache.commons.lang.ClassUtils; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 9 | import org.springframework.context.ConfigurableApplicationContext; 10 | import org.springframework.core.Ordered; 11 | import org.springframework.core.annotation.Order; 12 | import org.springframework.data.mongodb.core.MongoTemplate; 13 | import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory; 14 | import org.springframework.stereotype.Component; 15 | 16 | import javax.inject.Inject; 17 | 18 | /** 19 | * Default loader used to load a new spring bean independent of the type 20 | * 21 | */ 22 | @Component 23 | @Order(Ordered.LOWEST_PRECEDENCE - 1) 24 | @ConditionalOnClass(Mongo.class) 25 | public class MongoSpringLoader implements SpringLoader { 26 | 27 | private DefaultListableBeanFactory beanFactory; 28 | private MongoRepositoryFactory mongoRepositoryFactory; 29 | 30 | @Inject 31 | private MongoTemplate mongoTemplate; 32 | 33 | @Override 34 | public void init(ConfigurableApplicationContext applicationContext) { 35 | beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory(); 36 | this.mongoRepositoryFactory = new MongoRepositoryFactory(mongoTemplate); 37 | } 38 | 39 | @Override 40 | public boolean supports(Class clazz) { 41 | return ClassUtils.isAssignable(clazz, org.springframework.data.repository.Repository.class); 42 | } 43 | 44 | @Override 45 | public void registerBean(Class clazz) throws BeansException { 46 | String beanName = ReloaderUtils.constructBeanName(clazz); 47 | final Object repository = mongoRepositoryFactory.getRepository(clazz); 48 | beanFactory.registerSingleton(beanName, repository); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/loader/SpringLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.loader; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ConfigurableApplicationContext; 5 | 6 | /** 7 | * A loader is used by SpringReloader to load a new spring bean. 8 | */ 9 | public interface SpringLoader { 10 | 11 | /** 12 | * Initialize the loader class 13 | * @param applicationContext the application context 14 | */ 15 | void init(ConfigurableApplicationContext applicationContext); 16 | 17 | /** 18 | * Wheter the reloader class is supported by this loader 19 | * 20 | * @param clazz type of the spring bean to load 21 | * 22 | * @return true if supported, false otherwise 23 | */ 24 | boolean supports(Class clazz); 25 | 26 | /** 27 | * Register the class as a spring bean 28 | * The bean definition has been already registered before calling the method 29 | * 30 | * @param clazz class of the new bean to load 31 | * @throws BeansException if the bean could not be registered 32 | */ 33 | void registerBean(Class clazz) throws BeansException; 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/loader/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Hot reloading loader with Spring Loaded. 3 | */ 4 | package io.github.jhipster.loaded.reloader.loader; -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Hot reloading reloader with Spring Loaded. 3 | */ 4 | package io.github.jhipster.loaded.reloader; -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/type/ComponentReloaderType.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.type; 2 | 3 | /** 4 | * Defines the Component reloader type 5 | * 6 | * A Entity class must support org.springframework.stereotype.Component classes 7 | */ 8 | public final class ComponentReloaderType implements ReloaderType { 9 | 10 | private static final String name = "components"; 11 | public static final ComponentReloaderType instance = new ComponentReloaderType(); 12 | 13 | @Override 14 | public String getName() { 15 | return name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/type/ControllerReloaderType.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.type; 2 | 3 | /** 4 | * Defines the Controller reloader type 5 | * 6 | * A Entity class must support org.springframework.stereotype.Controller classes 7 | * or org.springframework.web.bind.annotation.RestController classes 8 | */ 9 | public final class ControllerReloaderType implements ReloaderType { 10 | 11 | private static final String name = "controllers"; 12 | public static final ControllerReloaderType instance = new ControllerReloaderType(); 13 | 14 | @Override 15 | public String getName() { 16 | return name; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/type/EntityReloaderType.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.type; 2 | 3 | /** 4 | * Defines the Entity reloader type 5 | * 6 | * A Entity class must support javax.persistence.Entity classes 7 | */ 8 | public final class EntityReloaderType implements ReloaderType { 9 | 10 | private static final String name = "entities"; 11 | public static final EntityReloaderType instance = new EntityReloaderType(); 12 | 13 | @Override 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/type/ReloaderType.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.type; 2 | 3 | /** 4 | * Used to define the type of reloader which are supported by Jhipster Reloaded 5 | */ 6 | public interface ReloaderType { 7 | 8 | /** 9 | * @return the name of the reloader 10 | */ 11 | String getName(); 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/type/RepositoryReloaderType.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.type; 2 | 3 | /** 4 | * Defines the Repository reloader type 5 | * 6 | * A Entity class must support org.springframework.stereotype.Repository classes 7 | */ 8 | public final class RepositoryReloaderType implements ReloaderType { 9 | 10 | private static final String name = "repositories"; 11 | public static final RepositoryReloaderType instance = new RepositoryReloaderType(); 12 | 13 | @Override 14 | public String getName() { 15 | return name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/type/RestDtoReloaderType.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.type; 2 | 3 | /** 4 | * Defines the REST DTO reloader type 5 | * 6 | * A REST DTO class must be located inside the package defined 7 | * by the configuration property named hotReload.package.restdto 8 | */ 9 | public final class RestDtoReloaderType implements ReloaderType { 10 | 11 | private static final String name = "dtos"; 12 | public static final RestDtoReloaderType instance = new RestDtoReloaderType(); 13 | 14 | @Override 15 | public String getName() { 16 | return name; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/type/ServiceReloaderType.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.loaded.reloader.type; 2 | 3 | /** 4 | * Defines the Service reloader type 5 | * 6 | * A Entity class must support org.springframework.stereotype.Service classes 7 | */ 8 | public final class ServiceReloaderType implements ReloaderType { 9 | 10 | private static final String name = "services"; 11 | public static final ServiceReloaderType instance = new ServiceReloaderType(); 12 | 13 | @Override 14 | public String getName() { 15 | return name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/java/io/github/jhipster/loaded/reloader/type/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Hot reloading reloader type with Spring Loaded. 3 | */ 4 | package io.github.jhipster.loaded.reloader.type; -------------------------------------------------------------------------------- /core/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | # Auto Configure 2 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.github.jhipster.loaded.JHipsterReloaderAutoConfiguration -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | org.sonatype.oss 5 | oss-parent 6 | 9 7 | 8 | 9 | 4.0.0 10 | io.github.jhipster.loaded 11 | jhipster 12 | 0.13-SNAPSHOT 13 | pom 14 | 15 | 16 | 17 | UTF-8 18 | UTF-8 19 | 1.7 20 | 1.7 21 | 1.7 22 | 23 | 1.2.1.BUILD-SNAPSHOT 24 | 4.0.7.RELEASE 25 | 1.5.0.RELEASE 26 | 1.4.1.RELEASE 27 | 1.1.6.RELEASE 28 | 1.0.0.Final 29 | 30 | 3.0.1 31 | 3.18.0-GA 32 | 2.3.1 33 | 3.2.0 34 | 3.4 35 | 2.6 36 | 2.4 37 | 1.7.6 38 | 39 | github 40 | 41 | 42 | 43 | common 44 | core 45 | agent 46 | 47 | 48 | 49 | 50 | spring-milestones 51 | Spring Milestones 52 | http://repo.spring.io/milestone 53 | 54 | false 55 | 56 | 57 | 58 | spring-snapshots-local 59 | Spring Snapshots Local 60 | http://repo.spring.io/libs-snapshot-local/ 61 | 62 | true 63 | 64 | 65 | 66 | 67 | 68 | 3.0.0 69 | 70 | 71 | 72 | 73 | org.springframework 74 | springloaded 75 | ${springloaded.version} 76 | 77 | 78 | org.springframework 79 | spring-context 80 | ${spring.version} 81 | provided 82 | 83 | 84 | org.springframework 85 | spring-webmvc 86 | ${spring.version} 87 | provided 88 | 89 | 90 | org.springframework 91 | spring-orm 92 | ${spring.version} 93 | provided 94 | 95 | 96 | org.springframework 97 | spring-web 98 | ${spring.version} 99 | provided 100 | 101 | 102 | org.springframework.data 103 | spring-data-jpa 104 | ${spring-data-jpa.version} 105 | provided 106 | 107 | 108 | org.springframework.data 109 | spring-data-mongodb 110 | ${spring-data-mongodb.version} 111 | provided 112 | 113 | 114 | org.springframework.boot 115 | spring-boot-autoconfigure 116 | ${spring-boot.version} 117 | provided 118 | 119 | 120 | org.hibernate.javax.persistence 121 | hibernate-jpa-2.1-api 122 | ${hibernate-jpa.version} 123 | provided 124 | 125 | 126 | javax.inject 127 | javax.inject 128 | 1 129 | provided 130 | 131 | 132 | org.javassist 133 | javassist 134 | ${javassist.version} 135 | provided 136 | 137 | 138 | javax.servlet 139 | javax.servlet-api 140 | ${servlet-api.version} 141 | provided 142 | 143 | 144 | com.fasterxml.jackson.datatype 145 | jackson-datatype-json-org 146 | ${jackson.version} 147 | provided 148 | 149 | 150 | commons-lang 151 | commons-lang 152 | ${commons-lang.version} 153 | provided 154 | 155 | 156 | commons-io 157 | commons-io 158 | ${commons-io.version} 159 | provided 160 | 161 | 162 | org.liquibase 163 | liquibase-core 164 | ${liquibase.version} 165 | provided 166 | 167 | 168 | org.liquibase.ext 169 | liquibase-hibernate4 170 | ${liquibase-hibernate4.version} 171 | provided 172 | 173 | 174 | org.hibernate.javax.persistence 175 | hibernate-jpa-2.0-api 176 | 177 | 178 | 179 | 180 | 181 | 182 | scm:git:git://github.com/jhipster/jhipster-loaded.git 183 | scm:git:git@github.com:jhipster/jhipster-loaded.git 184 | http://github.com/jhipster/jhipster-loaded 185 | HEAD 186 | 187 | 188 | 189 | 190 | ossrh 191 | https://oss.sonatype.org/content/repositories/snapshots 192 | 193 | 194 | 195 | 196 | 197 | release 198 | 199 | 200 | 201 | org.apache.maven.plugins 202 | maven-compiler-plugin 203 | 3.1 204 | 205 | ${java.version} 206 | ${java.version} 207 | 208 | 209 | 210 | org.sonatype.plugins 211 | nexus-staging-maven-plugin 212 | 1.6 213 | true 214 | 215 | ossrh 216 | https://oss.sonatype.org/ 217 | 218 | 219 | 220 | org.apache.maven.plugins 221 | maven-source-plugin 222 | 2.2.1 223 | 224 | 225 | attach-sources 226 | 227 | jar-no-fork 228 | 229 | 230 | 231 | 232 | 233 | org.apache.maven.plugins 234 | maven-javadoc-plugin 235 | 2.9.1 236 | 237 | 238 | attach-javadocs 239 | 240 | jar 241 | 242 | 243 | 244 | 245 | 246 | org.apache.maven.plugins 247 | maven-gpg-plugin 248 | 1.5 249 | 250 | 251 | sign-artifacts 252 | verify 253 | 254 | sign 255 | 256 | 257 | 258 | 259 | 260 | org.apache.maven.plugins 261 | maven-release-plugin 262 | 2.4.2 263 | 264 | true 265 | false 266 | release 267 | true 268 | deploy 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | --------------------------------------------------------------------------------