So at first I added a properties file with:
spring.data.mongodb.uri=mongodb://axon:axon#aurl:27017/axonframework
which works but I was forced to use axonframework as db name because it is what was created in my mongo db.
Now controlling the db name and other details isn't an option in this case, so I went and checked around and found the following:
#configuration
public class AxonConfiguration {
#Value("${mongo.host:127.0.0.1}")
private String mongoHost;
#Value("${mongo.port:27017}")
private int mongoPort;
#Value("${mongo.db:test}")
private String mongoDB;
#Bean
public MongoSagaStore sagaStore() {
return new MongoSagaStore(axonMongoTemplate());
}
#Bean
public TokenStore tokenStore(Serializer serializer) {
return new MongoTokenStore(axonMongoTemplate(), serializer);
}
#Bean
public EventStorageEngine eventStorageEngine(Serializer serializer) {
return new MongoEventStorageEngine(serializer, null, axonMongoTemplate(), new DocumentPerEventStorageStrategy());
}
#Bean
public MongoTemplate axonMongoTemplate() {
return new DefaultMongoTemplate(mongo(), mongoDB);
}
#Bean
public MongoClient mongo() {
MongoFactory mongoFactory = new MongoFactory();
mongoFactory.setMongoAddresses(Collections.singletonList(new ServerAddress(mongoHost, mongoPort)));
return mongoFactory.createMongo();
}
}
Now apparently this worked for people but what I'm not being able to get right is how am I supposed to set the username and password?
I'm using axon 4.1, axonframework.extensions.mongo 4.1
The snippet of code you share does not correspond with Axon Framework release 4.x or Axon Mongo Extension release 4.x. The shift from version 3 to 4 has replaced almost all constructors of the infrastructure components in favor of the Builder pattern.
As such, you should not be able to do new MongoEventStorageEngine(...), but instead should do:
MongoEventStorageEngine.builder().mongoTemplate(axonMongoTemplate).build()
If you're still able to use the constructor, I assume you still have Axon 3 somewhere on the class path!
Regarding the Mongo specifics, I'd trust #PolishCivil's statement by the way.
Hope this helps!
This issue is not really related to the axon itself but more likely to the spring configuration of the mongo client instance since the usage of mongo is just an extension over the axon framework.
AFAIK it's
spring.data.mongodb.password
and
spring.data.mongodb.username
Also theres one thing in the code you should consider changing
return new DefaultMongoTemplate(mongo(), mongoDB);
You call the method that is specified as a bean, so instead in spring you should just wire it to your method parameter like so :
public MongoTemplate axonMongoTemplate(MongoClient client) {
return new DefaultMongoTemplate(client, mongoDB);
}
Related
I'm trying to achieve something like this:
#Controller
public SomeController {
#CustomConfig("var.a")
private String varA;
#CustomConfig("var.b")
private String varB;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String get() {
return varA;
}
}
CustomConfig would be an #Interface class that accepts one value parameter. The reason why we are not using #Value is because this will not come from config file but from API (such as https://getconfig.com/get?key=var.a). So we are going to make HTTP request to inject it.
So far I've only manage to make something work if the varA and varB is inside get() method as parameter, by using below in a class that extends WebMvcConfigurerAdapter:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
CustomConfigResolver resolver = new CustomConfigResolver();
argumentResolvers.add(resolver);
}
And inside CustomComfigResolver.resolveArgument() we would do the HTTP query, but that's not really what we wanted, we need it to be injected as class variable.
Does anyone have experience in resolving it at class variable level?
Thank you
This could work if you use #Value instead of your own custom annotation. This uses the built in environment:
#Order(Ordered.HIGHEST_PRECEDENCE)
#Configuration
public class TcpIpPropertySourceConfig implements InitializingBean {
#Autowired
private ConfigurableEnvironment env;
#Autowired
private RestTemplate rest;
public void afterPropertiesSet() {
// Call your api using Resttemplate
RemoteProperties props = //Rest Call here;
// Add your source to the environment.
MutablePropertySources sources = env.getPropertySources();
sources.addFirst(new PropertiesPropertySource("customSourceName", props)
}
}
What you are trying to achieve is difficult when you start to consider "unhappy" scenarios. Server down / not reachable. You need to account for all of that in the method above.
I would highly recommend to instead use Spring Cloud Config. Great guide on that is here: https://www.baeldung.com/spring-cloud-configuration
This provides:
- Reloading of your #Value() properties, so no custom annotation needed.
- A more stable server and great Spring integration out of the box.
Best of all, it is easy to apply Retries and Backoffs if the configuration server goes down (see https://stackoverflow.com/a/44203216/2082699). This will make sure your app doesn't just crash when the server is not available.
I am having issues setting up ACL through Java config in a Spring Boot application. I have created one small project to reproduce the issues.
I have tried a few different approaches. First issue I had was with EhCache, and after I fixed that (I assume I did) I couldn't login any more, and it looks like all the data is gone.
There are 4 classes with different configurations:
ACLConfig1.class
ACLConfig2.class
ACLConfig3.class
ACLConfig4.class
All #PreAuthorize and #PostAuthorize annotations are working as expected, except hasPermission.
Controller holds 4 endpoints: one for User, one for Admin, one Public and the last one which gives me headache #PostAuthorize("hasPermission(returnObject,'administration')")
I am pretty sure that inserts in DB are correct. This class is one of four, the last one that I have tried:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ACLConfig4 {
#Autowired
DataSource dataSource;
#Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy());
}
#Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
#Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
#Bean
public DefaultPermissionGrantingStrategy permissionGrantingStrategy() {
ConsoleAuditLogger consoleAuditLogger = new ConsoleAuditLogger();
return new DefaultPermissionGrantingStrategy(consoleAuditLogger);
}
#Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMINISTRATOR"));
}
#Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
#Bean
public JdbcMutableAclService aclService() {
JdbcMutableAclService service = new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
return service;
}
#Bean
public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
return new DefaultMethodSecurityExpressionHandler();
}
#Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = defaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
return expressionHandler;
}
}
What am I missing here? Why I have no data if I use ACLConfig3.class or
ACLConfig4.class. Is there any example on how this should be configured programmatically in Spring Boot?
The reason why you have no data was a bit tricky to find out. As soon as you define a MethodSecurityExpressionHandler bean in your config, there is no data in the database tables. This is because your data.sql file isn't executed.
Before explaining why data.sql isn't executed I'd first like to point out that you're not using the file as intended.
data.sql is executed by spring-boot after hibernate has been initialized and normally only contains DML statements. Your data.sql contains both DDL (schema) statements and DML (data) statements. This isn't ideal as some of your DDL statements clash with hibernate's hibernate.hbm2ddl.auto behaviour (note that spring-boot uses 'create-drop' when an embedded DataSource is being used). You should put your DDL statements in schema.sql and your DML statements in data.sql. As you're manually defining all tables you should disable hibernate.hbm2ddl.auto (by adding spring.jpa.hibernate.ddl-auto=none to applciation.properties).
That being said, let's take a look at why data.sql isn't executed.
The execution of data.sql is triggered via an ApplicationEvent that's fired via a BeanPostProcessor. This BeanPostProcessor (DataSourceInitializedPublisher) is created as a part of spring-boot's Hibernate/JPA auto configuration (see org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher and org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer).
Normally the DataSourceInitializedPublisher is created before the (embedded) DataSource is created and everything works as expected but by defining a custom MethodSecurityExpressionHandler the normal bean creation order alters.
As you've configured #EnableGlobalMethodSecurity, your're automatically importing GlobalMethodSecurityConfiguration.
spring-security related beans are created early on. As your MethodSecurityExpressionHandler requires a DataSource for the ACL stuff and the spring-security related beans require your custom MethodSecurityExpressionHandler, the DataSource is created earlier than usual; in fact it's created so early on that spring-boot's DataSourceInitializedPublisher isn't created yet.
The DataSourceInitializedPublisher is created later on but as it didn't notice the creation of a DataSource bean, it also doesn't trigger the execution of data.sql.
So long story short: the security configuration alters the normal bean creation order which results in data.sql not being loaded.
I guess that fixing the bean creation order would do the trick, but as I don't now how (without further experimentation) I propose the following solution: manually define your DataSource and take care of data initialization.
#Configuration
public class DataSourceConfig {
#Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
//as your data.sql file contains both DDL & DML you might want to rename it (e.g. init.sql)
.addScript("classpath:/data.sql")
.build();
}
}
As your data.sql file contains all DDL required by your application you can disable hibernate.hbm2ddl.auto. Add spring.jpa.hibernate.ddl-auto=none to applciation.properties.
When defining your own DataSource spring-boot's DataSourceAutoConfiguration normally back's out but if you want to be sure you can also exclude it (optional).
#SpringBootConfiguration
#EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
#ComponentScan
#EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
This should fix your 'no data' problem. But in order to get everything working as expected you need to make 2 more modifications.
First of all, you should only define one MethodSecurityExpressionHandler bean. Currently you're defining 2 MethodSecurityExpressionHandler beans. Spring-security won't know which one to use and will (silently) use it's own internal MethodSecurityExpressionHandler instead. See org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration#setMethodSecurityExpressionHandler.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MyACLConfig {
//...
#Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler securityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
securityExpressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
securityExpressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
return securityExpressionHandler;
}
}
The last thing you need to do is make the getId() method in Car public.
#Entity
public class Car {
//...
public long getId() {
return id;
}
//...
}
The standard ObjectIdentityRetrievalStrategy will look for a public method 'getId()' when trying to determine an object's identity during ACL permission evaluation.
(Note that I've based my answer upon ACLConfig4.)
I have configured Spring Mongodb project to load data in database named "warehouse". Here is how my config class looks like
#Configuration
public class SpringMongoConfig extends AbstractMongoConfiguration {
#Override
protected String getDatabaseName() {
return "warehouse";
}
public #Bean Mongo mongo() throws Exception {
return new Mongo("localhost");
}
public #Bean MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongo(), getDatabaseName());
}
}
But Spring is always using the default database "test" to store and retrieve the collections. I have tried different approaches to point it to "warehouse" db. But it doesnt seem to work. What am doing wrong? Any leads are appreciated.
Assuming you have a standard mongo install (e.g., the database is at a default such as /data/db or C:\data\db), your configuration class looks correct. How are you using it? Can you try:
SpringMongoConfig config = new SpringMongoConfig();
MongoTemplate template = config.mongoTemplate();
template.createCollection("someCollection");
From a shell, if you then log into mongo and enter show dbs, do you not see a warehouse"?
Is there any way to load a class marked with #ConfigurationProperties without using a Spring Context directly? Basically I want to reuse all the smart logic that Spring does but for a bean I manually instantiate outside of the Spring lifecycle.
I have a bean that loads happily in Spring (Boot) and I can inject this into my other Service beans:
#ConfigurationProperties(prefix="my")
public class MySettings {
String property1;
File property2;
}
See the spring docco for more info http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-external-config-command-line-args
But now I need to access this bean from a class that is created outside of Spring (by Hibernate). The class is created so early in the app startup process that Spring Boot has not yet made the application context available through the classic lookup helper methods or roll-my-own static references.
So I instead want to do something like:
MySettings mySettings = new MySettings();
SpringPropertyLoadingMagicClass loader = new SpringPropertyLoadingMagicClass();
loader.populatePropertyValues(mySettings);
And have MySettings end up with all its values loaded, from the command line, system properties, app.properties, etc. Is there some class in Spring that does something like this or is it all too interwoven with the application context?
Obviously I could just load the Properties file myself, but I really want to keep Spring Boot's logic around using command line variables (e.g. --my.property1=xxx), or system variables, or application.properties or even a yaml file, as well as its logic around relaxed binding and type conversion (e.g. property2 is a File) so that it all works exactly the same as when used in the Spring context.
Possible or pipe dream?
Thanks for your help!
I had the same "issue".
Here is how I solved it in SpringBoot version 1.3.xxx and 1.4.1.
Let's say we have the following yaml configuration file:
foo:
apis:
-
name: Happy Api
path: /happyApi.json?v=bar
-
name: Grumpy Api
path: /grumpyApi.json?v=grrr
and we have the following ConfigurationProperties:
#ConfigurationProperties(prefix = "foo")
public class ApisProperties {
private List<ApiPath> apis = Lists.newArrayList();
public ApisProperties() {
}
public List<ApiPath> getApis() {
return apis;
}
public static class ApiPath {
private String name;
private String path;
public String getName() {
return name;
}
public void setName(final String aName) {
name = aName;
}
public String getPath() {
return path;
}
public void setPath(final String aPath) {
path = aPath;
}
}
}
Then, to do the "magic" things of Spring Boot programmatically (e.g. loading some properties in a static method), you can do:
private static ApisProperties apiProperties() {
try {
ClassPathResource resource;
resource = new ClassPathResource("/config/application.yml");
YamlPropertiesFactoryBean factoryBean;
factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setSingleton(true); // optional depends on your use-case
factoryBean.setResources(resource);
Properties properties;
properties = factoryBean.getObject();
MutablePropertySources propertySources;
propertySources = new MutablePropertySources();
propertySources.addLast(new PropertiesPropertySource("apis", properties));
ApisProperties apisProperties;
apisProperties = new ApisProperties();
PropertiesConfigurationFactory<ApisProperties> configurationFactory;
configurationFactory = new PropertiesConfigurationFactory<>(apisProperties);
configurationFactory.setPropertySources(propertySources);
configurationFactory.setTargetName("foo"); // it's the same prefix as the one defined in the #ConfigurationProperties
configurationFactory.bindPropertiesToTarget();
return apisProperties; // apiProperties are fed with the values defined in the application.yaml
} catch (BindException e) {
throw new IllegalArgumentException(e);
}
}
Here's an update to ctranxuan's answer for Spring Boot 2.x. In our situation, we avoid spinning up a Spring context for unit tests, but do like to test our configuration classes (which is called AppConfig in this example, and its settings are prefixed by app):
public class AppConfigTest {
private static AppConfig config;
#BeforeClass
public static void init() {
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setResources(new ClassPathResource("application.yaml"));
Properties properties = factoryBean.getObject();
ConfigurationPropertySource propertySource = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(propertySource);
config = binder.bind("app", AppConfig.class).get(); // same prefix as #ConfigurationProperties
}
}
The "magic" class you are looking for is PropertiesConfigurationFactory. But I would question your need for it - if you only need to bind once, then Spring should be able to do it for you, and if you have lifecycle issues it would be better to address those (in case they break something else).
This post is going into similar direction but extends the last answer with also validation and property placeholder resolutions.
Spring Boot Binder API support for #Value Annotations
#Value annotations in ConfigurationPropertys don't seem to bind properly though (at least if the referenced values are not part of the ConfigurationProperty's prefix namespace).
import org.springframework.boot.context.properties.bind.Binder
val binder = Binder.get(environment)
binder.bind(prefix, MySettings.class).get
I am connecting to a MongoDB sharding server via mongodb java driver in Spring MVC. I am using the following versions:
spring-webmvc-3.2.1.RELEASE
mongo-java-driver/2.10.0/mongo-java-driver-2.10.0
spring-data-mongodb-1.2.0.RELEASE
My Mongo options are set in the contextConfigLocation file mvc-dispatcher-servlet.xml
<mongo:mongo host="mongo.sample.com" port="30000">
<mongo:options auto-connect-retry="true"
slave-ok="true"/>
</mongo:mongo>
It works pretty well, but the slave-ok is deprecated by come.MongoDB.ReadPreference. I just wonder if there is any way to set the readPreference for Spring MVC in the contextConfiLocation file.
Declare the following bean
<bean id="readPreferenceSecondary" class="com.mongodb.TaggableReadPreference.SecondaryReadPreference">
</bean>
and
you inject this in your mongotemplate
<bean id="mongoTemplateProdDb" class="org.springframework.data.mongodb.core.MongoTemplate" >
<property name="readPreference" ref="readPreferenceSecondary"></property>
</bean>
Expanding #Trisha's response in to an answer: "Do it in MongoTemplate programmatically" by setting the ReadPreference to SECONDARY.
MongoTemplate template = new MongoTemplate(...);
template.setReadPreference(com.mongodb.ReadPreference.SECONDARY);
In case you are using spring-data-mongodb and have some requirement to use multiple Read Preferences based on find query, you can create multiple Mongo Templates and/or Repositories like
#EnableMongoRepositories(basePackages = {
"com.you.repo.package" }, mongoTemplateRef = "mongoTemplateOne")
#Configuration
public class MongoConfig {
#Bean(name="mongoTemplateOne")
public MongoTemplate getMongoTemplateOne() throws UnknownHostException {
MongoTemplate templateOne = new MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI("YOUR_MONGO_URL")));
templateOne.setReadPreference(ReadPreference.secondaryPreferred());
//setting WriteConcern but not relevant for this thread
templateOne.setWriteConcernResolver(yourWriteConcernResolver());
return templateOne;
}
#Bean(name = "mongoTemplateTwo")
public MongoTemplate getMongoTemplateTwo() throws UnknownHostException {
MongoTemplate templateTwo = new MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI("YOUR_MONGO_URL")));
templateTwo.setReadPreference(ReadPreference.secondaryPreferred());
return templateTwo;
}
private WriteConcernResolver yourWriteConcernResolver() {
return action -> {
if (action.getCollectionName()
.equals("your_collecton")
&& (action.getMongoActionOperation() == MongoActionOperation.SAVE
|| action.getMongoActionOperation() == MongoActionOperation.UPDATE)) {
return WriteConcern.MAJORITY;
}
return action.getDefaultWriteConcern();
};
}
In case you have more than one secondary (replica-set) you can be more specific and tell the mongo driver explicitly which of the secondaries you want to read from, using tags.
On the mongo side you run this command:
db.getMongo().setReadPref('secondaryPreferred',
[{"tagName":"TagVal1"},
{"tagName":"TagVal2"},
{}])
In the code it looks like this:
MongoTemplate template = new MongoTemplate(...)
template.setReadPreference(ReadPreference.secondaryPreferred("your DBObject that reflect your mongo tag names");
Hope it helps.
Here is one more way to do this using Mongo Repositories
#Configuration
#EnableMongoRepositories
class ApplicationConfig extends AbstractMongoClientConfiguration {
#Autowired
private Environment env;
#Value("${spring.data.mongodb.uri}")
public String mongoUri;
#Override
protected String getDatabaseName() {
return env.getProperty("spring.data.mongodb.database");
}
#Override
protected void configureClientSettings(MongoClientSettings.Builder builder) {
builder.applyConnectionString(new ConnectionString(mongoUri)).readPreference(ReadPreference.secondary());
}
}
sample app available #
https://github.com/prashanthmadi/cosmosdb-mongodb-readpreference-springboot-java/blob/main/src/main/java/azure/cosmosdb/mongodb/spring/cosmosdbmongodb/ApplicationConfig.java
If there is a need to mix between primary and secondary for reads depending on the collection, you can set the ReadPreference on the DBCollection object. This helps to avoid complex multiple MongoTemplate configuration. Instead, set collection level preference like below once in the application lifetime. All the reads for that specific collection will go to secondary, while for other collections it goes to primary.
DBCollection dbCollection = mongoTemplate.getCollection(mongoTemplate.getCollectionName(collection));
dbCollection.setReadPreference(ReadPreference.secondaryPreferred());
If you want to know different options to achieve it, please check Spring data mongodb secondary reads
As of spring-mongo-2.0.xsd, slave-ok has been entirely removed, but support has been added for XML config of ReadPreference. Here's the XML from the original question translated for the current XSD:
<mongo:mongo-client host="mongo.sample.com" port="30000">
<mongo:client-options read-preference="SECONDARY_PREFERRED" />
</mongo:mongo-client>