Getting details from database Spring Boot , Exception error - java

I am trying to get data by multiple data from database on the basis of multiple Ids using Spring boot.
Basically it is a GET call which takes request parameters as a list of IDs and return response accordingly. IDs are unique in database
Url : api/details/1a,2a,3b
I am getting response as:
Get(value = "api/details/{Ids})
{
[id="1a",name="Raj", interest="Football"],
[id="2a",name="Tom", interest="Cricket"]
[id="3b",name="Kane", interest="Baseball"]
}
It is fine. But when i am giving a wrong Id, I am getting response as:
Url : api/details/xyz,abc,3b
{
null,
null,
[id="3b",name="Kane", interest="Baseball"]
}
I am expecting that instead of null it show say that the ID is not present along with Status code. Something like
{
2-Not found,3-Not Found,
id="3b",name="Kane", hobby="Baseball,
}
My controller class is like:
#RequestMapping(value = "api/details{Ids}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Game>>
getMultipleDetails(#PathVariable("Idss") String Idss) {
HttpHeaders headers = new HttpHeaders();
List<String> ids = Arrays.asList(Idss.split(","));
List<Game> list = new ArrayList<>();
Game details= null;
for (String id : ids) {
details= da.getMultipleDetails(id);
list.add(devices);
}
if (details== null) {
throw new RuntimeException(HttpStatus.NOT_FOUND.toString());
}
return new ResponseEntity<List<Game>>(list, headers, HttpStatus.OK);
}
}
My repository class is like:
public Device getMultipleDetails(String id) {
Game details= null;
try {
details= jdbcTemplate.queryForObject("SELECT * FROM table_name WHERE Id = ?",new DeviceRowMapper(), id);
} catch (Exception e) {
// Log the system generated Id
String systemRefId = String.valueOf(System.currentTimeMillis());
LOGGER.error(systemRefId, e);
//throw new DatabaseException(systemRefId, e);
}
return details;
}
Game is my model class that conatins id, name, hobby

As you're setting the ResponseEntity<List<Game>> you should only return a List with Game objects inside.
Not sure why you want to return the failed ones in the same List but as a workaround I will set id of the not found and, in the fields name and Game I will set 'Not found' instead of returning null objects. For example:
public Device getMultipleDetails(String id) {
Game details = new Game();
try {
details= jdbcTemplate.queryForObject("SELECT * FROM table_name WHERE Id = ?",new DeviceRowMapper(), id);
//If details is not null but it's empty
if (StringUtils.IsEmpty(details.getId())) {
details.setId(id);
details.setName("Not Found");
details.setGame("Not Found");
}
} catch (Exception e) {
// Log the system generated Id
String systemRefId = String.valueOf(System.currentTimeMillis());
LOGGER.error(systemRefId, e);
//If details is null it will trow null pointer exception
details = new Game();
details.setId(id);
details.setName("Not Found");
details.setGame("Not Found");
}
return details;
}
I strongly recommend you to rename the field Game in you Game class. A field should not duplicate the name of its containing class.
It's confusing to have a class member with the same name (case differences aside) as its enclosing class. This is particularly so when you consider the common practice of naming a class instance for the class itself.
Best practice dictates that any field or member with the same name as the enclosing class be renamed to be more descriptive of the particular aspect of the class it represents or holds.
I would recommend to rename it to something like typeOfGame for example.

You should manage the empty objects, and manage the message also, the code should be like this, because if not, only the last detail is the one evaluated, thats why the exception is not raised.
for (String id : ids) {
details= da.getMultipleDetails(id);
list.add(devices);
if (details== null) {
throw new RuntimeException(HttpStatus.NOT_FOUND.toString());
}
}

Related

Java Spring - Request rejected because no multipart boundary was found, when trying to upload PDF file

PLEASE READ THE EDIT BELOW
I am trying to create a file archive program that receives a POST request with a pdf file in it's requestBody embedded and then save that pdf file directly into a Database.
But I receive a "unsupported Media Type" and "Content type 'application/pdf;charset=UTF-8' not supported" error message on Postman, as well as this in Eclipse when sending the request:
2020-05-25 11:20:58.551 WARN 3944 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/pdf;charset=UTF-8' not supported]
below is my current code. I've looked up multiple similar questions on here, but to no avail.
I am using Postman to send out my POST requests.
Here is the code of my Controller Class.
private final DocumentRepository docRepository;
private final DocumentService documentService;
/*#Autowired
public Controller(DocumentRepository docRepository, FileStorage fileStorage, DocumentService documentService) {
this.docRepository = docRepository;
this.fileStorage = fileStorage;
this.doc
}*/
// Just for testing purposes
#GetMapping("/files")
public List<Document> files() {
return docRepository.findAll();
}
// POST Method. Add a new entry to the Database.
// WIP. Works, but creates 2 new columns "file_name" and "size_in_bytes" with the values
// instead of inserting them under the actual made "fileName" and "sizeInBytes" columns
#PostMapping(value="/document", consumes=MediaType.APPLICATION_PDF_VALUE)
public Optional<Document> postDocument(#RequestHeader(value="fileName") String fileName, #RequestBody MultipartHttpServletRequest file
) {
Optional<Document> result = documentService.postToDB(fileName, file);
return result;
}
}
And then the code for the Service Class that handles the logic.
#Service
public class DocumentService {
public static String directory = System.getProperty("user.dir")+ "/test_uploads";
private final DocumentRepository docRepository;
public DocumentService(DocumentRepository docRepository) {
this.docRepository = docRepository;
}
public Optional<Document> postToDB(String fileName, MultipartHttpServletRequest file) {
// temporary counter to see if there already is another entry with the same name
// temporary id to compare all the ids and iterate the one with the biggest value.
int counter=0;
int temp_id=0;
Timestamp current_timestamp = new Timestamp(Calendar.getInstance().getTime().getTime());
// This loop is supposed to look if there already is an entry with the same file name, if yes, then the counter will get iterated by 1.
// Bugged. It will still add a new entry that has a identical file name with another entry as of right now.
for(int i = 0; i < docRepository.count(); i++) {
if(docRepository.findAll().get(i).getFileName() == fileName) {
counter = counter + 1;
}
}
// This checks if the counter is less than one, thus asking if there are 1 or more file name duplicates
if (counter < 1) {
// This gets every ID of every entry and saves them on the temporary variable
for(int i = 0; i < docRepository.count(); i++) {
temp_id= docRepository.findAll().get(i).getId();
// It is then checked if the next entry is bigger than the previously saved value, if yes, then the variable gets overwritten
// with the bigger ID until the biggest ID stands.
if(docRepository.findAll().get(i).getId() > temp_id) {
temp_id = docRepository.findAll().get(i).getId();
}
}
// after the for-loop closes, the new File will get added with the biggest ID +1 and the fileName in the Header of the POST request.
Optional<Document> service_result;
try {
service_result = DataAccess.saveEntry(fileName,file, temp_id, docRepository, current_timestamp, 1 );
return service_result;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
else {
return Optional.empty();
}
}
}
and then finally, the DataAcces Class that saves the requests.
public class DataAccess {
public static Optional<Document> saveEntry(String fileName, MultipartHttpServletRequest file, Integer temp_id, DocumentRepository docRepository, Timestamp updated, Integer version) throws IOException {
docRepository.save(new Document(temp_id +1,fileName+ ".pdf",file.getFile(fileName).getBytes(), file.getFile(fileName).getSize(),updated, version));
return docRepository.findById(temp_id +1);
}
}
EDIT 1: I changed consumes to form-data and I send the POST request in Postman with a custom content-type header with application/form-data.
#PostMapping(value="/document", consumes="multipart/form-data")
public Optional<Document> postDocument(#RequestHeader(value="fileName") String fileName, #RequestBody MultipartHttpServletRequest file
) {
Optional<Document> result = documentService.postToDB(fileName, file);
return result;
}
Now it seems to work, but I now get a new exception:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found

Integrating javax validation with JavaFX

I have worked on several Spring MVC projects where the validation could be done very simply like such :
CONTROLLER
#RequestMapping(value = {"/newHeightUnit"}, method = RequestMethod.POST)
public String saveHeightUnit(#Valid HeightUnit heightUnit, BindingResult result, ModelMap model)
{
boolean hasCustomErrors = validate(result, heightUnit);
if ((hasCustomErrors) || (result.hasErrors()))
{
setPermissions(model);
return "heightUnitDataAccess";
}
heightUnitService.save(heightUnit);
session.setAttribute("successMessage", "Successfully added height unit \"" + heightUnit.getName() + "\"!");
return "redirect:/heightUnits/list";
}
private boolean validate(BindingResult result, HeightUnit heightUnit)
{
boolean hasCustomErrors = false;
if (heightUnitService.nameExists(heightUnit))
{
FieldError error = new FieldError("heightUnit", "name", heightUnit.getName(), false, null, null,
heightUnit.getName() + " already exists!");
result.addError(error);
hasCustomErrors = true;
}
return hasCustomErrors;
}
This would validate the entity against whatever validation annotation it had (#NotNull, #Size, #Digits, etc).
How can the same be achieved in JavaFX? I have 9 entities all with their validation annotations as I was doing in my MVC projects. I am using Spring with what you could call a view / service / dao structure. I do not use FXML at all, my UI components are all generated in pure Java and I intend for it to stay that way.
How can I use the validation annotations on my entities in a similarly friendly approach to that of Spring MVC?
Clarifications
Just for reference, this is how my entities are currently saved. There is currently no validation of the user's inputs whatsoever when they are added but everything works perfectly fine. My entities are all annotated and ready to go and i'm just looking to learn how to integrate the good ol' #Valid into the mix:
#Override
public void saveEntity()
{
TextField nameField = (TextField)formFields.get(0);
try
{
Category newCategory = new Category(null, nameField.getText(), new Date(), null);
categoryService.save(newCategory);
}
catch (Exception ex)
{
logger.error("Error adding category : " + ex.getMessage());
}
}
Thanks!
So i ended up with a pretty clean result. First off i ended up with a validator class that looks something like this :
public class EntityValidator
{
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
public Set<ConstraintViolation<Category>> validateCategory(Category category)
{
return validator.validate(category);
}
}
I am using Spring to make this class available for autowiring :
#Bean
public EntityValidator entityValidator()
{
return new EntityValidator();
}
The bean validation goes something like this :
TextField nameField = (TextField)formFields.get(0);
try
{
Category newCategory = new Category(null, nameField.getText(), new Date(), null);
Set<ConstraintViolation<Category>> errors = validator.validateCategory(newCategory);
if (errors.isEmpty())
{
categoryService.save(newCategory);
close();
}
else
{
showErrorMessages(errors);
}
}
catch (Exception ex)
{
logger.error("Error adding category : " + ex.getMessage());
}
The showErrorMessages method just takes the error Set and displays the first error in an error dialog. Since i am using validation groups, there is never more than one error in the Set so this all looks pretty clean. It will never be as simple as doing it from a controller in a web project but i'm pretty happy with the result overall.
Cheers

Manipulate with cache as with collection in Spring

I looked a lot of stuff on on internet but I don't found any solution for my needs.
Here is a sample code which doesn't work but show my requirements for better understanding.
#Service
public class FooCachedService {
#Autowired
private MyDataRepository dataRepository;
private static ConcurrentHashMap<Long, Object> cache = new ConcurrentHashMap<>();
public void save(Data data) {
Data savedData = dataRepository.save(data);
if (savedData.getId() != null) {
cache.put(data.getRecipient(), null);
}
}
public Data load(Long recipient) {
Data result = null;
if (!cache.containsKey(recipient)) {
result = dataRepository.findDataByRecipient(recipient);
if (result != null) {
cache.remove(recipient);
return result;
}
}
while (true) {
try {
if (cache.containsKey(recipient)) {
result = dataRepository.findDataByRecipient(recipient);
break;
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return result;
}
}
and data object:
public class Data {
private Long id;
private Long recipient;
private String payload;
// getters and setters
}
As you can see in code above I need implement service which will be stored new data into database and into cache as well.
Whole algorithm should looks something like that:
Some userA create POST request to my controller to store data and it fire save method of my service.
Another userB logged in system send request GET to my controller which fire method load of my service. In this method is compared logged user's id which sent request with recipients' ids in map. If map contains data for this user they are fetched with repository else algorithm check every second if there are some new data for that user (this checking will be some timeout, for example 30s, and after 30s request return empty data, and user create new GET request and so on...)
Can you tell me if it possible do it with some elegant way and how? How to use cache for that or what is the best practice for that? I am new in this area so I will be grateful for any advice.

Java - How to delete an entity from Google Cloud Datastore

Architecture: I have a web application from where I'm interacting with the Datastore and a client (raspberry pi) which is calling methods from the web application using Google Cloud Endpoints.
I have to add that I'm not very familiar with web applications and I assume that something's wrong with the setConsumed() method because I can see the call of /create in the app engine dashboard but there's no entry for /setConsumed.
I'm able to add entities to the Datastore using objectify:
//client method
private static void sendSensorData(long index, String serialNumber) throws IOException {
SensorData data = new SensorData();
data.setId(index+1);
data.setSerialNumber(serialNumber);
sensor.create(data).execute();
}
//api method in the web application
#ApiMethod(name = "create", httpMethod = "post")
public SensorData create(SensorData data, User user) {
// check if user is authenticated and authorized
if (user == null) {
log.warning("User is not authenticated");
System.out.println("Trying to authenticate user...");
createUser(user);
// throw new RuntimeException("Authentication required!");
} else if (!Constants.EMAIL_ADDRESS.equals(user.getEmail())) {
log.warning("User is not authorised, email: " + user.getEmail());
throw new RuntimeException("Not authorised!");
}
data.save();
return data;
}
//method in entity class SensorData
public Key<SensorData> save() {
return ofy().save().entity(this).now();
}
However, I'm not able to delete an entity from the datastore using the following code.
EDIT: There are many logs of the create-request in Stackdriver Logging, but none of setConsumed(). So it seems like the calls don't even reach the API although both methods are in the same class.
EDIT 2: The entity gets removed when I invoke the method from the Powershell so the problem is most likely on client side.
//client method
private static void removeSensorData(long index) throws IOException {
sensor.setConsumed(index+1);
}
//api method in the web application
#ApiMethod(name = "setConsumed", httpMethod = "put")
public void setConsumed(#Named("id") Long id, User user) {
// check if user is authenticated and authorized
if (user == null) {
log.warning("User is not authenticated");
System.out.println("Trying to authenticate user...");
createUser(user);
// throw new RuntimeException("Authentication required!");
} else if (!Constants.EMAIL_ADDRESS.equals(user.getEmail())) {
log.warning("User is not authorised, email: " + user.getEmail());
throw new RuntimeException("Not authorised!");
}
Key serialKey = KeyFactory.createKey("SensorData", id);
datastore.delete(serialKey);
}
This is what I follow to delete an entity from datastore.
public boolean deleteEntity(String propertyValue) {
String entityName = "YOUR_ENTITY_NAME";
String gql = "SELECT * FROM "+entityName +" WHERE property= "+propertyValue+"";
Query<Entity> query = Query.newGqlQueryBuilder(Query.ResultType.ENTITY, gql)
.setAllowLiteral(true).build();
try{
QueryResults<Entity> results = ds.run(query);
if (results.hasNext()) {
Entity rs = results.next();
ds.delete(rs.getKey());
return true;
}
return false;
}catch(Exception e){
logger.error(e.getMessage());
return false;
}
}
If you don't want to use literals, you can also use binding as follows:
String gql = "SELECT * FROM "+entityName+" WHERE property1= #prop1 AND property2= #prop2";
Query<Entity> query = Query.newGqlQueryBuilder(Query.ResultType.ENTITY, gql)
.setBinding("prop1", propertyValue1)
.setBinding("prop2", propertyValue2)
.build();
Hope this helps.
I was able to solve it by myself finally!
The problem was just related to the data type of the index used for removeSensorData(long index) which came out of a for-loop and therefore was an Integer instead of a long.

Why is my setter called twice?

I am working on a REST web service, using JAX-RS, JPA and JAXB, for the management of games and their highscores. A game has the following properties: name, url and highscoreTableSize.
A short description of what I'm trying to do: I have the createRow() method in the controller which consumes JSON (the JSON serialization of a Game object, class Game being annotated with #XmlRootElement), which calls the static createRow() from the Game model class, and inside of it the setUrl() is called. The thing is that, for some reason, the setter is called twice.
Now what it happens is that, if the url sent in the body of the request is not valid against a pattern, after the "mysterious" first call it becomes null, and the second time the setter is called, it goes inside if (url == null), instead of going inside if (!matcher.matches()), when actually the latter is the real situation, because I've sent a mistyped URL.
Does anybody know why this is happening and how can I solve this?
Thank you in advance!
Class Game:
#Entity
#Table(name="games")
#XmlRootElement(name = "Game")
public class Game implements Serializable {
//properties
public void setUrl(String url) throws CustomWebServiceException {
String regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]";
Pattern pattern = Pattern.compile(regex);
System.out.println("URL: " + url);
if ( url == null || url.length() == 0) {
throw new CustomWebServiceException(Response.Status.BAD_REQUEST, new ErrorMessage("The url of the game is mandatory!"));
} else {
Matcher matcher = pattern.matcher(url);
if (!matcher.matches()) {
throw new CustomWebServiceException(Response.Status.BAD_REQUEST, new ErrorMessage("The url is invalid! Please check its syntax!"));
} else {
this.url = url;
}
}
}
public static Response createRow(EntityManager em, UserTransaction ut, String name, Game gameData) throws Exception {
ut.begin();
Game _game = em.find(Game.class, name);
if (_game != null) {
Util.tryRollback(ut);
ErrorMessage errorMessage = new ErrorMessage(
"The game with name " + name
+ " already exists in the database!");
throw new CustomWebServiceException(Response.Status.CONFLICT,
errorMessage);
}
String url = gameData.getUrl();
Integer highscoreTableSize = gameData.getHighscoreTableSize();
Game newGame = new Game();
newGame.setName(name);
newGame.setUrl(url);
newGame.setHighscoreTableSize(highscoreTableSize);
em.persist(newGame);
// force the persistence manager to save data to DB
ut.commit();
if (highscoreTableSize == null) {
highscoreTableSize = 7;
}
SuccessfulRequestMessage succesfulRequestMessage = new SuccessfulRequestMessage(
" Game entry created with name: " + name
+ ", url: " + url + " and highscoreTableSize: " + highscoreTableSize
+ ".");
return Response.status(Status.CREATED).entity(succesfulRequestMessage).type(MediaType.APPLICATION_JSON).build();
}
}
Controller:
#PUT
#Path("/{name}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response createRow(
#PathParam("name") String name,
Game gameData) throws CustomWebServiceException {
try {
return Game.createRow(em, ut, name, gameData);
} catch (SystemException | NotSupportedException | IllegalStateException | SecurityException | HeuristicMixedException
| HeuristicRollbackException | RollbackException e) {
Util.tryRollback(ut);
ErrorMessage errorMessage = new ErrorMessage(
"Error when trying to create entry:" + e.toString()
+ " with message: " + e.getMessage());
throw new CustomWebServiceException(
Response.Status.INTERNAL_SERVER_ERROR, errorMessage);
} catch (CustomWebServiceException e) {
throw e;
} catch (Exception e) {
Util.tryRollback(ut);
ErrorMessage errorMessage = new ErrorMessage(
"During creation of game data, the following error(s) was(were) encountered: "
+ e.toString());
throw new CustomWebServiceException(Response.Status.BAD_REQUEST,
errorMessage);
}
}
Well, it should be called twice as per your code. Once during deserialization and once you do it yourself:
newGame.setUrl(url);
Using the same class for model and for representation is a bad idea in general.
IMHO,What you should do:
Separate your "JSON" game from the object you save in the database
Don't do validation in your setters. There is a Spring Validation for that. Use that to make sure that your JSON object is valid and then just go directly for the database.
You can use dozer to automatically convert model object to representation objects and vice versa
Edit:
Without using any libraries the easiest thing you can do is to move validation to a method in your controller:
void validateInput(Game game) throws Exception {
if (game == null) {
throw new Exception("Game object is not present in the request");
}
if (game.getUrl() == null || !game.maches({some-fancyreg-exp}) {
throw new Exception("Game URL is not valid");
}
//etc, check the rest of the fields
}
Call validateInput(game) in your controller. After that you can be sure that the input is valid. Quick and dirty. Let setters be setters.

Categories

Resources