I'm trying to integrate Apollo-Android with a server mounted locally, the query to show all of the entries is working fine, but when I try to create a new entry, it sends a query without the parameters.
At first I thought it was a server-side problem, but it doesn't seems like it, since it's creating the entry but with null values.
The server it's made on Spring Boot using SPQR for the schema generation, and SpringData-JPA with Hibernate for the connection to a PostGreSQL database.
This is what the server says:
Hibernate: insert into author (first_name, last_name) values (?, ?)
2018-06-25 14:48:25.479 INFO 6670 --- [nio-8080-exec-3] s.testing.controllers.GraphQLController : {data={createAuthor={__typename=Author, firstName=null, lastName=null}}}
And this is the response I get on Android Studio:
06-25 14:48:24.974 16068-16132/com.example.nburk.apollodemo D/graphcool: Executed mutation: CreateAuthor{__typename=Author, firstName=null, lastName=null}
In Android Studio this is the builder:
private void createPost(String firstname, String lastname) {
application.apolloClient().mutate(
CreateAuthorMutation.builder()
.firstName(firstname)
.lastName(lastname)
.build())
.enqueue(createPostMutationCallback);
}
That enqueue this:
private ApolloCall.Callback<CreateAuthorMutation.Data> createPostMutationCallback = new ApolloCall.Callback<CreateAuthorMutation.Data>() {
#Override
public void onResponse(#Nonnull final Response<CreateAuthorMutation.Data> dataResponse) {
Log.d(ApolloDemoApplication.TAG, "Executed mutation: " + dataResponse.data().createAuthor().toString());
fetchPosts();
}
#Override
public void onFailure(#Nonnull ApolloException e) {
Log.d(ApolloDemoApplication.TAG, "Error:" + e.toString());
}
};
If you need it, here is my mutation:
mutation CreateAuthor($firstName: String, $lastName: String){
createAuthor(firstName: $firstName, lastName: $lastName){
firstName
lastName
}
}
This project is based on this: https://github.com/graphcool-examples/android-graphql/tree/master/quickstart-with-apollo
Uploading my schema too.
Copia de schema.txt
Thanks beforehand for your help.
The problem was from SPQR, you need to add a mapper for the variables
#PostMapping(value = "/graphql", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public Map<String, Object> indexFromAnnotated(#RequestBody Map<String, Object> request, HttpServletRequest raw) {
ExecutionResult executionResult = graphQL.execute(ExecutionInput.newExecutionInput()
.query((String) request.get("query"))
.operationName((String) request.get("operationName"))
// -------- THIS RESOLVES THE VARIABLES--------
.variables((Map<String, Object>) request.get("variables"))
.context(raw)
.build());
LOGGER.info(executionResult.toSpecification().toString());
return executionResult.toSpecification();
}
Related
I'm trying to add custom tags - the path variables and their values from each request - to each metric micrometer generates. I'm using spring-boot with java 16.
From my research i've found that creating a bean of type WebMvcTagsContributor alows me to do just that.
This is the code
public class CustomWebMvcTagsContributor implements WebMvcTagsContributor {
private static int PRINT_ERROR_COUNTER = 0;
#Override
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response,
Object handler,
Throwable exception) {
return Tags.of(getAllTags(request));
}
private static List<Tag> getAllTags(HttpServletRequest request) {
Object attributesMapObject = request.getAttribute(View.PATH_VARIABLES);
if (isNull(attributesMapObject)) {
attributesMapObject = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
if (isNull(attributesMapObject)) {
attributesMapObject = extractPathVariablesFromURI(request);
}
}
if (nonNull(attributesMapObject)) {
return getPathVariablesTags(attributesMapObject);
}
return List.of();
}
private static Object extractPathVariablesFromURI(HttpServletRequest request) {
Long currentUserId = SecurityUtils.getCurrentUserId().orElse(null);
try {
URI uri = new URI(request.getRequestURI());
String path = uri.getPath(); //get the path
UriTemplate uriTemplate = new UriTemplate((String) request.getAttribute(
HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)); //create template
return uriTemplate.match(path); //extract values form template
} catch (Exception e) {
log.warn("[Error on 3rd attempt]", e);
}
return null;
}
private static List<Tag> getPathVariablesTags(Object attributesMapObject) {
try {
Long currentUserId = SecurityUtils.getCurrentUserId().orElse(null);
if (nonNull(attributesMapObject)) {
var attributesMap = (Map<String, Object>) attributesMapObject;
List<Tag> tags = attributesMap.entrySet().stream()
.map(stringObjectEntry -> Tag.of(stringObjectEntry.getKey(),
String.valueOf(stringObjectEntry.getValue())))
.toList();
log.warn("[CustomTags] [{}]", CommonUtils.toJson(tags));
return tags;
}
} catch (Exception e) {
if (PRINT_ERROR_COUNTER < 5) {
log.error("[Error while getting attributes map object]", e);
PRINT_ERROR_COUNTER++;
}
}
return List.of();
}
#Override
public Iterable<Tag> getLongRequestTags(HttpServletRequest request, Object handler) {
return null;
}
}
#Bean
public WebMvcTagsContributor webMvcTagsContributor() {
return new CustomWebMvcTagsContributor();
}
In order to test this, i've created a small spring boot app, added an endpoint to it. It works just fine.
The problem is when I add this code to the production app.
The metrics generates are the default ones and i can't figure out why.
What can I check to see why the tags are not added?
local test project
http_server_requests_seconds_count {exception="None", method="GET",id="123",outcome="Success",status="200",test="test",uri="/test/{id}/compute/{test}",)1.0
in prod - different (& bigger) app
http_server_requests_seconds_count {exception="None", method="GET",outcome="Success",status="200",uri="/api/{something}/test",)1.0
What i've tried and didn't work
Created a bean that implemented WebMvcTagsProvider - this one had an odd behaviour - it wasn't creating metrics for endpoints that had path variables in the path - though in my local test project it worked as expected
I added that log there in order to see what the extra tags are but doesn't seem to reach there as i don't see anything in the logs - i know, you might say that the current user id stops it, but it's not that.
there i had tried to get a Mono from my repository of Mongo. But my debugger is lost after receiving the response from repo as Mono object and applied block() to it.
HERE IS THE DETAILED CODE...
private Map<String, Object> parameters(Bid bid,String tripId) {
final Map<String, Object> parameters = new HashMap<>();
ShipperLoad shipperLoad = (ShipperLoad)bid.getLoad();
// getting supplier data from other service..
SupplierUserDTO supplier =
WebClient.
create("http://localhost:8888/XXXXXXXX/"+bid.getSupplierId())
.get()
.retrieve()
.bodyToMono(SupplierUserDTO.class)
.block();
TripInvoiceDetails tripInvoice = bidService.getInvoiceDetails(tripId).block();
parameters.put("load", shipperLoad);
parameters.put("bid", bid);
parameters.put("logo", getClass().getResourceAsStream(logo_path));
parameters.put("supplier", supplier);
parameters.put("invoice", tripInvoice);
return parameters;
}
Bid service method:
public Mono<TripInvoiceDetails> getInvoiceDetails(String tripId)
{
Mono<TripInvoiceDetails> invoice = tripInvoiceRepository.findByTripId(tripId);
return invoice;
}
Repository
public interface TripInvoiceRepository extends ReactiveMongoRepository<TripInvoiceDetails, String>{
Mono<TripInvoiceDetails> findByTripId(String tripId);
}
The control is lossing on bidService.getInvoiceDetails(tripId).block();
TripInvoiceDetails.java
#Data
#Document("tripInvoice")
#NoArgsConstructor
#AllArgsConstructor
public class TripInvoiceDetails {
#Id
String id;
String invoiceNo;
Double invoiceamount;
Double invoiceamountGst;
ValueLabel packageType;
Integer noOfUnits;
ValueLabel materialType;
List<ValueLabel> materialTypeSecondary;
String hsnCode;
String consigneeName;
String address;
String gstOrPan;
String tripId;
String transporterId;
String shipperName;
String loadId;
}
console log
Resolved
try {
//pls don't remove futureData since. it is required to resolve futureData of Mono..
CompletableFuture<TripInvoiceDetails> futureData = tripInvoice.toFuture();
invoice = tripInvoice.toFuture().get();
} catch (Exception e) {
e.printStackTrace();
}
Thanks all for your valuable response. Finally, I got a solution, I just break the .bock() method into steps, which means the did the exact implementation of the .block() method instead of calling .block() it works for me, maybe the block in the earlier case is waiting for any producer which may be in this case not marked as a producer.
try {
//pls don't remove futureData since. it is required to resolve futureData of Mono..
CompletableFuture<TripInvoiceDetails> futureData = tripInvoice.toFuture();
invoice = tripInvoice.toFuture().get();
} catch (Exception e) {
e.printStackTrace();
}
The goal is to send a HTTP GET request containing a list of string representing the enum values QuestionSubject, then use those parameters to select the questions of the right subject. I also added a custom converter to convert the received string to my enum. My problem is that "subjects" is always null when I debug inside the method.
This is my current REST endpoint:
#ResponseBody
#GetMapping(path = "question/getquestionsbysubjects")
public List<Question> loadQuestionsBySubjects(#RequestParam(required=false) QuestionSubject[] subjects) throws IOException, GeneralSecurityException {
if(subjects == null || subjects.length == 0){
return this.loadAllQuestions();
}
return questionRepository.findByQuestionSubjectIn(Arrays.asList(subjects));
}
I am able to get my questions when passing a single subject in a method with the following signature:
public List<Question> loadQuestionsBySubjects(#RequestParam(required=false) QuestionSubject subject)
So it does not appear to be an issue of converting the string to enum.
I tried sending multiple requests, but subjects is always null in the endpoint. Here's what I already tried using postman:
http://localhost:8080/question/getquestionsbysubjects?subjects=contacts,ko
http://localhost:8080/question/getquestionsbysubjects?subjects=["contacts", "ko"]
http://localhost:8080/question/getquestionsbysubjects?subjects=contacts&subjects=ko
Is there an issue I'm not aware of? Those seems to be working in what I found in other questions.
Here's the converter:
public class StringToQuestionSubjectConverter implements Converter<String, QuestionSubject> {
#Override
public QuestionSubject convert(String source) {
return QuestionSubject.valueOf(source.toUpperCase());
}
public Iterable<QuestionSubject> convertAll(String[] sources) {
ArrayList<QuestionSubject> list = new ArrayList<>();
for (String source : sources) {
list.add(this.convert(source));
}
return list;
}
}
Use a List directly:
#RequestParam(required=false) List<QuestionSubject> subjects
return questionRepository.findByQuestionSubjectIn(subjects);
I have a strage(for me) question to ask. I have created synchronized Service which is called by Controller:
#Controller
public class WebAppApiController {
private final WebAppService webApService;
#Autowired
WebAppApiController(WebAppService webApService){
this.webApService= webApService;
}
#Transactional
#PreAuthorize("hasAuthority('ROLE_API')")
#PostMapping(value = "/api/webapp/{projectId}")
public ResponseEntity<Status> getWebApp(#PathVariable(value = "projectId") Long id, #RequestBody WebAppRequestModel req) {
return webApService.processWebAppRequest(id, req);
}
}
Service layer is just checking if there is no duplicate in request and store it in database. Because client which is using this endpoint is making MANY requests continously it happened that before one request was validated agnist duplicate other the same was put in database - that is why I am trying to do synchronized block.
#Service
public class WebAppService {
private final static String UUID_PATTERN_TO = "[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}";
private final WebAppRepository waRepository;
#Autowired
public WebAppService(WebAppRepository waRepository){
this.waRepository= waRepository;
}
#Transactional(rollbackOn = Exception.class)
public ResponseEntity<Status> processScanWebAppRequest(Long id, WebAppScanModel webAppScanModel){
try{
synchronized (this){
Optional<WebApp> webApp=verifyForDuplicates(webAppScanModel);
if(!webApp.isPresent()){
WebApp webApp=new WebApp(webAppScanModel.getUrl())
webApp=waRepository.save(webApp);
processpropertiesOfWebApp(webApp);
return new ResonseEntity<>(HttpStatus.CREATED);
}
return new ResonseEntity<>(HttpStatus.CONFLICT);
}
} catch (NonUniqueResultException ex){
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
} catch (IncorrectResultSizeDataAccessException ex){
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
}
}
}
Optional<WebApp> verifyForDuplicates(WebAppScanModel webAppScanModel){
return waRepository.getWebAppByRegex(webAppScanModel.getUrl().replaceAll(UUID_PATTERN_TO,UUID_PATTERN_TO)+"$");
}
And JPA method:
#Query(value="select * from webapp wa where wa.url ~ :url", nativeQuery = true)
Optional<WebApp> getWebAppByRegex(#Param("url") String url);
processpropertiesOfWebApp method is doing further processing for given webapp which at this point should be unique.
Intended behaviour is:
when client post request contains multiple urls like:
https://testdomain.com/user/7e1c44e4-821b-4d05-bdc3-ebd43dfeae5f
https://testdomain.com/user/d398316e-fd60-45a3-b036-6d55049b44d8
https://testdomain.com/user/c604b551-101f-44c4-9eeb-d9adca2b2fe9
Only first one will be stored within database but at this moment this is not what is happening. Select from my database:
select inserted,url from webapp where url ~ 'https://testdomain.com/users/[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}$';
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
(3 rows)
I will try to add unique constraint on url column but I can't imagine this will solve the problem while when UUID changes new url will be unique
Could anyone give me a hint what I am doing wrong?
Question is related with the one I asked before but not found proper solution, so I simplified my method but still no success
I am trying to execute a very simple query from Spring JDBCTemplate. I am retrieving one attribute from a record that is identified by primary key. The entirely of the code is shown below. When I do this with a query constructed by concatenation (dangerous and ugly, and currently uncommented) it executes in 0.1 second. When I change my comments and use the parameterized query it executes in 50 seconds. I would much prefer to get the protection that comes with the parameterized query, however 50 seconds seems like a steep price to pay. Any hints how this could be made more reasonable.
public class JdbcEventDaoImpl {
private static JdbcTemplate jtemp;
private static PreparedStatement getJsonStatement;
private static final Logger logger = LoggerFactory.getLogger(JdbcEventDaoImpl.class);
#Autowired
public void setDataSource(DataSource dataSource) {
JdbcEventDaoImpl.jtemp = new JdbcTemplate(dataSource);
}
public String getJdbcForPosting(String aggregationId){
try {
return (String) JdbcEventDaoImpl.jtemp.queryForObject("select PostingJson from PostingCollection where AggregationId = '" + aggregationId + "'", String.class);
//return (String) JdbcEventDaoImpl.jtemp.queryForObject("select PostingJson from PostingCollection where AggregationId = ?", aggregationId, String.class);
} catch (EmptyResultDataAccessException e){
return "Not Available";
}
}
}