I'm currently new to the Spring Boot Java framework and I'm building a simple application. When my service starts, I want to be able to read a raw file from a URL, parse that data, and upload it into my mongodb database of atlas. So far this is what I have:
#Service
public class CoronaVirusDataService {
private List<LocationStats> allConfirmedStats = new ArrayList<>();
MongoOperations mongoOperations;
#PostConstruct // run this method as soon as the application runs
#Scheduled(cron = "* * 1 * * *") // execute this method every day
public void fetchVirusData() {
List<LocationStats> newStats = new ArrayList<>(); // to hold the stats of each state
HttpClient client = HttpClient.newHttpClient();
// creating a new http request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(ConstantsUtil.VIRUS_CONFIRMED_DATA_URL))
.build();
// get a response by having the client send the request
try {
HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
// parse the body of the request from csv format to readable format
StringReader csvBodyReader = new StringReader(httpResponse.body());
Iterable<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(csvBodyReader);
for (CSVRecord record: records) {
// create a model with the parsed data
LocationStats stats = new LocationStats();
stats.setState(record.get("Province/State"));
stats.setCountry(record.get("Country/Region"));
// the latest day
int latestCases = Integer.parseInt(record.get(record.size() - 1));
int prevDayCases = Integer.parseInt(record.get(record.size() - 2));
stats.setLatestTotalCases(latestCases);
stats.setDiffFromPreviousDay(prevDayCases);
mongoOperations.save(LocationStats);
// add to new stats
newStats.add(stats);
}
// assign to class array -> we use this array to display the data
this.allConfirmedStats = newStats;
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
So the main issue with this is the data is not saving to the mongoDB once I call mongoOperations.save(). Also, I've learned that it is bad practice to maintain some type of state in a Service. What is the best practice for this? Will inserting the data into MongoDB take care of that since we are not managing state.
Here is my model class that I want to save to mongodb
#Document(collection = "LocationStats")
public class LocationStats {
/** Location model to show corona virus statistics in each state*/
#Id
private String state;
private String country;
private int latestTotalCases;
private int diffFromPreviousDay;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public int getLatestTotalCases() {
return latestTotalCases;
}
public void setLatestTotalCases(int latestTotalCases) {
this.latestTotalCases = latestTotalCases;
}
public int getDiffFromPreviousDay() {
return diffFromPreviousDay;
}
public void setDiffFromPreviousDay(int diffFromPreviousDay) {
this.diffFromPreviousDay = diffFromPreviousDay;
}
#Override
public String toString() {
return "LocationStats{" +
"state='" + state + '\'' +
", country='" + country + '\'' +
", latestTotalCases=" + latestTotalCases +
'}';
}
}
once I have my models saved into mongoDB, I want to read from the database and get all the data from each collection and display it on the webpage. I'm thinking I'd fetch that data within the controller class and pass it to the frontend, is this good practice? here is my controller class.
#Controller
public class HomeController {
/** Controller class to generate/render the html UI */
#Autowired
CoronaVirusDataService coronaVirusDataService;
#Autowired
MongoOperations mongoOperations;
#GetMapping("/") // map this to the root template
public String home(Model model) {
List<LocationStats> allStats = coronaVirusDataService.getAllConfirmedStats();
// instead of above getter method, have a method call that fetches all data from mongoDB and return it as a List<LocationStats>
// get the total confirmed cases
int totalConfirmedCases = allStats.stream().mapToInt(LocationStats::getLatestTotalCases).sum();
int totalNewCases = allStats.stream().mapToInt(LocationStats::getDiffFromPreviousDay).sum();
// send the models to the view
model.addAttribute("locationStats", allStats);
model.addAttribute("totalReportedCases", totalConfirmedCases);
model.addAttribute("totalNewCases", totalNewCases);
return "home";
}
}
Related
Below is the service method (JsonObjectBuilderService) that converts an object (FeatureCollectionForGeoJson) to a jsonStr. This service method is used in the Get RequestMapping to send a response to the front-end.
The FeatureCollectionForGeoJson object is a class mapped for GeoJson FeatureCollection.
The GeometryForGeoJson is another class that contains the string type with "Point" value and the array that contains the latitude and longitude for the point.
The PropertyForGeoJson class contains information/properties about that pin that will be displayed in the pop-up when the pin is clicked on on the map.
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
public class FeatureForGeoJson {
private final String type = "Feature";
private GeometryForGeoJson geometry;
private PropertyForGeoJson properties;
}
#Service
public class JsonObjectBuilderService {
public String transformObjectToGeoJson(FeatureCollectionForGeoJson featureCollectionForGeoJson){
ObjectMapper Obj = new ObjectMapper();
String jsonStr = null;
try {
jsonStr = Obj.writeValueAsString(featureCollectionForGeoJson);
} catch (JsonProcessingException e) {
e.printStackTrace();
} //catch (IOException e) {
return jsonStr;
}
}
This is the GetMapping that sends the response to Angular
#GetMapping("/power-plants")
public ResponseEntity<String> getAllPowerPlants() {
try {
FeatureCollectionForGeoJson powerPlantsToFeatureCollectionForGeoJson ;
//jpa query for the database to return the information
List<PowerPlant> powerPlantList = powerPlantJpaService.findAll();
if (powerPlantList.isEmpty()) {
logger.info("The power plant list is empty.");
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
logger.info("The power plant list is populated and has been returned successfully.");
powerPlantsToFeatureCollectionForGeoJson = transformPowerPlantsToFeaturesCollection.transformPowerPlantToGeoJsonElements(powerPlantList);
String objectToGeoJson = jsonObjectBuilderService.transformObjectToGeoJson(powerPlantsToFeatureCollectionForGeoJson);
logger.info(objectToGeoJson);
return new ResponseEntity<>(objectToGeoJson, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This is how the response looks like in the browser
This is the Angular method that fetches the response.
This is the Angular component where I call the service method that fetches the response and where I want to add the pins to the map with the pop-ups.
How do I take that response from the API (line 27 from Home.component.ts -right above- or the getAll() method from the PowerPlantService) and process it to extract the Point Geometry, to create a pin with it and extract the properties to add to a pop-up to the pin?
if you use angular you should use Observables and not Promises, also avoid to post images of code, now I can't copy/paste you code.
what you want to do is return an observable in getAll(), something like this:
// in component
this.powerPlantService.getAll$().subscribe(
res => this.featureCollection = res,
err => console.log(err)
);
// in service
getAll$(): Observable<any[]> {
return this.http.get(baseUrl).pipe(
map(data => {
// transform your data here, or remove this pipe if you don't need it
return data;
})
);
}
you can transform your features in a flat object like this:
return this.http.get(baseUrl).pipe(
map(features => {
return features.map(f => {
const pointGeometry: any = {
...f.geometry,
...f.properties
};
return pointGeometry;
});
})
);
If you want to know how the back end formats and sends the response, please check in the body of the question.
Below is the service method that performs a GET request to the back end.
export class PowerPlantService {
constructor(private http: HttpClient) { }
getAll() {
return this.http.get(baseUrl);
}
Below is the component method that subscribes to the answer and adds the elements to the map.
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
private latitude: number = 45.6427;
private longitude: number = 25.5887;
private map!: L.Map;
private centroid: L.LatLngExpression = [this.latitude, this.longitude];
ngOnInit(): void {
this.initMap();
}
constructor(private powerPlantService: PowerPlantService) {
}
private initMap(): void {
this.map = L.map('map', {
center: this.centroid,
zoom: 2.8
});
const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
minZoom: 2.8,
attribution: '© OpenStreetMap'
});
tiles.addTo(this.map);
this.powerPlantService.getAll().subscribe((data: any)=>{
console.log(data);
L.geoJSON(data).addTo(this.map)
})
I need to validate my ui data and api responses are same,
here is my code I tried,
private ValidateContentPage cp = new ValidateContentPage();
public void getTitle() {
String UITitle = driver.findElement(titlepage).getText();
System.out.println(UITitle);
Assert.assertEquals(UITitle, cp.getAPICall(),"Passed");
}
here im getting my api responses,
public class ValidateContentPage {
public common cm = new common();
public Properties prop;
public void baseURI() {
prop = cm.getProperties("./src/test/API/IndiaOne/propertyfile/EndpointURL.properties");
RestAssured.baseURI = prop.getProperty("baseURI");
}
public String getAPICall() {
objectpojo ps = given().expect().defaultParser(Parser.JSON).when().get(prop.getProperty("resources")).as(objectpojo.class, cm.getMapper());
int number = ps.getPosts().size();
System.out.println(number);
System.out.println(ps.getPosts().get(0).getTitle());
return ps.getPosts().get(0).getTitle();
}
If i validate both using testng assertion it throwing null pointer exception, anyone help me on how to validate my ui data and api responses.
You need to call your ValidateContentPage from #Test itself or from #BeforeTest
#Test
public void getTitle() {
String UITitle = driver.findElement(titlepage).getText();
System.out.println(UITitle);
ValidateContentPage cp = new ValidateContentPage();
Assert.assertEquals(UITitle, cp.getAPICall(),"Passed");
}
Here it is my simple code, it read from pubsub subscription and save the body of the message to Cassandra table with current timestamp.
The message is consumed from subscription but there is no record insert to table and there is no error messages.
But if I change Date type "Timestamp" to Long in class TestTable, this code is working and insert the record to the table.
here it is the script to create the table.
DROP TABLE IF EXISTS test_table;
CREATE TABLE IF NOT EXISTS test_table(
post_index int,
ingestion_time TIMESTAMP,
body text,
PRIMARY KEY ((post_index))
);
#Table(keyspace = "{keyspace_name}", name = "{table_name}",
readConsistency = "LOCAL_QUORUM",
writeConsistency = "LOCAL_QUORUM",
caseSensitiveKeyspace = false,
caseSensitiveTable = false)
class TestTable implements Serializable {
#PartitionKey
#Column(name="post_index")
Integer postIndex;
#Column(name="ingestion_time")
Timestamp ingestionTime;
#Column(name = "body")
String body;
public Integer getPostIndex() {
return postIndex;
}
public void setPostIndex(Integer postIndex) {
this.postIndex = postIndex;
}
public Timestamp getIngestionTime() {
return ingestionTime;
}
public void setIngestionTime(Timestamp ingestionTime) {
this.ingestionTime = ingestionTime;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public TestTable(Integer postIndex, Timestamp ingestionTime, String body) {
this.body = body;
this.ingestionTime = ingestionTime;
this.postIndex = postIndex;
}
public TestTable() {
this.body = "";
this.ingestionTime = Timestamp.from(Instant.now());
this.postIndex = 0;
}
}
public class TestCassandraJobJava {
public static void main(String[] args) {
Pipeline pipeline = Pipeline.create(PipelineOptionsFactory.fromArgs(args).create());
PCollection<String> data = pipeline.apply("ReadStrinsFromPubsub",
PubsubIO.readStrings().fromSubscription("projects/{project_id}/subscriptions/{subscription_name}"))
.apply("window", Window.into(FixedWindows.of(Duration.standardSeconds(5))))
.apply("CreateMutation", ParDo.of(new DoFn<String, TestTable>() {
#ProcessElement
public void processElement(#Element String word, OutputReceiver<TestTable> out) {
TestTable t = new TestTable(new Random().nextInt(), java.sql.Timestamp.from(Instant.now()), word);
out.output(t);
}
})).apply(CassandraIO.<TestTable>write()
.withHosts(Arrays.asList("127.0.0.1"))
.withPort(9042)
.withKeyspace("{keyspace}")
.withLocalDc("Cassandra")
.withEntity(TestTable.class)
);
pipeline.run().waitUntilFinish();
}
}
To get this working you need to have a codec between the Cassandra's timestamp and java.sql.Timestamp. By default, in Java driver 3.x, the timestamp is converted into java.util.Date (see mapping), although you can also use Joda Time, or Java 8.x time API via extra codecs. And in Java driver 4.x, the Instant is used for representation of timestamps.
There is no built-in codec for java.sql.Timestamp, but it shouldn't be very hard to implement your own - the documentation describes process of custom codec creation & usage in much details.
Hey guys am new to this and I would appreciate any help.
I want to call getListTenant() from my save function below and clear the list using iterator before doing my save. Below is the code in my controller:
package controllers;
public class TenantController extends AppController {
Tenant tenant;
FacilityUnit unit;
// list tenants in selected facility
public Result listTenant() {
return ok(Json.toJson(getTenantList()));
}
private List<Tenant> getTenantList() {
List<Tenant> tenants = Tenant.find
.fetch("unit.facility")
.where().eq("unit.facility", currentFacility())
.findList();
return tenants;
}
public Result saveTenant() {
JsonNode submissionNode = request().body().asJson();
JsonNode itemsArray = submissionNode.get("items");
//clear tenant
// create the new tenant
if (itemsArray.isArray()) {
for (JsonNode itemNode : itemsArray) {
JsonNode tenantNode = itemNode.get("tenant");
String tenantId = tenantNode.get("id").asText();
JsonNode unitNode = itemNode.get("unit");
String unitId = unitNode.get("id").asText();
System.out.println("##### Tenant ID IS " + tenantId);
System.out.println("##### unit ID IS " + unitId);
// Tenant.find.where().eq("tenant.id",
// tenant.getTenant().getId()).eq("unit.id", unit.getId()
// ).delete();
// Util.isNotEmpty() &&
if (Util.isNotEmpty(tenantId) && Util.isNotEmpty(unitId)) {
// these two are the minimal criteria for an tenant
Tenant tenant = new Tenant();
tenant.setTenant(Person.find.byId(tenantId));
tenant.setUnit(FacilityUnit.find.byId(unitId));
tenant.save();
System.out.println("##### SAVED A TENANT");
}
}
}
System.out.println("##### DONE");
return ok(infoMessage("Update of " + tenant.getTenant() + "successful"));
}
Iterate over the Tenants from getTenantList() and call delete() on them to clear them.
For example:
private List<Tenant> getTenantList() {
List<Tenant> tenants = Tenant.find
.fetch("unit.facility")
.where().eq("unit.facility", currentFacility())
.findList();
return tenants;
}
public Result saveTenant() {
// Do something...
// ...
// Get the tenants list that we want to clear before saving.
// Best way to do this is loop over it and call delete.
for (Tenant tenant : getTenantList()) {
tenant.delete();
}
// Do some more things...
// ...
}
I have an MVC app that is creating new offices instead of updating them on when using an edit form. Please help me understand why this is happening.
Search method that populates the search results:
#RequestMapping(value = "/searchResults", method = RequestMethod.POST)
public ModelAndView search(#RequestParam String searchCriteria, HttpServletRequest request) {
List<Office> offices = officeServiceImpl.search(searchCriteria);
return new ModelAndView("searchResults", "offices", offices);
}
Here's what the link to the edit form looks like on the search results page:
Edit Office
Here is the Controller's edit GET method that populates the form with the existing Office:
#RequestMapping(value = "/{officeId}/edit", method = RequestMethod.GET)
#Transactional(noRollbackFor=NoResultException.class)
public ModelAndView initUpdateOfficeForm(
#PathVariable("officeId") Long officeId, Model model) {
Office office = officeServiceImpl.find(officeId);
//prepareEditFormModelAndView(office) just converts some objects to strings for typeahead form population
return prepareEditFormModelAndView(office);
}
Here is the edit POST method:
#RequestMapping(value = "/{officeId}/edit", method = RequestMethod.POST)
public ModelAndView processUpdateOfficeForm(#ModelAttribute("office") #Valid Office office,
BindingResult result, SessionStatus status) {
if (! "united states of america".equals(office.getFolderStrings().toLowerCase())) {
//This portion of code converts the typeahead strings to objects
result = tryCountries(office, result);
result = tryDepartments(office, result);
result = tryEmployees(office, result);
}
if (result.hasErrors()) {
return prepareEditFormModelAndView(office);
} else {
officeServiceImpl.save(office);
status.setComplete();
return new ModelAndView("editResult", "office", office);
}
}
officeServiceImpl calls officeRepositoryImpl method save which looks like:
#Override
public Office save(Office office) {
em.merge(office);
em.flush();
return office;
}
Thanks
Edit: Adding prepareEditFormModelAndView(office), This method attempts to build strings from associated objects:
#Transactional(noRollbackFor={NoResultException.class, IndexOutOfBoundsException.class})
private ModelAndView prepareEditFormModelAndView(Office office) {
String departmentStrings = "";
String employeeStrings = "";
List<OOM> officeOOMs = new ArrayList<OOM>();
StringBuilder sb = new StringBuilder();
try {
officeOOMs = oomServiceImpl.getOOMsForCurrentOffice(office.getId());
} catch (NoResultException e) {
officeOOMs = null;
}
for (OOM o : officeOOMs) {
try {
Employee tempEmployee = employeeServiceImpl.find(o
.getEmployeeId());
sb.append(tempEmployee.getDisplayName() + ", ");
} catch (NoResultException e) {
sb.append("Not found in system");
}
}
employeeStrings = sb.toString();
if ((! "".equals(office.getDepartmentStringsOnForm())) && office.getDepartmentStringsOnForm() != null) {
departmentStrings = office.getDepartmentStringsOnForm();
}
String folderStrings = "";
try {
folderStrings = kmlFolderServiceImpl.getInternationalOfficeString(office.getId());
LOGGER.info("Folder Strings: " + folderStrings);
} catch (NoResultException e) {
folderStrings = "";
LOGGER.info("Folder Strings: " + "no result");
}
boolean isInternational = office.isInternational();
ModelAndView result = new ModelAndView("editOfficeForm", "office", office);
result.addObject("departmentStrings", departmentStrings);
result.addObject("isInternational", isInternational);
result.addObject("folderStrings", folderStrings);
result.addObject("employeeStrings", employeeStrings);
return result;
}
I am adding a previous comment here, for better clarification. According to the OP the following fixes the problem:
When the ID is not in the form then when the model is posted back no ID is set to the entity making the persistence provider believe it is new entity.
Therefor the most obvious solution is to post the ID of the entity as well in the save operation (probably using a hidden field).
Another solution would be to try to load the entity in the database based on some business key
to see if the entity is new or not.