1

So for a school project we created a site where a user could submit a report on underwater life etc. We used simple dependency injection (javax.inject) and an error checking pattern as follows :

ReportService.java

public interface ReportService {
    public static enum ReportServiceErrorsENUM {
        DB_FAILURE, WRONG_COORD // etc
    }
    public Set<ReportServiceErrorsENUM> getLastErrors();
    public int addNewReport(Report report);
}

ReportServiceImpl.java

public class ReportServiceImpl implements ReportService {

    private Set<ReportServiceErrorsENUM> lastErrors;
    private @Inject ReportDAO reportDAO;

    @Override
    public Set<ReportServiceErrorsENUM> getLastErrors() {
        return this.lastErrors;
    }

    @Override
    public int addNewReport(Report report) {
        lastErrors= new HashSet<ReportServiceErrorsENUM>();//throw away previous errors
        UserInput input = report.getUserInput();
        if (input.getLatitude() == null) {
            addError(ReportServiceErrorsENUM.WRONG_COORD);
        }
        // etc etc
        if (reportDAO.insertReport(report) != 0) {
            // failure inserting the report in the DB
            addError(ReportServiceErrorsENUM.DB_ERROR);
        }
        if (lastErrors.isEmpty()) // if there were no errors
            return EXIT_SUCCESS; // 0
        return EXIT_FAILURE;  // 1
    }
}

SubmitReportController.java

@WebServlet("/submitreport")
public class SubmitReportController extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private @Inject ReportService reportService;

    @Override
    protected void doPost(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
        Report report = new Report();
        // set the report's fields from the HttpServletRequest attributes
        if(reportService.addNewReport(report) == ReportService.EXIT_FAILURE) {
            for(ReportServiceErrorsENUM error : reportService.getLastErrors())
                // display the errors etc
        } else {
            // display confirmation
        }
    }
}

The idea is that the Servlet controller calls the service (which is injected) then checks the services' return value and calls getLastErrors() on the service if there was an error - to inform the user what went wrong etc. Now I just came to realize that this is not thread safe - the @Inject'ed ReportService (reportService) will be shared by all threads using the servlet

  1. Is it (crosses fingers) ?
  2. How could one improve on this error mechanism ?

Thanks

Sully
  • 14,672
  • 5
  • 54
  • 79
Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361

2 Answers2

1

typically for servlets you'd want to keep those variables (generally called "state") in some container-managed context. i'd move these errors to the request scope - that way they're stored on the request object (conceptually) and any servlet/jsp/whatever working on that same request can see/edit them. different requests mean different data storage.

example code for using the request scope from a servlet can be found here: http://www.exampledepot.com/egs/javax.servlet/State.html

radai
  • 23,949
  • 10
  • 71
  • 115
  • Unfortunately the Set of errors belongs to the Service class - I do not see a way of getting to the Request from there (?) – Mr_and_Mrs_D Jul 22 '12 at 19:09
  • easiest way to handle this would be something like: class ServiceState {private Set lastErrors} and then in your servlet - ServiceState stateForThisRequest = new ...; ReportService.addNewReport(report, stateForThisRequest); after which you add stateForThisRequest to the request (under some known string key) for use by anything else processing that request (i assume you have a jsp or something for display?) – radai Jul 22 '12 at 19:16
  • there' probably some way to inject some transaction or session context into your service (dont really know CDI - javax.inject), but this will be enough. like Tomasz says - multiple request on the same session are possible - thats why the request scope is best for keeping this. – radai Jul 22 '12 at 19:19
  • Thanks - the idea is nice but needs deep refactoring. Any other way (than changing all methods signatures ?) :D – Mr_and_Mrs_D Jul 22 '12 at 19:49
  • 1
    well, according to this http://openejb.apache.org/examples-trunk/cdi-request-scope/ you could inject a ServiceState (in which you keep youe "state" - the errors) into your service class as @RequestScoped. never tried these newfangled CDI shenanigans myself though :-) – radai Jul 22 '12 at 19:53
  • Aha - well maybe I will accept your answer after all. Quick tests indicate that if I annotate the ReportServiceImpl with @ RequestScoped its constructor gets called every time the Servlet gets a request. Not entirely sure yet. Second point - I guess the DAO Injected in the ReportServiceImpl will get constructed every time the ReportServiceImpl is constructed (there is no chance the framework injects the same reference ?). If all this is like this ammending the situation amounts to adding RequestScoped at 12ish ServicesImpl - I guess there is a performance penalty - but for the moment it's ok. – Mr_and_Mrs_D Jul 22 '12 at 20:44
  • there's going to be some performance penalty somewhere for keeping multiple states for different requests :-) actually i dont think the DAO will be created multiple times - assumming its defined as some logner lived managed component – radai Jul 23 '12 at 05:35
  • Well - the DAO is injected in the service - so yes it gets recreated :) And surely it is a shitty design - for some reason I thought that injection would create a new service every so often lol. But as it is now the situation is amended with just a dozen @RequestScoped - this would even permit submitting from 2 different tabs had not my coworkers mingled session and request. Anyway - great quickfix - as it seems now at least. Yes DI is not easy to master (EDIT ; added the DAO injection) – Mr_and_Mrs_D Jul 23 '12 at 12:52
  • CDI should be aware of different scopes, and i think (though i have no experience with CDI) that a properly defined service should be at the very least pooled (a-la j2ee MDBs/SLSBs) – radai Jul 24 '12 at 06:44
1

Your design is neither thread-safe nor ready to server multiple users. It is not thread safe because several users (browsers) can hit the servlet at the same time and in turn access lastErrors set concurrently. (Yes, there is only one instance of servlet and your service).HashSet which you use is not thread safe.

Also if two different people try to use the same application, they will overwrite and have access to reports (errors) submitted by each other. In other words there is global state shared among all users while there should have been a state per user/session.

By fixing the second issue (I gave you a tip: use HTTPSession) you are unlikely to see the first issue. This is because it is rather rare to see concurrent access to the same session. But it's possible (concurrent AJAX requests, two browser tabs). Keep that in mind, but there are more important issues to solve now.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • He can use a `ConcurrentSet` to bypass the first issue too. – Amir Pashazadeh Jul 22 '12 at 19:19
  • Could you discern a way to move the shared variables (basically the Set lastErrors) from the Service to the Servlet - request scope as suggested in the other answer would be ideal. keep in mind there are many services using this pattern. Even if I used synchronized methods if user A called say addReport and this returned user B could call addReport and clear() the Set of errors before user A called getLastErrors() (race). And brute synchronization would just kill the benefits of multithreading. Also I am not sure I get the difference between the 2 scenarios – Mr_and_Mrs_D Jul 22 '12 at 19:19
  • @Mr_and_Mrs_D: request scope is not enough, you'll loose `lastErrors` once the request is done. You need a session to persists `lastErrors` in between. User A and B have distinct sessions so they are not interfering with each other. But every time user A access the application (any servlet), he'll get the same session. You'll find plenty of resources on how to use `HTTPSession` in servlets. And yes, `synchronized` is pointless here, you have a problem on different level. – Tomasz Nurkiewicz Jul 22 '12 at 19:33
  • No prob - lastErrors are meant to be checked once in doPost/Get to print error messages (like "email is invalid") - then doPost will redirect so user can correct errors - so it is really request scope that is needed. My problem now is how to (painlessly) move the errors from the (injected) Service object to Servlet. One solution would be to have to have Service.method() return the set of errors and have method() be synchronized - so each thread would have its errors. But this would kill multithreading. I know about HTTPSession - used a lot - the code posted is much much stripped down. – Mr_and_Mrs_D Jul 22 '12 at 19:47
  • It is an interesting problem after all :) Maybe making lastErrors a local variable of Service.method() ? Hmmm – Mr_and_Mrs_D Jul 22 '12 at 19:49
  • @Mr_and_Mrs_D: oh, sorry! I just read your code carefully. All you have to do is change `lastErrors` from field to local variable (update: looks like you already got that idea) and return it (as you suggested). No synchronization is then needed since your service will become stateless: `getLastErrors()` and `lastErrors` field is not needed at all. And the design is much better! – Tomasz Nurkiewicz Jul 22 '12 at 19:51
  • Ok - thanks - it was a field cause some methods return an object - so they could not return a Set - if you have any idea on this (harder) problem - you are welcome :) – Mr_and_Mrs_D Jul 22 '12 at 19:54
  • @Mr_and_Mrs_D: sure, just wrap `Set` in an object that can contain other fields and return that object. – Tomasz Nurkiewicz Jul 22 '12 at 19:56
  • Hmm - radai suggested a javaEE mechanism I knew nothing about and seems to the point. I have to read up to it though – Mr_and_Mrs_D Jul 22 '12 at 20:00