I don't want to install the full mongodb, so I created a simple spring-boot application with the following pom:
<!-- This dependency is to have an embedded mongodb -->
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>1.50.5</version>
</dependency>
<!-- while this provides a spring factory bean for the embedded mongodb -->
<dependency>
<groupId>cz.jirutka.spring</groupId>
<artifactId>embedmongo-spring</artifactId>
<version>RELEASE</version>
</dependency>
<!-- finally this one is the spring-boot starter for mongodb -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>${spring.boot.version}</version>
</dependency>
It works fine, but on the application shutdown I lose all the data stored.
I noticed that the db is created inside of a temporary folder under
C:\Users\myUser\AppData\Local\Temp\embedmongo-db-78beadc3-fa16-4101-afb1-ea7496f6f90c and, every time the application is restarted, another folder with different ID is created at the same location.
So, would it be possible to specify where the DB should be created so to keep the existing one and not lose all the data? Like you would do with h2 or sqllite?
Now it's possible see it.
Next code just shows my solution to implement it.
public class MongoInMemory {
private int port;
private String host;
private MongodProcess process = null;
public MongoInMemory(int port, String host){
this.port = port;
this.host = host;
}
#PostConstruct
public void init() throws IOException {
Storage storage = new Storage(
System.getProperty("user.home") + "/.ttraining-storage", null, 0);
IRuntimeConfig runtimeConfig = new RuntimeConfigBuilder()
.defaults(Command.MongoD)
.artifactStore(new ExtractedArtifactStoreBuilder()
.defaults(Command.MongoD)
.download(new DownloadConfigBuilder()
.defaultsForCommand(Command.MongoD).build())
.executableNaming(new UserTempNaming()))
.build();
IMongodConfig mongodConfig = new MongodConfigBuilder()
.version(Version.Main.PRODUCTION)
.net(new Net(host, port, false))
.replication(storage)
.build();
MongodStarter runtime = MongodStarter.getInstance(runtimeConfig);
process = runtime.prepare(mongodConfig).start();
}
#PreDestroy
public void stop(){
process.stop();
}
}
In configuration class define that as a bean
#Bean
public MongoInMemory mongoInMemory(
#Value("${spring.data.mongodb.port}") int port,
#Value("${spring.data.mongodb.host}") String host) {
return new MongoInMemory(port, host)
}
At last, remove embedded mongodb autoconfiguration in your entry point
#SpringBootApplication(exclude = EmbeddedMongoAutoConfiguration.class)
PS: Remove from dependecies cz.jirutka.spring:embedmongo-spring
PSS: Tested on Spring-boot 2.0.0.RELEASE
PSSS: Also you can define path as a property in application.properties and also it in constructor
The easiest way is to set the database-dir and oplog-size of the embedded mongo-db in the application.properties as described here: http://sahits.ch/blog/openpatrician/2017/05/20/embedded-databases-for-statistic-storage/
spring.mongodb.embedded.storage.database-dir=your-dir-name
spring.mongodb.embedded.storage.oplog-size=0
I needed this as well and it works like a charm. Tested with spring-boot 2.2.5.RELEASE and de.flapdoodle.embed.mongo 2.2.0
Related
I have a spring boot application running a feature. I want to toggle that feature(on/off) at runtime without redeploying or restarting the application. Issue is that I can't deploy any rest endpoint as server has only exposed some specific port because of security.
I want to remotely control the toggle so that I can set that feature on and off. I tried reading the environment variable on my local machine using:
System.getEnv("envVariable")
but even after updating it using export envVariable=true it's not reflecting updated value in the code.
Can someone suggest any way to achieve this ?
Thanks,
To do this you need some more dependencies.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
in properties file you need to write
management.endpoints.web.exposure.include=*
and on the class wherever you are are using environment variables use Annotation #RefreshScope like
import org.springframework.web.bind.annotation.RestController;
import org.springframework.cloud.context.config.annotation.RefreshScope;
#RefreshScope
#RestController
public class DemoController {
#Value("${my.data}")
String str;
// code
}
and whenever you are changing environment variable just hit a post request http://localhost:PORT/actuator/refresh
using above configuration you can change the environment variables.
There is a programming pattern Feature Toggle that provides a way to turn on/off application components during the runtime. The core idea is to ask property files or database config table about current states of config fields and change application functionality if config changed. This pattern described here https://martinfowler.com/bliki/FeatureToggle.html. You can find more by using keyword "Feature Flags".
One of popular implementations of Feature Flags for Java is togglz (https://www.togglz.org/quickstart.html).
Here is an exaple of using togglz:
Create enum for features representation
public enum MyFeatures implements Feature {
#EnabledByDefault
#Label("First Feature")
FEATURE_ONE,
#Label("Second Feature")
FEATURE_TWO;
public boolean isActive() {
return FeatureContext.getFeatureManager().isActive(this);
}
Implement TogglzConfig
#ApplicationScoped
public class DemoConfiguration implements TogglzConfig {
public Class<? extends Feature> getFeatureClass() {
return MyFeatures.class;
}
public StateRepository getStateRepository() {
return new FileBasedStateRepository(new File("/tmp/features.properties"));
}
public UserProvider getUserProvider() {
return new ServletUserProvider("admin");
}
}
Describe the feature behavior depended on toggle:
if( MyFeatures.FEATURE_ONE.isActive() ) {
// new stuff here
}
Source: https://www.togglz.org/quickstart.html
I created a simple spring boot app to retrieve secrets from keyvault.
I added the following dependency to work around with,
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>azure-spring-boot-starter-keyvault-secrets</artifactId>
<version>3.5.0</version>
</dependency>
and added the following in application.properties
azure.keyvault.enabled=true
azure.keyvault.uri=<URL>
#keys
mySecretProperty=secret
and my main application,
#SpringBootApplication
public class KeyVaultSample implements CommandLineRunner {
#Value("${mySecretProperty}")
private String mySecretProperty;
public static void main(String[] args) {
SpringApplication.run(KeyVaultSample.class, args);
}
#Override
public void run(String... args) {
System.out.println("property your-property-name value is: " + mySecretProperty);
}
}
But every time I tried to run the above app on local, it tries to use ManagedIdentityCredential to connect. So I added a configuration class for creating a bean for SecretClient with AzureCliCredential, but then too, the results are the same.
My Configuration class,
#Configuration
public class AppConfiguration {
#Bean
public SecretClient secretClient() {
AzureCliCredential az = new AzureCliCredentialBuilder().build();
SecretClient sec = new SecretClientBuilder().vaultUrl("<url>")
.credential(az).buildClient();
return sec;
}
}
I'm looking for ways I could use/test this keyvault on my local.
Is there any configuration I could put in the properties file which would make it use AzureCliCredential instead of ManagedIdentityCredential?
azure-spring-boot-starter-keyvault-secrets uses MSI / Managed identities.
If you would like to authenticate with Azure CLI, just use azure-identity and azure-security-keyvault-secrets.
public void getSecretWithAzureCliCredential() {
AzureCliCredential cliCredential = new AzureCliCredentialBuilder().build();
// Azure SDK client builders accept the credential as a parameter
SecretClient client = new SecretClientBuilder()
.vaultUrl("https://{YOUR_VAULT_NAME}.vault.azure.net")
.credential(cliCredential)
.buildClient();
KeyVaultSecret secret = secretClient.getSecret("<secret-name>");
System.out.printf("Retrieved secret with name \"%s\" and value \"%s\"%n", secret.getName(), secret.getValue());
}
If you don't necessarily need the real thing in local (a test double can be fine instead of Azure Key Vault) you could try Lowkey Vault too! It supports keys and secrets using a local container and you can fake the authentication using a simple basic auth.
Project home: https://github.com/nagyesta/lowkey-vault
Java POC (although not using the Spring Boot starter): https://github.com/nagyesta/lowkey-vault-example
I am upgrading Spring Boot from 1.3 to 1.5. For upgrading to 1.5 I have replaced
#SpringApplicationConfiguration(classes = TestConfig.class)
#WebIntegrationTest
with
#SpringBootTest(classes = TestConfig.class)
Also, I am using
#Value("${local.server.port}")
protected int port;
to get port number defined in application.properties file. I further use this port number to build a REST URL.
But after the upgrade I am getting the error below whereas the same works fine with 1.3 Spring Boot Test.
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'local.server.port' in value "${local.server.port}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174)
Am I missing any changes that I need to do for this to work.
You have to provide a value for webEnvironment. In your case DEFINED_PORT like this
#SpringBootTest(classes = App.class, webEnvironment = WebEnvironment.DEFINED_PORT)
public class YourTest {
#LocalServerPort // shorthand for #Value("${local.server.port}")
private Integer port;
...
}
For details see: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications
Adding another alternate solution which I had elsewhere.
I had configured
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
and
#RunWith(SpringRunner.class)
#SpringBootTest(classes = App.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class YourTest {
#LocalServerPort // shorthand for #Value("${local.server.port}")
private Integer port;
...
}
Thinking that was it, and still getting this error even when specifying web environment etc. My ${local.server.port} seemed to be always null.
After some time, I noticed that my Spring Boot startup message contained no notion of the port it was using, so apparently it really didn't listen to any port at all - which explained why it was null in the first place. Adding actual container implementation dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Caused this to appear on my logs:
2019-02-26 18:45:47.231 INFO 12504 --- [ main] o.s.b.web.embedded.jetty.JettyWebServer : Jetty started on port(s) 43132 (http/1.1) with context path '/'
after which local.server.port and #LocalServerPort would also work.
For me the problem was that there was alternative #Configuration class(es) in my other test(s) like this:
#Configuration
public class ReadPropertiesConfiguration {
#Bean
PropertyPlaceholderConfigurer propConfig() {
PropertyPlaceholderConfigurer placeholderConfigurer = new PropertyPlaceholderConfigurer();
placeholderConfigurer.setLocation(new ClassPathResource("application.properties"));
return placeholderConfigurer;
}
}
and #SpringBootApplication of the app was picking that up due to its #ComponentScan, and for some reason it resulted in this problem. When adding exclusion for those and/or replacing them with other solutions things started again to work without problems.
I don't know the root cause why this happens, but that might be your issue as well.
First make sure the property is correctly spelled in the properties file. As i did just few days back using spring-boot-1.5.2 & it works.
Or
You need to add
#PropertySource("classpath:application.properties")
to your class, so it will pick your configurations.
If you need different configurations for test you can add
#TestPropertySource(locations="classpath:test.properties")
Refer #Value not work on Spring Boot Test
I am trying to programmatically restart my spring boot application end point. Below is the lines I have used.
public class FileWatcher {
#Autowired
private RestartEndpoint restartEndpoint;
public void onFileChange() {
Thread restartThread = new Thread(() -> restartEndpoint.restart());
restartThread.setDaemon(false);
restartThread.start();
}
}
But it throws the below error.
Error:(32, 64) java: cannot access org.springframework.boot.actuate.endpoint.AbstractEndpoint
class file for org.springframework.boot.actuate.endpoint.AbstractEndpoint not found
What am I doing wrong here? Any help would be much appreciated.
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready
Add the actuator dependencies, in maven:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
For Gradle, use the declaration:
dependencies {
compile("org.springframework.boot:spring-boot-starter-actuator")
}
I've got a working Spring Boot Elasticsearch Application which uses one of two profiles: application.dev.properties or application.prod.properties. That part works fine. I am having issue with getting the external elasticsearch to read from the application.xxx.properties.
This works:
#Configuration
#PropertySource(value = "classpath:config/elasticsearch.properties")
public class ElasticsearchConfiguration {
#Resource
private Environment environment;
#Bean
public Client client() {
TransportClient client = new TransportClient();
TransportAddress address = new InetSocketTransportAddress(
environment.getProperty("elasticsearch.host"),
Integer.parseInt(environment.getProperty("elasticsearch.port"))
);
client.addTransportAddress(address);
return client;
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchTemplate(client());
}
}
but obviously doesn't solve my multi-environment issue.
I've also tried #Value annotations for host and port variables without success.
How can I convert the above to read its values from the application properties file or choose a different #PropertySource file based on whichever profile I want to run?
spring.data.elasticsearch.properties.host = 10.10.1.10
spring.data.elasticsearch.properties.port = 9300
Thanks
Remove your configuration class and properties.
Add the following dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
Just add the spring.data.elasticsearch properties to an application-prod.properties and application-dev.properties and change for the desired environment. This is described in the ElasticSearch section of the Spring Boot guide.
spring.data.elasticsearch.cluster-nodes=10.10.1.10:9300
The value in either file will of course differ (or put the default in the application.properties and simply override with an application-dev.properties.
Spring Boot will based on the spring.profiles.active load the desired properties file.
There is no need to hack around yourself.
I agree with Deinum, if you are using Spring boot it will get the properties from the active profile active.
I have different profiles in my project and this is my elasticsearch configuration:
#Configuration
public class ElasticSearchConfiguration {
#Value("${spring.data.elasticsearch.cluster-name}")
private String clusterName;
#Value("${spring.data.elasticsearch.cluster-nodes}")
private String clusterNodes;
#Bean
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
String server = clusterNodes.split(":")[0];
Integer port = Integer.parseInt(clusterNodes.split(":")[1]);
Settings settings = Settings.settingsBuilder()
.put("cluster.name", clusterName).build();
client = TransportClient.builder().settings(settings).build()
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(server), port));
return new ElasticsearchTemplate(client);
}