Spring Data Elasticsearch #Document indexName defined at runtime - spring-mvc

Is it possible to specify dynamically (at runtime) the indexName for each #Document, for example, via a configuration file? Or is it possible to make #Document Spring environment (dev, prod) dependant?
Thank you!

The #Document annotation does not permit to pass the indexname in parameter directly. However I found a work around.
In my configuration class I created a Bean returning a string. In this string I injected the name of the index with #Value :
#Value("${etrali.indexname}")
private String indexName;
#Bean
public String indexName(){
return indexName;
}
Afterward it is possible to inject the index into the #Documentation annotation like this :
#Document(indexName="#{#indexName}",type = "syslog_watcher")
It works for me, I hope it will help you.
Best regards

The solution from Bruno probably works but the "I created a Bean returning a string" part is a bit confusing.
Here is how I do it :
Have the "index.name" key valued in an application.properties file loaded by "<context:property-placeholder location="classpath:application.properties" />"
Create a bean named ConfigBean annotated with #Named or #Component
#Named
public class ConfigBean {
#Value("${index.name}")
private String indexName;
public String getIndexName() {
return indexName;
}
public void setIndexName(String indexName) {
this.indexName = indexName;
}
}
Inject the value of configBean.getIndexName() into the "#Document" annotation using Spring EL : #Document(indexName = "#{ configBean.indexName }", type = "myType")
P.S. : You may achieve the same result directly using the implicit bean "systemProperties" (something like #{ systemProperties['index.name'] }) but it didn't work for me and it's pretty hard to debug since u can't resolve systemProperties in a programmatic context (https://jira.spring.io/browse/SPR-6651)

The Bruno's solution works but there is no need to create a new Bean in this way. What I do is:
create a bean annotated with #org.springframework.stereotype.Service where the index name is loaded from the database:
#Service
public class ElasticsearchIndexConfigService {
private String elasticsearchIndexName;
// some code to update the elasticsearchIndexName variable
public String getIndexName() {
return elasticsearchIndexName;
}
}
call the getIndexName() method from the bean in the #Document annotation using the SpEL:
#Document(indexName = "#{#elasticsearchIndexConfigService.getIndexName()}", createIndex = false)
public class MyEntity {
}
The crucial part is to use # - #{elasticsearchIndexConfigService.getIndexName()} won't work. I lost some time to figure this out.

Related

Can't use a session ejb in my managed bean cause i get a Null Pointer Exception

First of all I want to say I'm pretty new in programming with ejb and jsf, and I'm trying to complete a project started by a friend of mine.
I'm getting a NullPointerException caused by the invoke of the method utenteSessionBean.CheckUtentebyId(username) of the session bean object called utenteSessionBean, declared inside the managed bean called Neo4jMBean.
I learned that it's not necessary creating and initializing a session bean (as you must do with a normal java object) in managed bean, but it's enough declaring it.
Here is the code of the session bean, which retrieves data from a DB
#Stateless
#LocalBean
public class UtenteSessionBean {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("EnterpriseApplication2-ejbPU");
public boolean CheckUtentebyId(String username){
EntityManager em = emf.createEntityManager();
Query query = em.createNamedQuery("Utente.findByUsername");
query.setParameter("username", username);
List<Utente> Res=query.getResultList();
//completare funzione ctrl+spazio
System.out.println("pre");
System.out.println("pre"+Res.isEmpty());
em.close();
System.out.println("post");
System.out.println("post"+Res.isEmpty());
if(Res.size()>=1)
{
return true;
}
else
{
return false;
}
}
}
Here's the code of the managed bean:
#ManagedBean
#ViewScoped
public class Neo4jMBean {
#EJB
private UtenteSessionBean utenteSessionBean;
static String SERVER_ROOT_URI = "http://localhost:7474/db/data/";
public Neo4jMBean() {
}
public boolean getUser(String username) {
return utenteSessionBean.CheckUtentebyId(username);
}
}
I've searched on StackOverFlow many times a solution for fixing this problem, but I haven't found something that works for me yet.
I fixed it accessing the EJB Components using JNDI.
In few words, if i use an EJB in a managed bean method, i need to add the next lines of code:
InitialContext ic = new InitialContext();
SessionBeanName = (SessionBeanClass) ic.lookup("java:global/NameOfTheApplication/NameOfTheEJBpackage/NameOfTheSessionBean");
It must be surronded by a try-catch statement
Create empty beans.xml file in your WEB-INF folder to enable CDI

Instantiate DbContext-derived class with Mehdime.Entity AmbientDbContextLocator

can anyone tell me what I am doing wrong?
I am wanting to use Mehdime.Entity from https://www.nuget.org/packages/Mehdime.Entity in order to manage my DBContext-derived classes in a Console Application. I am also using NInject.
The connection strings for my DBContext-derived classes are partially generated from standard app.config ConnectionStrings and also by an AppDomain value that (in my Console App case) comes in via a command line argument.
My DBContext-derived classes have their connection strings prepared using a program-implemented class which takes into account of the command line argument as follows:
public class TaskManagementDbContext : DbContext
{
public TaskManagementDbContext(IConnectionStringResolver csr) :
base(csr.GetConnectionString("Default"))
{
}
}
(IConnectionStringResolver basically implements GetConnectionString() which returns the connection string by using given named standard app.config ConnectionString and the command line argument.
This is fine when I use NInject to instantiate the DbContext directly but when trying to use with Mehdime.Entity, it is AmbientDbContextLocator that is doing the instantiation and it throws a MissingMethodException because it requires my DBContext-derived class to have a parameterless constructor:
public class TaskRepository : ITaskRepository
{
private readonly IAmbientDbContextLocator _ambientDbContextLocator;
private TaskManagementDbContext DbContext
{
get
{
// MissingMethodException thrown "No parameterless constructor defined for this object"
var dbContext = _ambientDbContextLocator.Get<TaskManagementDbContext>();
...
}
}
How should I provide a connection string to my DBContext-derived classes at run-time in this situation? I suspect I am going about this the wrong way. Thanks.
OK. I've worked out the solution and I'm putting it here for anyone else with this issue:
Create your own implementation of IDbContextFactory (see below). I put this in the same class library as my Data Access Layer (i.e. my DbContexts). You will see in my example how I "look for" a specific constructor prototype (in my case, 1 parameter of type IDbContextFactory - your's will no doubt be different). If found, get the actual parameters and invoke a new instance of your DBContext-derived class. If not found, you can throw an exception or in my case, try to call the default constructor (if exists).
Code:
using System;
using System.Data.Entity;
using Mehdime.Entity;
using Ninject;
using TaskProcessor.Common;
namespace TaskProcessor.Data.Connection
{
public class DbContextWithCSRFactory : IDbContextFactory
{
public TDbContext CreateDbContext<TDbContext>() where TDbContext : DbContext
{
// Try to locate a constuctor with a single IConnectionStringResolver parameter...
var ci = typeof(TDbContext).GetConstructor(new[] { typeof(IConnectionStringResolver) });
if(ci != null)
{
// Call it with the actual parameter
var param1 = GlobalKernel.Instance.Get<IConnectionStringResolver>();
return (TDbContext)ci.Invoke(new object[] { param1 });
}
// Call parameterless constuctor instead (this is the default of what DbContextScope does)
return (TDbContext)Activator.CreateInstance<TDbContext>();
}
}
}
Create a binding in NInject so that your IDbContextFactory implementation is called:
Code:
private void AddBindings(IKernel kernel)
{ ...
kernel.Bind<IDbContextFactory>().To<Data.Connection.DbContextWithCSRFactory>().InSingletonScope();
}
Everything now falls into place.

Is there a way to customize the ObjectMapper used by Spring MVC without returning String?

I have a graph of objects that I'd like to return different views of. I don't want to use Jackson's #JsonViews to implement this. Right now, I use Jackson MixIn classes to configure which fields are shown. However, all my rest methods return a String rather than a type like BusinessCategory or Collection< BusinessCategory >. I can't figure out a way to dynamically configure the Jackson serializer based on what view I'd like of the data. Is there any feature built into Spring to configure which Jackson serializer to use on a per-function basis? I've found posts mentioning storing which fields you want in serialized in thread-local and having a filter send them and another post filtering based on Spring #Role, but nothing addressing choosing a serializer (or MixIn) on a per-function basis. Any ideas?
The key to me thinking a proposed solution is good is if the return type is an object, not String.
Here are the objects in my graph.
public class BusinessCategory implements Comparable<BusinessCategory> {
private String name;
private Set<BusinessCategory> parentCategories = new TreeSet<>();
private Set<BusinessCategory> childCategories = new TreeSet<>();
// getters, setters, compareTo, et cetera
}
I am sending these across the wire from a Spring MVC controller as JSON like so:
#RestController
#RequestMapping("/business")
public class BusinessMVC {
private Jackson2ObjectMapperBuilder mapperBuilder;
private ObjectMapper parentOnlyMapper;
#Autowired
public BusinessMVCfinal(Jackson2ObjectMapperBuilder mapperBuilder) {
this.mapperBuilder = mapperBuilder;
this.parentOnlyMapper = mapperBuilder.build();
parentOnlyMapper.registerModule(new BusinessCategoryParentsOnlyMapperModule());
}
#RequestMapping(value="/business_category/parents/{categoryName}")
#ResponseBody
public String getParentCategories(#PathVariable String categoryName) throws JsonProcessingException {
return parentOnlyMapper.writeValueAsString(
BusinessCategory.businessCategoryForName(categoryName));
}
}
I have configure the serialization in a MixIn which is in turn added to the ObjectMapper using a module.
public interface BusinessCategoryParentsOnlyMixIn {
#JsonProperty("name") String getName();
#JsonProperty("parentCategories") Set<BusinessCategory> getParentCategories();
#JsonIgnore Set<BusinessCategory> getChildCategories();
}
public class BusinessCategoryParentsOnlyMapperModule extends SimpleModule {
public BusinessCategoryParentsOnlyMapperModule() {
super("BusinessCategoryParentsOnlyMapperModule",
new Version(1, 0, 0, "SNAPSHOT", "", ""));
}
public void setupModule(SetupContext context) {
context.setMixInAnnotations(
BusinessCategory.class,
BusinessCategoryParentsOnlyMixIn.class);
}
}
My current solution works, it just doesn't feel very clean.
"categories" : [ {
"name" : "Personal Driver",
"parentCategories" : [ {
"name" : "Transportation",
"parentCategories" : [ ]
} ]
}
Oh yes, I'm using:
spring-boot 1.2.7
spring-framework: 4.1.8
jackson 2.6.3
Others listed here: http://docs.spring.io/spring-boot/docs/1.2.7.RELEASE/reference/html/appendix-dependency-versions.html
In the end, the only process that met my needs was to create a set of view objects which exposed only the fields I wanted to expose. In the grand scheme of things, it only added a small amount of seemingly unnecessary code to the project and made the flow of data easier to understand.

Accessing Static Fields From FreeMarker Template (Spring)

I want to access static properties from FreeMarker Template
I have a class say Global which contains Static Final Fields and this class is not part of
the FreeMarker model. I want to know if there is a way to add this class to FreeMarker model
at startup of my app and access from Freemarker template like this:
${statics["com.ums.common.Global"].Change}
Try
dataModel.put("statics", ((BeansWrapper) cfg.getObjectWrapper()).getStaticModels());
where cfg is the FreeMarker Configuration object. Then you can use statics['com.example.ClassName'].FIELD_NAME to access the static fields or methods. You can also use something like
dataModel.put("globals", ((BeansWrapper) cfg.getObjectWrapper()).getStaticModels().get("com.ums.common.Global"));
if you only want to expose the constants of that class.
You can create Spring configuration class to configure Freemarker and use the Freemarker configuration.setSharedVariables() method to enable access to static methods from any template. The shared variables is also very useful for putting global properties (e.g. from your application.yaml file) so they are globally available from any template.
#Configuration
public class FreemarkerConfig implements BeanPostProcessor {
#Value("${email.from-address}") // e.g. from `application.yaml`
private String emailFromAddress;
#SneakyThrows // or use try / catch (if not using lombok)
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof FreeMarkerConfigurer) {
FreeMarkerConfigurer configurer = (FreeMarkerConfigurer) bean;
freemarker.template.Configuration configuration = configurer.getConfiguration();
BeansWrapper objectWrapper = (BeansWrapper) configuration.getObjectWrapper();
configuration.setSharedVariables(getSharedVariables(objectWrapper));
}
return bean;
}
private Map<String, ?> getSharedVariables(BeansWrapper config) {
Map<String, Object> sharedVariables = new HashMap<>();
// Add global variables
sharedVariables.put("emailFromAddress", emailFromAddress);
// Add static support
TemplateModel statics = config.getStaticModels();
sharedVariables.put("statics", statics);
return sharedVariables;
}
}
Once the configuration is set up, you can add access your shared variables in any template (e.g. ${emailFromAddress}).
To access static methods you first of all need to link the class containing the static method. For example, if you wanted to do a more bespoke comparison of 2 objects you might have: -
public class FreemarkerUtils {
/**
* Compare 2 objects. Both objects are converted to Strings first of all (empty String if null) and compared.
*/
public static boolean isEqualTo(Object obj1, Object obj2) {
String str1 = obj1 != null ? String.valueOf(obj1) : "";
String str2 = obj2 != null ? String.valueOf(obj2) : "";
return str1.equals(str2);
}
}
This static method must be assigned as follows in a freemarker template ...
<#assign Util=statics['com.my.package.FreemarkerUtils']>
... and used as follows ...
<#if Util.isEqualTo (obj1, obj2)> objects are equal! </#if>
Using Java static methods like this from your templates can be useful when Freemarker isn't powerful enough for your particular use-case.

Seam 3 - retrieve all seam components in application context

is there a way to get all the Seam 3 component classes which are #ApplicationScoped?
Didn't try myself, just a guess after reading 16.5. The Bean interface chapter of Weld documentation
class ApplicationScopedBeans {
#Inject BeanManager beanManager;
public Set<Bean<?>> getApplicationScopedBeans() {
Set<Bean<?>> allBeans = beanManager.getBeans(Object.class, new AnnotationLiteral<Any>() {});
Set<Bean<?>> result = new HashSet<Bean<?>>();
for(Bean<?> bean : allBeans) {
if(bean.getScope().equals(ApplicationScoped.class)) {
result.add(bean);
}
}
return result;
}
}
UPDATE
To obtain an instance of a Bean:
public Object getApplicationScopedInstance(Bean<?> bean) {
CreationalContext ctx = beanManager.createCreationalContext(bean);
Context appCtx = beanManager.getContext(ApplicationScoped.class);
return appCtx.get(bean, ctx);
}
UPDATE 2
Looks like all above misses the whole point of CDI :)
class ApplicationScopedBeans {
#Inject #ApplicationScoped Instance<Object> appScopedBeans;
}
if you want to call a method from a component in applicationContext or use a field in this, it's better that u define it as producer method or field and inject it where u want.
You would use getApplicationContext() to get the context, and then the getNames() to get all names of things that are application scope, and then you would use get()to retrieve them by name.
What are you trying to do? From there you would have to use reflection to get them to the right type..
Context appContext = Contexts.getApplicationContext();
String [] names = appContext.getNames();
//Do whatever with them..
for(String s : names){
Object x = appContext.get(name);
// do something.
}

Resources