Is there a way to sort repository query results by querydsl alias?
So far I've managed to filter, but sorting results with an error:
org.springframework.data.mapping.PropertyReferenceException: No property username found for type User!
request:
GET /users?size=1&sort=username,desc
my rest controller method:
#GetMapping("/users")
public ListResult<User> getUsersInGroup(
#ApiIgnore #QuerydslPredicate(root = User.class) Predicate predicate,
Pageable pageable) {
Page<User> usersInGroup =
userRepository.findByGroup(CurrentUser.getGroup(), predicate, pageable);
return new ListResult<>(usersInGroup);
}
my repository:
#Override
default void customize(QuerydslBindings bindings, QUser root) {
bindings.including(root.account.login, root.account.firstName, root.account.lastName,
root.account.phoneNumber, root.account.email, root.account.postalCode, root.account.city,
root.account.address, root.account.language, root.account.presentationAlias);
bindAlias(bindings, root.account.login, "username");
}
default Page<User> findByGroup(Group group, Predicate predicate, Pageable pageable) {
BooleanExpression byGroup = QUser.user.group.eq(group);
BooleanExpression finalPredicate = byGroup.and(predicate);
return findAll(finalPredicate, pageable);
}
default void bindAlias(QuerydslBindings bindings, StringPath path, String alias) {
bindings.bind(path).as(alias).first(StringExpression::likeIgnoreCase);
}
I've also tried to implement my own PageableArgumentResolver based on QuerydslPredicateArgumentResolver, but some of the methods used there are package private so I thought maybe I am going in the wrong direction
I succeeded by creating a PageableArgumentResolver annotated with class type of query root class and adding alias registry to my generic repository interface.
This solution seems like a workaround but at least it works ;)
repository:
public interface UserRepository extends PageableAndFilterableGenericRepository<User, QUser> {
QDSLAliasRegistry aliasRegistry = QDSLAliasRegistry.instance();
#Override
default void customize(QuerydslBindings bindings, QUser root) {
bindAlias(bindings, root.account.login, "username");
}
default void bindAlias(QuerydslBindings bindings, StringPath path, String alias) {
bindings.bind(path).as(alias).first(StringExpression::likeIgnoreCase);
aliasRegistry.register(alias, path);
}
alias registry:
public class QDSLAliasRegistry {
private static QDSLAliasRegistry inst;
public static QDSLAliasRegistry instance() {
inst = inst == null ? new QDSLAliasRegistry() : inst;
return inst;
}
private QDSLAliasRegistry() {
registry = HashBiMap.create();
}
HashBiMap<String, Path<?>> registry;
resolver:
public class QDSLSafePageResolver implements PageableArgumentResolver {
private static final String DEFAULT_PAGE = "0";
private static final String DEFAULT_PAGE_SIZE = "20";
private static final String PAGE_PARAM = "page";
private static final String SIZE_PARAM = "size";
private static final String SORT_PARAM = "sort";
private final QDSLAliasRegistry aliasRegistry;
public QDSLSafePageResolver(QDSLAliasRegistry aliasRegistry) {
this.aliasRegistry = aliasRegistry;
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return Pageable.class.equals(parameter.getParameterType())
&& parameter.hasParameterAnnotation(QDSLPageable.class);
}
#Override
public Pageable resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
MultiValueMap<String, String> parameterMap = getParameterMap(webRequest);
final Class<?> root = parameter.getParameterAnnotation(QDSLPageable.class).root();
final ClassTypeInformation<?> typeInformation = ClassTypeInformation.from(root);
String pageStr = Optional.ofNullable(parameterMap.getFirst(PAGE_PARAM)).orElse(DEFAULT_PAGE);
String sizeStr = Optional.ofNullable(parameterMap.getFirst(SIZE_PARAM)).orElse(DEFAULT_PAGE_SIZE);
int page = Integer.parseInt(pageStr);
int size = Integer.parseInt(sizeStr);
List<String> sortStrings = parameterMap.get(SORT_PARAM);
if(sortStrings != null) {
OrderSpecifier[] specifiers = new OrderSpecifier[sortStrings.size()];
for(int i = 0; i < sortStrings.size(); i++) {
String sort = sortStrings.get(i);
String[] orderArr = sort.split(",");
Order order = orderArr.length == 1 ? Order.ASC : Order.valueOf(orderArr[1].toUpperCase());
specifiers[i] = buildOrderSpecifier(orderArr[0], order, typeInformation);
}
return new QPageRequest(page, size, specifiers);
} else {
return new QPageRequest(page, size);
}
}
private MultiValueMap<String, String> getParameterMap(NativeWebRequest webRequest) {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
for (Map.Entry<String, String[]> entry : webRequest.getParameterMap().entrySet()) {
parameters.put(entry.getKey(), Arrays.asList(entry.getValue()));
}
return parameters;
}
private OrderSpecifier<?> buildOrderSpecifier(String sort,
Order order,
ClassTypeInformation<?> typeInfo) {
Expression<?> sortPropertyExpression = new PathBuilderFactory().create(typeInfo.getType());
String dotPath = aliasRegistry.getDotPath(sort);
PropertyPath path = PropertyPath.from(dotPath, typeInfo);
sortPropertyExpression = Expressions.path(path.getType(), (Path<?>) sortPropertyExpression, path.toDotPath());
return new OrderSpecifier(order, sortPropertyExpression);
}
}
Related
We have a gRPC server that inserts the data into the CockRoachDB and the data is coming from a Spring Boot micro-service.
This is my code to persist in the CRDB database:
#Service
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class CockroachPersister {
private static final String X_AMZN_REQUESTID = "x-amzn-RequestId";
private static final String X_AMZN_RESPONSE = "x-amzn-Response";
private static final String PUTITEM = "PutItem";
private static final String GETITEM = "GetItem";
private static final String DELETEITEM = "DeleteItem";
private static final String UPDATEITEM = "UpdateItem";
public <T extends Message> T save(final String requestBody, final String action, final String tableName) {
T t = null;
try {
List<GRPCMapper> lGRPCMapper = ServiceMapper.getServices(action,tableName);
for (GRPCMapper grpcMapper : lGRPCMapper) {
System.out.println("grpcMapper.getClassName() ==> "+grpcMapper.getClassName());
Class<?> className = Class.forName(grpcMapper.getClassName());
Class<?> implementedClassType = Class.forName(grpcMapper.getImplementedClass());
Method userMethod = implementedClassType.getDeclaredMethod(grpcMapper.getServiceName(), className);
System.out.println("userMethod\t" + userMethod.getName());
t = (T) userMethod.invoke(null, ProtoUtil.getInstance(requestBody, grpcMapper.getProtoType()));
System.out.printf("Service => %s row(s) Inserted \n", t.getAllFields().toString());
}
} catch (Exception e) {
e.printStackTrace();
}
return t;
}
}
If the initial insertion failed, I would like to try at least 3 TIMES before we can log the error. How do I implement that?
A solution that use message queue will be also acceptable.
Can anyone explain me why if I have my class so defined
public abstract class GenericClient
{
private static final Logger LOG = LoggerFactory.getLogger(GenericClient.class);
#Inject
#ConfigProperty( name = "dirx-ws.url" )
private String url;
private final Client client;
private HttpHeaders httpHeaders;
public GenericClient()
{
this.client = ClientBuilder.newClient();
}
public void setHttpHeaders( final HttpHeaders httpHeaders )
{
this.httpHeaders = httpHeaders;
}
protected HttpHeaders getHttpHeaders()
{
return this.httpHeaders;
}
protected Client getClient()
{
return this.client;
}
protected MultivaluedMap<String, Object> getHeaders()
{
final MultivaluedMap<String, String> myHttpHeaders = this.httpHeaders.getRequestHeaders();
final MultivaluedMap<String, Object> result = new MultivaluedHashMap<String, Object>();
myHttpHeaders.forEach(( name, values ) -> {
result.putSingle(name, values.size() != 1 ? values : values.get(0));
});
printHeaderInfo();
return result;
}
protected UriBuilder createURIBuilder( final String... paths )
{
final UriBuilder uriBuilder = UriBuilder.fromUri(this.url);
for (final String path : paths)
{
uriBuilder.path(path);
}
return uriBuilder;
}
}
there is no error, while if I define it this way (the only changes are private UriBuilder createdURI; and and the updated method createURIBuilder)
public abstract class GenericClient
{
private static final Logger LOG = LoggerFactory.getLogger(GenericClient.class);
#Inject
#ConfigProperty( name = "dirx-ws.url" )
private String url;
private final Client client;
private HttpHeaders httpHeaders;
private UriBuilder createdURI;
public GenericClient()
{
this.client = ClientBuilder.newClient();
}
public void setHttpHeaders( final HttpHeaders httpHeaders )
{
this.httpHeaders = httpHeaders;
}
protected HttpHeaders getHttpHeaders()
{
return this.httpHeaders;
}
protected Client getClient()
{
return this.client;
}
protected MultivaluedMap<String, Object> getHeaders()
{
final MultivaluedMap<String, String> myHttpHeaders = this.httpHeaders.getRequestHeaders();
final MultivaluedMap<String, Object> result = new MultivaluedHashMap<String, Object>();
myHttpHeaders.forEach(( name, values ) -> {
result.putSingle(name, values.size() != 1 ? values : values.get(0));
});
printHeaderInfo();
return result;
}
protected final UriBuilder createURIBuilder( final String... paths )
{
if (this.createdURI == null)
{
this.createdURI = UriBuilder.fromUri(this.url);
for (final String path : paths)
{
this.createdURI.path(path);
}
}
return this.createdURI;
}
}
I get the error
WELD-001410: The injection point has non-proxyable dependencies:
[BackedAnnotatedField] #Inject private
ch.ethz.id.sws.iamws.webservices.endpoint.RequestInterceptor.userClient
[INFO] at
ch.ethz.id.sws.iamws.webservices.endpoint.RequestInterceptor.userClient(RequestInterceptor.java:0)
My RequestScoped UserClient class has (only) an empty public constructor and it extends GenericClient.
Problem solved. Removing final from createURIBuilder method did the job.
I would be glad if someone could explain me anyway the reason why setting the method as 'final' is a problem.
I'm writing tests for RESTful services in a legacy application using Resteasy Client and Embedded Jaxrs Server. I'm facing problems with Serialization/Deserialization using Jackson with a peculiar Entity, which I'm not supposed to do any changes. This Entity has:
A public "getter" that only performs logic (no equivalent field) and should be serializable, but not deserializable;
A private field used for certain controls and persistence, but without public getter/setter, shouldn't be Serialized/Deserialized;
While in the application our Front-end never sends those attributes back (so they're never deserialized), in my unit tests they are failing due to "org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field". If both properties above have their public getters/setters the test won't fail, but then again I'm not supposed to do changes to the code and those methods would break the architeture of those classes.
I've tweaked my Object Mapper but no configuration was able to deal with this scenario. Either one or another ends up being not found. Is there a way to turn of serialization in those cases by adjusting only my Object Mapper to ignore those properties? Or is it mandatory to have the code altered somehow?
Thanks!
(Small sample)
// Entity
public class Entidade {
private int numero;
private String dado;
private boolean naoUsado = false;
public Entidade() { /**/ }
public Entidade(int numero, String dado) {
this.numero = numero;
this.dado = dado;
}
public int getNumero() {
return numero;
}
public void setNumero(int numero) {
this.numero = numero;
}
public String getDado() {
return dado;
}
public void setDado(String dado) {
this.dado = dado;
}
public String getTeste() {
return "Regra de negócio de leitura";
}
}
// Embedded Server Helper
public class ServidorEmbarcadoRestEasy {
private static final int POOL_SIZE = 9999;
private static final int PORTA = 5555;
private static final String HOST = "localhost";
private TJWSEmbeddedJaxrsServer servidor;
private ResteasyClient resteasyClient;
private ServidorEmbarcadoRestEasy(Object webservice, Class<?>... providers) {
this.servidor = new TJWSEmbeddedJaxrsServer();
this.resteasyClient = new ResteasyClientBuilder().
connectionPoolSize(POOL_SIZE).
connectionCheckoutTimeout(100, TimeUnit.MILLISECONDS).
build();
servidor.setPort(PORTA);
servidor.setBindAddress(HOST);
servidor.getDeployment().getResources().add(webservice);
servidor.setSecurityDomain(null);
servidor.start();
ResteasyProviderFactory factory = servidor.getDeployment().getDispatcher().getProviderFactory();
for (Class<?> c : providers) {
factory.registerProvider(c);
}
}
public static ServidorEmbarcadoRestEasy iniciar(Object webservice, Class<?>... providers) {
ServidorEmbarcadoRestEasy servidorEmbarcado = new ServidorEmbarcadoRestEasy(webservice, providers);
return servidorEmbarcado;
}
public void fechar() {
servidor.stop();
}
private String getUrlCompleta(String uri) {
final String base = "http://%s:%s/%s";
return String.format(base, HOST, PORTA, uri);
}
private Builder request(String uri, List<String> queryParams) {
String urlCompleta = getUrlCompleta(uri);
ResteasyWebTarget target = resteasyClient.target(urlCompleta);
for (String s : queryParams) {
String[] chaveValor = s.split("=");
String chave = chaveValor[0];
String valor = chaveValor[1];
target = target.queryParam(chave, valor);
}
return target.request();
}
private Builder request(String uri) {
if (uri.contains("?")) {
String[] urlFracionada = uri.split("\\?");
String baseUri = urlFracionada[0];
String paramString = urlFracionada[1];
String[] paramArray = paramString.split("&");
List<String> parametros = new ArrayList<>(Arrays.asList(paramArray));
if (!parametros.isEmpty()) {
return request(baseUri, parametros);
}
}
String urlCompleta = getUrlCompleta(uri);
return resteasyClient.target(urlCompleta).request();
}
public Response get(String url) {
return request(url).buildGet().invoke();
}
public Response delete(String url) {
return request(url).buildDelete().invoke();
}
public Response put(String url, Object payload) {
return request(url).buildPut(Entity.json(payload)).invoke();
}
public Response post(String url, Object payload) {
return request(url).buildPost(Entity.json(payload)).invoke();
}
}
// Jackson Provider
public class JacksonTestsProvider extends JacksonJsonProvider {
public static ObjectMapper getMapper() {
return new ObjectMapper()
.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
#Override
public void writeTo(Object arg0, Class<?> arg1, Type arg2, Annotation[] arg3, MediaType arg4,
MultivaluedMap<String, Object> arg5, OutputStream arg6) throws IOException {
super.setMapper(getMapper());
super.writeTo(arg0, arg1, arg2, arg3, arg4, arg5, arg6);
}
#Override
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException {
super.setMapper(getMapper());
return super.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream);
}
}
// Unit Tests
public class ServidorEmbarcadoRestEasyTest {
#Path("meuservico")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public static class ServicoTeste {
private Map<Integer, Entidade> entidades = new HashMap<>();
public ServicoTeste() {
for (int i = 1; i <= 100; i++) {
entidades.put(i, new Entidade(i, "Teste " + i));
}
}
#GET
#Path("minha-entidade")
public Response getEntidade() {
return Response.ok(new Entidade(9999, "Nova entidade")).build();
}
#GET
#Path("minha-entidade/{id}")
public Response getEntidade(#PathParam("id") Integer id) {
Entidade retorno = entidades.get(id);
if (retorno == null) {
return Response.status(Status.BAD_REQUEST).build();
} else {
return Response.ok(retorno).build();
}
}
}
private static ServidorEmbarcadoRestEasy servidor;
private static ServicoTeste servico;
#BeforeClass
public static void setUpBeforeClass() throws Exception {
servico = new ServicoTeste();
servidor = ServidorEmbarcadoRestEasy.iniciar(servico,
JacksonTestsProvider.class);
}
#AfterClass
public static void tearDownAfterClass() throws Exception {
servidor.fechar();
}
#Test
public void deveFazerGetComSucesso() {
Response response = servidor.get("meuservico/minha-entidade");
assertEquals(Status.OK.getStatusCode(), response.getStatus());
Entidade entidade = response.readEntity(Entidade.class);
assertEquals(9999, entidade.getNumero());
assertEquals("Nova entidade", entidade.getDado());
response = servidor.get("meuservico/minha-entidade/98");
assertEquals(Status.OK.getStatusCode(), response.getStatus());
entidade = response.readEntity(Entidade.class);
assertEquals(98, entidade.getNumero());
assertEquals("Teste 98", entidade.getDado());
}
}
First I do one override to the RequestMappingHandlerMapping as below
public class RestAnnotationHandler extends RequestMappingHandlerMapping{
Logger logger = LoggerFactory.getLogger(getClass());
#Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = null;
List<RequestMethod> requestMethod = new ArrayList<>(RequestMethod.values().length);
String[] value = null;
if(method.isAnnotationPresent(Post.class)){
Post postAnnotation = AnnotationUtils.findAnnotation(method, Post.class);
requestMethod.add(RequestMethod.POST);
value = postAnnotation.value();
}else if(method.isAnnotationPresent(Get.class)){
Get getAnnotation = AnnotationUtils.findAnnotation(method, Get.class);
requestMethod.add(RequestMethod.GET);
value = getAnnotation.value();
}else if(method.isAnnotationPresent(Put.class)){
Put putAnnotation = AnnotationUtils.findAnnotation(method, Put.class);
requestMethod.add(RequestMethod.PUT);
value = putAnnotation.value();
}else if(method.isAnnotationPresent(Delete.class)){
Delete deleteAnnotation = AnnotationUtils.findAnnotation(method, Delete.class);
requestMethod.add(RequestMethod.DELETE);
value = deleteAnnotation.value();
}else if(method.isAnnotationPresent(RequestMapping.class)){
RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
requestMethod.addAll(Arrays.asList((annotation.method()!=null && annotation.method().length>0)?annotation.method():RequestMethod.values()));
value = annotation.value();
}
final String[] requestMappingValue = value;
if(requestMethod.size() > 0){
final RequestMethod[] requestMappingRequestMethod = requestMethod.toArray(new RequestMethod[requestMethod.size()]);
logger.info("Open a web rest annotation: {} and method {}!", requestMappingValue, requestMappingRequestMethod.toString());
RequestMapping methodAnnotation = new RequestMapping() {
#Override
public Class<? extends Annotation> annotationType() {
return RequestMapping.class;
}
#Override
public String name() {
return "";
}
#Override
public String[] value() {
return requestMappingValue;
}
#Override
public String[] produces() {
return new String[]{};
}
#Override
public String[] params() {
return new String[]{};
}
#Override
public RequestMethod[] method() {
return requestMappingRequestMethod;
}
#Override
public String[] headers() {
return new String[]{};
}
#Override
public String[] consumes() {
return new String[]{};
}
#Override
public String[] path() {
return requestMappingValue;
}
};
RequestCondition<?> methodCondition = getCustomMethodCondition(method);
info = createRequestMappingInfo(methodAnnotation, methodCondition);
RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnotation != null) {
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
}
}
return info;
}
}
then I config it in spring context.xml loaded by org.springframework.web.context.ContextLoaderListener, so this bean will be in root WebApplicationContext.
<bean id="RequestMappingHandlerMapping" class="com.hisoka.handler.RestAnnotationHandler"/>
Now we can use my rest Annotation to annotate one control method, and i can visit that method on browser.
#Documented
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Get {
String[] value() default{};
}
#Get("/")
#ResponseBody
public ModelAndView index() {
Map<String, Object> result = new HashMap<String, Object>();
result.put("name", "Hinsteny");
return new ModelAndView("home").addObject("name", "Hinsteny Hisoka");
}
But when i config DefaultServletHttpRequestHandler to deal static resource, and myself rest Annotation can't work properly
<mvc:default-servlet-handler/>
Question: How to make my rest Annotation and DefaultServletHttpRequestHandler work together in springmv? please help me
I have a decent amount of experience with REST and JSON, but I'm failing at coming up with a way to read some JSON as a Java object.
The response is here: https://api.kraken.com/0/public/OHLC?pair=XBTCZEUR&interval=60
Notice how one of the names (the relevant data) is dependent on a query parameter. I'm not sure how to create a Java object for Gson to use for deserialization, as one of the variable names can change.
I thought that maybe using a JsonReader to read the response in a streaming fashion might work, but when I do this I get a 403 error response.
Any ideas?
If you don't have exact knowledge regarding what the response will contain, you can always use an implementation of map class to pass on to gson, as I have tried to demonstrate in here :
public class RestResponse {
private boolean success;
private String errorDescription;
private Map<String, Object> data;
private static Gson GSON = new Gson();
private RestResponse()
{
data = new HashMap<String, Object>();
}
public boolean isSuccess() {
return success;
}
private void setSuccess(boolean success) {
this.success = success;
}
public String getErrorDescription() {
return errorDescription;
}
private void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
public Object getData(String... nestedKeys)
{
List<String> nestedKeysAsList = Arrays.asList(nestedKeys);
return getData(nestedKeysAsList);
}
public Object getData(List<String> nestedKeys)
{
String firstKey = nestedKeys.get(0);
if(!data.containsKey(firstKey))
throw new IllegalArgumentException("Key not found");
Object mapValue = data.get(firstKey);
if(!(mapValue instanceof Map))
return mapValue;
String finalKey = nestedKeys.get(nestedKeys.size()-1);
if(nestedKeys.size() > 2)
{
for(String nextKey : nestedKeys.subList(1,nestedKeys.size()-1))
{
Map<String,Object> tempMap = (Map)mapValue;
mapValue = tempMap.get(nextKey);
}
}
Map<String,Object> tempMap = (Map)mapValue;
return tempMap.get(finalKey);
}
private Map<String, Object> getData() {
return data;
}
private void setData(Map<String, Object> map){
this.data = map;
}
public static RestResponse createUnsuccessfulResponse(Exception e)
{
return createUnsuccessfulResponse(e.getMessage());
}
public static RestResponse createUnsuccessfulResponse(String reason)
{
RestResponse res = new RestResponse();
res.setSuccess(false);
res.setErrorDescription(reason);
return res;
}
public static RestResponse createSuccessfulResponse(String jsonString)
{
Map<String, Object> jsonToDataMap = GSON.fromJson(jsonString, Map.class);
return createSuccessfulResponseByMap(jsonToDataMap);
}
private static RestResponse createSuccessfulResponseByMap(Map<String, Object> jsonToDataMap)
{
RestResponse res = new RestResponse();
res.setSuccess(true);
res.setErrorDescription("Success");
res.setData(jsonToDataMap);
return res;
}
}
Usage examples can be found over here :
https://github.com/cgunduz/btcenter/blob/master/src/main/java/com/cemgunduz/utils/entity/RestResponse.java