Given using annotation on both Class and fields levels to set my mappings settings and use settings file like this
#Setting(settingPath = "settings.json")
to define custom analyzers, how would I set mappings "dynamic": false ?
Setting the dynamic mapping type on document and objects was introduced in Spring Data Elasticsearch version 4.0.0. It can be defined like this (code from the tests):
#Document(indexName = "test-index-configure-dynamic-mapping")
#DynamicMapping(DynamicMappingValue.False)
class ConfigureDynamicMappingEntity {
#Nullable
#DynamicMapping(DynamicMappingValue.Strict)
#Field(type = FieldType.Object)
private Author author;
#Nullable
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
Related
I am using Spring Data Neo4j RX. And I have a query like this:
#Query("MATCH (a:Repo)-[:REPO_DEPEND_ON]->(b:Repo) WHERE a.name= $name RETURN a.name, b.name")
String[] getSingleRepoDependencyTo(String name);
I know the return type is wrong here, as it cannot be a String array. But how can I get the result properly, which contains two fields?
I have searched online for a long time but cannot find an answer. The "#QueryResult" annotation is not supported in this RX version yet.
Thanks for your help.
Assuming that you have a mapped #Node Repo with its relationships like
#Node
public class Repo {
// other things
String name;
#Relationship("REPO_DEPEND_ON") Repo repo;
}
and defining this method in a ...extends Neo4jRepository<Repo,...>,
you could use Projections.
public interface RepoProjection {
String getName();
DependingRepo getRepo();
/**
* nested projection
*/
interface DependingRepo {
String getName();
}
}
Important to keep in mind that the returned values should be the nodes and relationship to make it work this way.
You could also remove the custom query and do something like:
RepoProjection findByName(String name)
if you do not have the need for a findByName in this repository for the entity itself.
Take a look here: https://neo4j.github.io/sdn-rx/current/#projections.interfaces
It seems to list exactly what you want. From those docs:
interface NamesOnly {
String getFirstName();
String getLastName();
}
interface PersonRepository extends Neo4jRepository<Person, Long> {
List<NamesOnly> findByFirstName(String firstName);
}
There are some other variations too.
You can use annotation #QueryResult on your expected model. For instance you can do that in this way.
DTO:
import org.springframework.data.neo4j.annotation.QueryResult;
#QueryResult
public class SomeDto {
private int someInt;
private SomeObject sobj;
private double sdouble;
private AnotherObject anObj;
//getters setters
}
Neo4jRepository:
public interface DomainObjectRepository extends Neo4jRepository<DomainObject, Long> {
#Query("MATCH(n:SomeTable) RETURN someInt, sobj, sdouble, anObj") //Return a few columns
Optional<SomeDto> getSomeDto();
}
How can I exclude the child domain property when I use the grailsWebDataBinder?
For example, I have domains:
class Car {
String carPropertyToExclude
Set<Detail> details
static hasMany = [details: Detail]
}
class Detail {
String detailPropertyToExclude
static belongsTo= [car: Car]
}
I want to exclude the detailPropertyToExclude from Detail when I call the bind method of grailsWebDataBinder and give the car instance as a parameter
Code:
List blackList = ["carPropertyToExclude"]
grailsWebDataBinder.bind(car, new SimpleMapDataBindingSource(params), null, blackList)
Note:
Don't suggest the bindable: false or variants when excluded from anywhere. Only need to know is there a way to do it by providing blackList as bind() method parameter.
These variants also not working:
List blackList = ["carPropertyToExclude", "details.detailPropertyToExclude"]
List blackList = ["carPropertyToExclude", [Detail.class : "detailPropertyToExclude"]]
The main question is how to prepare the blackList to exclude also child's property?
blacklist parameter supports only direct object properties
you can use DataBindingListener
import grails.databinding.events.DataBindingListenerAdapter
class BlackListener extends DataBindingListenerAdapter{
List<String> list
//returns false if you want to exclude property from binding
public Boolean beforeBinding(Object obj, String propertyName, Object value, Object errors) {
return !list.contains("${obj?.class.name}.${propertyName}".toString())
}
}
...
List blackList = ["Car.carPropertyToExclude", "Details.detailPropertyToExclude"]
grailsWebDataBinder.bind(car, new SimpleMapDataBindingSource(params),
new BlackListener(list:blackList) )
UPD:
Unfortunately the method above does not work with Collection binding.
The problem that SimpleDataBinder.setPropertyValue(...) method loses listener when processing a list.
Not sure if following workaround is good (potentially context initialization required)
but it's possible to register converter for each black list:
import grails.databinding.SimpleDataBinder
import grails.databinding.SimpleMapDataBindingSource
import grails.databinding.converters.ValueConverter
SimpleDataBinder setBlackList(SimpleDataBinder binder, Map<Class,List<String>> blackLists) {
blackLists.each { Class clazz, List<String> blackList ->
def vc = new ValueConverter(){
boolean canConvert(Object value){
return value instanceof Map
}
Object convert(Object value){
def obj = clazz.newInstance()
binder.bind( obj, new SimpleMapDataBindingSource(value), [], blackList )
return obj
}
Class<?> getTargetType(){ clazz }
}
binder.registerConverter(vc)
}
return binder
}
...
Map blackLists = [
(Car.class) : ["carPropertyToExclude"],
(Detail.class) : ["detailPropertyToExclude"]
]
setBlackList(grailsWebDataBinder,blackLists)
...
grailsWebDataBinder.bind(car, new SimpleMapDataBindingSource(params), null,
blackLists[car.getClass()] )
PS: as alternative possible to set grailsWebDataBinder.conversionService...
In a controller you can exclude props from binding by:
def someAction(){
Car car = new Car()
bindData car, params, [exclude: ['carPropertyToExclude', 'details']]
car.details = params.list('details').collect{
bindData new Detail(), [exclude: ['detailPropertyToExclude']]
}
}
You might also want to use the command-objects to represent your form-data.
I have found one solution based on daggett answer. Maybe in greater versions, the bug is fixed or will be fixed. The bug is that when we give the listener as a parameter of bind method for child domains the listener isn't triggered but when we set it as class level listener works. Grails version 3.2.11.
I have created the BlackListener like this:
public class BlackListListener extends DataBindingListenerAdapter {
private final Map<Class<?>, Collection<String>> blackList;
public BlackListListener(Map<Class<?>, Collection<String>> blackList) {
this.blackList = blackList;
}
public Boolean beforeBinding(Object obj, String propertyName, Object value, Object errors) {
Boolean result = Boolean.TRUE;
Collection<String> list = blackList.get(obj.getClass());
if (CollectionUtils.isNotEmpty(list)) {
result = !list.contains(propertyName);
}
return result;
}
}
Then I make my own grailsWebDatabinder bean as prototype:
<bean id="webDataBinder" class="grails.web.databinding.GrailsWebDataBinder" c:_0-ref="grailsApplication"
scope="prototype"/>
<bean id="carBinder" class="CarDataBinder" c:_0-ref="webDataBinder"/>
and then when I want to use data binder I inject the webDataBinder and init listener:
public CarDataBinder(GrailsWebDataBinder grailsWebDataBinder) {
this.grailsWebDataBinder = grailsWebDataBinder;
DataBindingListener blackListListener = new BlackListListener(
ImmutableMap.of(
Car.class, ImmutableSet.of("carPropertyToExclude"),
Detail.class, ImmutableSet.of("detailPropertyToExclude")
)
);
grailsWebDataBinder.setDataBindingListeners(blackListListener);
}
and then:
void bindData(Car car, Map<?, ?> params) {
grailsWebDataBinder.bind(car, new SimpleMapDataBindingSource(params));
}
If there are better ways you can post.
I am trying to create nodes of a specific type with properties which can be dynamic .
For Example : I can create a Person node with name,age,address properties. But these need not be the only properties when I create another Person node. This node can have name,age,address and an additional property salary. Using spring data or query DSL needs me to create Java POJO class Person with fixed number of instance variables name,age and address .
#NodeEntity
public class Person {
#GraphId private Long id;
private String name;
private String age;
private String address;
}
I cannot add a dynamic property for salary for another Person node. Is there a way I can achieve this ?
Dynamic properties are not supported in Neo4j-OGM at the moment (see https://jira.spring.io/browse/DATAGRAPH-555)
If you only interact with your graph via the OGM and do not have to query on individual dynamic properties, you could try a Map of properties with a custom Converter, that converts this Map to a String (like json). The OGM will then use this converter to serialize the map to and from the graph.
Note that because the values are squashed into a String, it is now not trivial to query on an individual dynamic property.
To create a custom converter you need to implement org.neo4j.ogm.typeconversion.AttributeConverter and provide the implementation to convert from a Map to String.
Then, annotate your map property in your domain entity like this:
#Convert(MoneyConverter.class)
Edit:
As pointed out by Michael, if the salary is the only extra optional property, then it makes sense to have this property but set it only when it has a value. Dynamic properties are overkill in this case. You may want to use dynamic properties when you have an unknown and arbitrary set of properties to be persisted with the node
You can workaround the limitations by creating a CompositeAttributeConverter saving each dynamic property in the graph (not only as JSON-String wich cannot be queried well - as mentioned by luanne in the accepted answer)
import java.lang.reflect.Field;
import java.util.*;
import org.neo4j.ogm.typeconversion.CompositeAttributeConverter;
public abstract class DynamicPropertiesConverter implements CompositeAttributeConverter<Map<String, ?>> {
private Set<String> blacklist;
public DynamicPropertiesConverter(Class<?> clazz) {
blacklist = new HashSet<>();
addAllFields(clazz);
}
public DynamicPropertiesConverter(Set<String> blacklist) {
this.blacklist = blacklist;
}
public void addAllFields(Class<?> type) {
for (Field field : type.getDeclaredFields()) {
blacklist.add(field.getName());
}
if (type.getSuperclass() != null) {
addAllFields(type.getSuperclass());
}
}
#Override
public Map<String, ?> toGraphProperties(Map<String, ?> value) {
Map<String, ?> result = new HashMap<>(value);
result.keySet().removeAll(blacklist);
return result;
}
#Override
public Map<String, ?> toEntityAttribute(Map<String, ?> value) {
return toGraphProperties(value);
}
}
Now you can create a special version of this converter:
public class DynamicNodePropertiesConverter extends DynamicPropertiesConverter {
public DynamicNodePropertiesConverter() {
super(Node.class);
}
}
And use it like this:
import java.util.Map;
import DynamicNodePropertiesConverter;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;
import org.neo4j.ogm.annotation.typeconversion.Convert;
#NodeEntity
public class Node {
#Convert(DynamicNodePropertiesConverter.class)
private Map<String, Object> data;
/* getter and setter */
}
After upgrading from SDN 2.3.4 to 3.0.0.RELEASE, I have two tests that fail with :
Index name for class java.lang.String name rel: false idx: true must differ from the default name: BesoinNode
In the BesoinNode class, I previously used #Indexed(indexName = "indexOfNeeds") but removed the indexName because of https://stackoverflow.com/a/22084251/1528942. Now my index declaration is simply :
#Indexed
private String name;
The failing test code is :
#Test
public final void t02_TestBesoinDAORetrieve() {
// We create a new object in the datastore
Besoin need1 = needDAO.create(new BesoinNode("t02 need"));
assertEquals(new Long(1), needDAO.count());
// We retrieve the object from the datastore using its id
Besoin need1bis = needDAO.retrieveById(need1.getId());
assertEquals(need1, need1bis);
assertEquals(need1.getId(), need1bis.getId());
assertEquals(need1.getName(), need1bis.getName());
// We retrieve the object from the datastore using its name
Besoin need1ter = needDAO.retrieveByName(need1.getName());
assertEquals(need1, need1ter);
assertEquals(need1.getId(), need1ter.getId());
assertEquals(need1.getName(), need1ter.getName());
}
The test succeeds if I comment out the call to retrieveByName. Code is below:
#Override
public Besoin retrieveByName(String name) {
return repository.findByPropertyValue("name", name);
}
How can I rewrite my retrieveByName method to solve the error? Thanks!
Note : while this may no be related to the problem, I see in the API that IndexRepository#findByPropertyValue(String, Object) is deprecated. Only AbstractGraphRepository#findByPropertyValue(String, Object) is not deprecated. The problem is that my repository extends GraphRepository and that GraphRepository extends IndexRepository, something I have no control over.
Note 2 : Using #Indexed(indexType = IndexType.SIMPLE) makes the test succeed. But this toggles the old index system (not the new one which is label-based).
I'm new to Guice and here is a naive question. I learned that we could bind String to a particular value through:
bind(String.class)
.annotatedWith(Names.named("JDBC URL"))
.toInstance("jdbc:mysql://localhost/pizza");
But what if I want to bind String to any possible characters?
Or I think it could be described this way:
How can I replace "new SomeClass(String strParameter)" with Guice?
You first need to annotate the constructor for SomeClass:
class SomeClass {
#Inject
SomeClass(#Named("JDBC URL") String jdbcUrl) {
this.jdbcUrl = jdbcUrl;
}
}
I prefer to use custom annotations, like this:
class SomeClass {
#Inject
SomeClass(#JdbcUrl String jdbcUrl) {
this.jdbcUrl = jdbcUrl;
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.PARAMETER})
#BindingAnnotation
public #interface JdbcUrl {}
}
Then you need to provide a binding in your Module:
public class SomeModule extends AbstractModule {
private final String jdbcUrl; // set in constructor
protected void configure() {
bindConstant().annotatedWith(SomeClass.JdbcUrl.class).to(jdbcUrl);
}
}
Then an time Guice creates SomeClass, it will inject the parameter. For instance, if SomeOtherClass depends on SomeClass:
class SomeOtherClass {
#Inject
SomeOtherClass(SomeClass someClass) {
this.someClass = someClass;
}
Often, when you think you want to inject a String, you want to inject an object. For instance, if the String is a URL, I often inject a URI with a binding annotation.
This all assumes there is some constant value you can define at module creation time for the String. If the value isn't available at module creation time, you can use AssistedInject.
This might be off-topic, but Guice makes configuration much easier than writing an explicit binding for every String you need. You can just have a config file for them:
Properties configProps = Properties.load(getClass().getClassLoader().getResourceAsStream("myconfig.properties");
Names.bindProperties(binder(), configProps);
and voilĂ all your config is ready for injection:
#Provides // use this to have nice creation methods in modules
public Connection getDBConnection(#Named("dbConnection") String connectionStr,
#Named("dbUser") String user,
#Named("dbPw") String pw,) {
return DriverManager.getConnection(connectionStr, user, pw);
}
Now just create your Java properties file myconfig.properties at the root of your classpath with
dbConnection = jdbc:mysql://localhost/test
dbUser = username
dbPw = password
or merge authorization information from some other source into the properties and you're set.
I was able to inject a string through Named annotation.
#Provides
#Named("stage")
String stage() {
return domain;
}
class SomeClass {
#Inject
#Named("stage")
String stageName;
}
I find a solution in the FAQ of Guice:
http://code.google.com/docreader/#p=google-guice&s=google-guice&t=FrequentlyAskedQuestions
In addition to define an annotation and a String attribute in MyModule, I need to write below line to get a instance of SomeClass:
SomeClass instance = Guice.createInjector(new MyModule("any string i like to use")).getInstance(SomeClass.class);
But I remembered that Injector.getInstance() should not be used except for the root object, so is there any better way to do this?