简体   繁体   中英

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM