Development Best Practices
Code Review Checklist
Subversion Best Practices
Branches
Redmine Best Practices
Add yourself as a watcher on projects you are working on
When ready for code review, assign task in Feedback to project owner with released to set to Local Machine
Java Best Practices
Hibernate Queries
When possible, use Hibernate Criteria API to fetch records from the database. If the Criteria API does not suffice, use a native query instead. Avoid HQL queries, since we have deemed them more work and less readable than native queries.
Native queries should be written in ALL CAPS.
API Security
Follow the principle of least privilege when creating API endpoints. At a minimum, verify that any resources created, edited, or returned match the community id of the user making the request.
All Controllers should be annotated with @PreAuthorize("denyAll()") This will force developers to override @PreAuthorize in each method, causing them to think about who should be authorized to call a the endpoint. If authorization is handled in the method body, in the @PostAuthorize method, or the endpoint really needs to be called by any authenticated user, the method can be annotated with @PreAuthorize("isAuthenticated()").
For a GET request for a single record by id, you can leverage @PostAuthorize on the object in the response body to make sure the community id matches the requesting user's community id.
@PostAuthorize("@customerAccess.isCommunityUser(returnObject.body.content.communityId)")
For a GET request for a list of records, make sure your service/dao call filters by communityId.
final Long communityId = SecurityUtility.communityId();
List<Mode> modes = modeService.getModesForCommunity(communityId);
For a GET request for a list of children of a parent record (for example /service-periods/{id}/normal-hours), fetch the parent record to verify the community matches the user's before returning the child list.
ServicePeriod servicePeriod = servicePeriodService.getServicePeriod(servicePeriodId);
if(servicePeriod == null || !SecurityUtility.communityId().equals(servicePeriod.getCommunityId())) {
throw new NullResourceException(ServicePeriod.class);
}
Resources<NormalHoursResource> normalHoursResources = new Resources<>(servicePeriod.getNormalHoursList().stream().map(NormalHoursResource::new).collect(Collectors.toList()));
return ResponseEntity.ok().cacheControl(CacheDefaults.FIVE_MINUTES).body(normalHoursResources);
For a POST, PUT, or PATCH request, you can @PreAuthorize the request body if the DTO contains the community id.
@PreAuthorize("@customerAccess.isCommunityUser(#noServiceRequestedDTO.communityId)")
Alternatively, or for a DELETE request, you can fetch the record to verify the community matches the user's before saving to the database.
ServicePeriod servicePeriod = servicePeriodService.getServicePeriod(servicePeriodId);
if(servicePeriod == null || !SecurityUtility.communityId().equals(servicePeriod.getCommunityId())) {
throw new NullResourceException(ServicePeriod.class);
}