I'm trying to call Spring's ControllerLinkBuilder.methodOn() with a non-String type, which always fails. And I don't know which kind of Converter to use and where to register it.
Here's my Controller:
#RestController
#RequestMapping("/companies")
class CompanyController {
#RequestMapping(value="/{c}", method=RequestMethod.GET)
void getIt(#PathVariable Company c) {
System.out.println(c);
Link link = linkTo(methodOn(getClass()).getIt(c));
}
}
The System.out.println(c) works well. My Company Domain object get's fetched from DB. (I'm using DomainClassConverter)
But the other way doesn't work: ConverterNotFoundException: No converter found capable of converting from type #PathVariable Company to type String
Do I just need a Converter<Company, String>? And where should I register it? I tried something within the addFormatters(FormatterRegistry registry) method of WebMvcConfigurationSupport, but it did just display the same error. But after all I'm not sure what exactly I tried...
I had the same issue, it is a bug. If you don't want to do copy & paste on every controller you can try something like this in your WebMvcConfigurationSupport. It works for me.
#Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
try {
Class<?> clazz = Class.forName("org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor$BoundMethodParameter");
Field field = clazz.getDeclaredField("CONVERSION_SERVICE");
field.setAccessible(true);
DefaultFormattingConversionService service = (DefaultFormattingConversionService) field.get(null);
for (Converter<?, ?> converter : beanFactory.getBeansOfType(Converter.class).values()) {
service.addConverter(converter);
}
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
Found a "solution". It requires a lot copy & paste from Spring's classes, but at least it works!
Basically I had to copy org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor and change two lines:
class AnnotatedParametersParameterAccessor {
...
static class BoundMethodParameter {
// OLD: (with this one you can't call addConverter())
// private static final ConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService();
// NEW:
private static final FormattingConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService();
...
public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationAttribute attribute) {
...
// ADD:
CONVERSION_SERVICE.addConverter(new MyNewConverter());
}
...
}
This class get's used by ControllerLinkBuilderFactory. So I had to copy & paste that, too.
And this one get's used by ControllerLinkBuilder. Also copy & paste.
My Converter just does myDomainObject.getId().toString():
public class MyNewConverter implements Converter<Company, String> {
#Override
public String convert(Company source) {
return source.getId().toString();
}
}
Now you can use the copy&pasted ControllerLinkBuilder inside the controller and it works as expected!
I developed a framework to render links in spring hateoas and it supports annotated parameters (#PathVariable and #RequestParam) and arbitrary parameters types.
In order to render these arbitrary types you have to create a spring bean that implements com.github.osvaldopina.linkbuilder.argumentresolver.ArgumentResolver interface.
The interface has 3 methods:
public boolean resolveFor(MethodParameter methodParameter)
Is used to determine if the ArgumentResolver can be used to deal with the methodParameter. For example:
public boolean resolveFor(MethodParameter methodParameter) {
return UserDefinedType.class.isAssignableFrom(methodParameter.getParameterType());
}
Defines that this ArgumentResover will be used for UserDefinedType.
public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter)
Is used to include in the uriTemplate associated with the method the proper template parts. For example:
#Override
public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter) {
uriTemplateAugmenter.addToQuery("value1");
uriTemplateAugmenter.addToQuery("value2");
}
adds 2 query parameters (value1 and value2) to the uri template.
public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames)
Sets in the template the values for the template variables. For example:
#Override
public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames) {
if (parameter != null && ((UserDefinedType) parameter).getValue1() != null) {
template.set("value1", ((UserDefinedType) parameter).getValue1());
}
else {
template.set("value1", "null-value");
}
if (parameter != null && ((UserDefinedType) parameter).getValue2() != null) {
template.set("value2", ((UserDefinedType) parameter).getValue2());
}
else {
template.set("value2", "null-value");
}
}
gets the UserDefinedType instance and use it to sets the templates variables value1 and value2 defined in augmentTemplate method.
A ArgumentResolver complete example would be:
#Component
public class UserDefinedTypeArgumentResolver implements ArgumentResolver {
#Override
public boolean resolveFor(MethodParameter methodParameter) {
return UserDefinedType.class.isAssignableFrom(methodParameter.getParameterType());
}
#Override
public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter) {
uriTemplateAugmenter.addToQuery("value1");
uriTemplateAugmenter.addToQuery("value2");
}
#Override
public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames) {
if (parameter != null && ((UserDefinedType) parameter).getValue1() != null) {
template.set("value1", ((UserDefinedType) parameter).getValue1());
}
else {
template.set("value1", "null-value");
}
if (parameter != null && ((UserDefinedType) parameter).getValue2() != null) {
template.set("value2", ((UserDefinedType) parameter).getValue2());
}
else {
template.set("value2", "null-value");
}
}
}
and for the following link builder:
linksBuilder.link()
.withRel("user-type")
.fromControllerCall(RootRestController.class)
.queryParameterForUserDefinedType(new UserDefinedType("v1", "v2"));
to the following method:
#RequestMapping("/user-defined-type")
#EnableSelfFromCurrentCall
public void queryParameterForUserDefinedType(UserDefinedType userDefinedType) {
}
would generate the following link:
{
...
"_links": {
"user-type": {
"href": "http://localhost:8080/user-defined-type?value1=v1&value2=v2"
}
...
}
}
full config in spring boot. same as Franco Gotusso's answer just provide more detail.
```
/**
* This configuration file is to fix bug of Spring Hateoas.
* please check https://github.com/spring-projects/spring-hateoas/issues/118.
*/
#Component
public class MvcConfig extends WebMvcConfigurerAdapter {
#Autowired
private ApplicationContext applicationContext;
#Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
try {
Class<?> clazz = Class.forName("org.springframework.hateoas.mvc."
+ "AnnotatedParametersParameterAccessor$BoundMethodParameter");
Field field = clazz.getDeclaredField("CONVERSION_SERVICE");
field.setAccessible(true);
DefaultFormattingConversionService service =
(DefaultFormattingConversionService) field.get(null);
for (Formatter<?> formatter : applicationContext
.getBeansOfType(Formatter.class).values()) {
service.addFormatter(formatter);
}
for (Converter<?, ?> converter : applicationContext
.getBeansOfType(Converter.class).values()) {
service.addConverter(converter);
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
```
Related
How can i register a global available DynamicParameterizedType in hibernate?
I wrote the following type:
public class QuantityType extends AbstractSingleColumnStandardBasicType<Quantity<?>> implements DynamicParameterizedType {
public static final QuantityType INSTANCE = new QuantityType();
public QuantityType() {
super(DoubleTypeDescriptor.INSTANCE, new QuantityJavaDescriptor(AbstractUnit.ONE));
}
#Override
public String getName() {
return QuantityType.class.getSimpleName();
}
#Override
public void setParameterValues(Properties parameters) {
ParameterType reader = (ParameterType) parameters.get(PARAMETER_TYPE);
if (reader == null) throw new RuntimeException("Not Implemented");
Unit<?> resolvedUnit = resolveUnit(reader);
setJavaTypeDescriptor(new QuantityJavaDescriptor(resolvedUnit));
}
private Unit<?> resolveUnit(ParameterType reader) {...}
}
and registered it with a service registration in hibernate:
public class QuantityTypeRegistration implements TypeContributor {
#Override
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
typeContributions.contributeType(QuantityType.INSTANCE);
}
}
If i use the type in an entity, the wrap/unwrap method of the JavaTypeDescriptor gets called,
but instead of the parameterized JavaTypeDescriptor, the default JavaTypeDescriptor gets called. For some reason the setParameterValues method was not called.
Code: https://github.com/raynigon/unit-api/tree/master/jpa-starter/src/main/java/com/raynigon/unit_api/jpa
Here is my ConfigUpdater class
private final class ConfigUpdater implements ManagedService {
#SuppressWarnings("rawtypes")
#Override
public void updated(Dictionary config) throws ConfigurationException {
if (config == null) {
return;
}
String title = ((String)config.get("title"));
}
}
My question is how can I access String title in any other class? Or how can I get config dictionary in any other class... Method updated will only be called when a config file is changed... once it is changed how can access its data in other class?
In general you would create a service that exposes these properties to other components.
For example, you could give your ConfigUpdater a second interface. Another component can than lookup/inject this interface from the service registry and use it's methods to access the properties.
I created an example project on GitHub: https://github.com/paulbakker/configuration-example
The most important part is the service that implements both ManagedService and a custom interface:
#Component(properties=#Property(name=Constants.SERVICE_PID, value="example.configurationservice"))
public class ConfigurationUpdater implements ManagedService, MyConfiguration{
private volatile String message;
#Override
public void updated(#SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException {
message = (String)properties.get("message");
}
#Override
public String getMessage() {
return message;
}
}
The configuration can then be used like this:
#Component(provides=ExampleConsumer.class,
properties= {
#Property(name = CommandProcessor.COMMAND_SCOPE, value = "example"),
#Property(name = CommandProcessor.COMMAND_FUNCTION, values = {"showMessage"}) })
public class ExampleConsumer {
#ServiceDependency
private volatile MyConfiguration config;
public void showMessage() {
String message = config.getMessage();
System.out.println(message);
}
}
I have created a custom string converter for spring webflow that trims spaces for every String property
public class StringTrimmerConverter implements Converter {
public Object convertSourceToTargetClass(final Object object, final Class clazz) throws Exception {
if ((object != null) && (object.getClass() == getSourceClass()) && (clazz == getTargetClass())) {
return ((String) object).trim();
}
return object;
}
public Class<String> getSourceClass() { return String.class; }
public Class<String> getTargetClass() { return String.class; }
}
It is added in conversion services
public class FlowConversationService extends DefaultConversionService {
protected void addDefaultConverters() {
super.addDefaultConverters();
this.addConverter(new StringTrimmerConverter());
}
}
Is there a way to disable this converter for password fields only?
I'm using spring webflow 2.3.2.RELEASE and spring 3.2.2.RELEASE.
You can force a specific converter with <binding> configuration.
Alternatively you can store password field in char[]. I had discussions with people which prefer this over String as char array will not get interned.
Im new using this front end framework application...
I recently started to work with smartgwt and i'm bulding a new application with a Spring MVC integration.
I'm using a ListGrid with a RestDataSource (Consume the Rest service with mvc:annotation-driven for plain JSON)
I can see that the servaice gets consuming properly perhaps my grid is never shown with the data in it.
Can someone help me here ?
Here's my ListGrid class
public class ListGrid extends com.smartgwt.client.widgets.grid.ListGrid {
private final SpringJSONDataSource springJSONDataSource;
public ListGrid(List<DataSourceField> fields) {
this(new PatientDataSource(fields));
}
public ListGrid(SpringJSONDataSource springJSONDataSource) {
this.springJSONDataSource = springJSONDataSource;
init();
}
private void init() {
setAutoFetchData(true);
setAlternateRecordStyles(true);
setEmptyCellValue("???");
setDataPageSize(50);
setDataSource(springJSONDataSource);
}
}
Now there's the DataSource implmentation
public abstract class SpringJSONDataSource extends RestDataSource {
protected final HTTPMethod httpMethod;
public SpringJSONDataSource(List<DataSourceField> fields) {
this(fields, HTTPMethod.POST);
}
public SpringJSONDataSource(List<DataSourceField> fields, HTTPMethod httpMethod) {
this.httpMethod = httpMethod;
setDataFormat(DSDataFormat.JSON);
addDataSourceFields(fields);
setOperationBindings(getFetch());
addURLs();
}
private void addURLs() {
if(getUpdateDataURL() != null)
setUpdateDataURL(getUpdateDataURL());
if(getRemoveDataURL() != null)
setRemoveDataURL(getRemoveDataURL());
if(getAddDataURL() != null)
setAddDataURL(getAddDataURL());
if(getFetchDataURL() != null)
setFetchDataURL(getFetchDataURL());
}
private void addDataSourceFields(List<DataSourceField> fields) {
for (DataSourceField dataSourceField : fields) {
addField(dataSourceField);
}
}
protected abstract OperationBinding getFetch();
protected abstract OperationBinding getRemove();
protected abstract OperationBinding getAdd();
protected abstract OperationBinding getUpdate();
public abstract String getUpdateDataURL();
public abstract String getRemoveDataURL();
public abstract String getAddDataURL();
public abstract String getFetchDataURL();
}
The class PatientDataSource that extends SpringJSONDataSource
public class PatientDataSource extends SpringJSONDataSource {
public PatientDataSource(List<DataSourceField> fields) {
super(fields);
setPrettyPrintJSON(true);
}
#Override
protected OperationBinding getFetch() {
OperationBinding fetch = new OperationBinding();
fetch.setOperationType(DSOperationType.FETCH);
fetch.setDataProtocol(DSProtocol.POSTMESSAGE);
DSRequest fetchProps = new DSRequest();
fetchProps.setHttpMethod(httpMethod.toString());
fetch.setRequestProperties(fetchProps);
return fetch;
}
#Override
public String getFetchDataURL() {
return "/spring/fetchPatients";
}
#Override
protected OperationBinding getRemove() {
return null;
}
#Override
public String getRemoveDataURL() {
return null;
}
#Override
protected OperationBinding getAdd() {
return null;
}
#Override
public String getAddDataURL() {
return null;
}
#Override
protected OperationBinding getUpdate() {
return null;
}
#Override
public String getUpdateDataURL() {
return null;
}
}
My spring controller PatientControler
#Controller
public class PatienController {
Logger logger = Logger.getLogger(PatienController.class);
#Autowired
private PatientServices patientServices;
#RequestMapping(value = "/patientTest", method = RequestMethod.GET)
#ResponseBody
public Object getTest()
{
return patientServices.getAllPatients();
}
#RequestMapping(value = "/fetchPatients", method = RequestMethod.POST)
#ResponseBody
public Object getAllPatients()
{
return patientServices.getAllPatients();
}
}
PatientServiceImpl
public class PatientServicesImpl implements PatientServices {
public List<Patient> getAllPatients() {
List<Patient> patients = new ArrayList<Patient>();
Patient patient;
for(int i = 0 ; i < 500 ; i++){
patient = new Patient();
patient.setDateOfBirth(new Date());
patient.setFirstName("Joe");
patient.setMiddleName("Moe");
patient.setLastName("Blow");
patient.setLastConsultation(new Date());
patient.setSex(Sex.M);
patients.add(patient);
}
return patients;
}
}
*Im Really stuck right now i've been looking for all type of answers .... but so far nothing worked when i tried to override the transformResponse from my RestDataSource impentation the parameter "data" as an OBJECT, returns me an array [object Object],[object Object],[object Object],[object Object],[object Object] *
The Data which is transferred from the RestDataSource has a specific format which is described in the JavaDoc of the RestDataSource
Your server must understand the request and send back a valid response.
At the moment your example doesn't seem to honour the contract.
To debug the traffic send to and from your server you can use the SmartClient-Console. You can open it by a browser bookmark like this:
javascript:isc.showConsole()
Of cause you need to deploy this console by adding the following module to your gwt.xml
<inherits name="com.smartclient.tools.SmartClientTools"/>
Now go to the RPC Tab and check Track-RPCs
My enums are stored as int in mongodb (from C# app). Now in Java, when I try to retrieve them, it throws an exception (it seems enum can be converted from string value only). Is there any way I can do it?
Also when I save some collections into mongodb (from Java), it converts enum values to string (not their value/cardinal). Is there any override available?
This can be achieved by writing mongodb-converter on class level but I don't want to write mondodb-converter for each class as these enums are in many different classes.
So do we have something on the field level?
After a long digging in the spring-mongodb converter code,
Ok i finished and now it's working :) here it is (if there is simpler solution i will be happy see as well, this is what i've done ) :
first define :
public interface IntEnumConvertable {
public int getValue();
}
and a simple enum that implements it :
public enum tester implements IntEnumConvertable{
vali(0),secondvali(1),thirdvali(5);
private final int val;
private tester(int num)
{
val = num;
}
public int getValue(){
return val;
}
}
Ok, now you will now need 2 converters , one is simple ,
the other is more complex. the simple one (this simple baby is also handling the simple convert and returns a string when cast is not possible, that is great if you want to have enum stored as strings and for enum that are numbers to be stored as integers) :
public class IntegerEnumConverters {
#WritingConverter
public static class EnumToIntegerConverter implements Converter<Enum<?>, Object> {
#Override
public Object convert(Enum<?> source) {
if(source instanceof IntEnumConvertable)
{
return ((IntEnumConvertable)(source)).getValue();
}
else
{
return source.name();
}
}
}
}
the more complex one , is actually a converter factory :
public class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
#Override
public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
if (enumType == null) {
throw new IllegalArgumentException(
"The target type " + targetType.getName() + " does not refer to an enum");
}
return new IntegerToEnum(enumType);
}
#ReadingConverter
public static class IntegerToEnum<T extends Enum> implements Converter<Integer, Enum> {
private final Class<T> enumType;
public IntegerToEnum(Class<T> enumType) {
this.enumType = enumType;
}
#Override
public Enum convert(Integer source) {
for(T t : enumType.getEnumConstants()) {
if(t instanceof IntEnumConvertable)
{
if(((IntEnumConvertable)t).getValue() == source.intValue()) {
return t;
}
}
}
return null;
}
}
}
and now for the hack part , i personnaly didnt find any "programmitacly" way to register a converter factory within a mongoConverter , so i digged in the code and with a little casting , here it is (put this 2 babies functions in your #Configuration class)
#Bean
public CustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
converters.add(new IntegerEnumConverters.EnumToIntegerConverter());
// this is a dummy registration , actually it's a work-around because
// spring-mongodb doesnt has the option to reg converter factory.
// so we reg the converter that our factory uses.
converters.add(new IntegerToEnumConverterFactory.IntegerToEnum(null));
return new CustomConversions(converters);
}
#Bean
public MappingMongoConverter mappingMongoConverter() throws Exception {
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setApplicationContext(appContext);
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mappingContext);
mongoConverter.setCustomConversions(customConversions());
ConversionService convService = mongoConverter.getConversionService();
((GenericConversionService)convService).addConverterFactory(new IntegerToEnumConverterFactory());
mongoConverter.afterPropertiesSet();
return mongoConverter;
}
You will need to implement your custom converters and register it with spring.
http://static.springsource.org/spring-data/data-mongo/docs/current/reference/html/#mongo.custom-converters
Isn't it easier to use plain constants rather than an enum...
int SOMETHING = 33;
int OTHER_THING = 55;
or
public class Role {
public static final Stirng ROLE_USER = "ROLE_USER",
ROLE_LOOSER = "ROLE_LOOSER";
}
String yourRole = Role.ROLE_LOOSER