0

I have a loader class that executes data loading functions at boot-time for my application to give me test data.

The idea is pretty simple, I'm reading in XML data, using JaxB to turn it into a POJO and then persisting the POJO using JPA repo.

Here is my code (pretty small):

Looking at the parseFile method, I create a new JaxB instance but it requires the UserContainer class. The problem is, I want to reuse this method for potentially infinitely many containers. What is the best way to refactor this to make it more generic?

I was hoping to avoid the need to pass around a reference to the class type in each subsequent method.

@Override
  public void onApplicationEvent(ApplicationReadyEvent event) {
    LOGGER.log(Level.INFO, "Preparing to load data into database");

    try {
      loadAndStoreUserData();
    } catch (IOException | JAXBException e) {
      LOGGER.log(Level.INFO, "Unable to store user data");
      e.printStackTrace();
    }
  }

  /**
   * Loads up any test data from resources and stores into embedded DB for test env
   * @throws IOException
   * @throws JAXBException
   */
  private void loadAndStoreUserData() throws IOException, JAXBException {
    LOGGER.log(Level.INFO, "User data table being added to database");
    UserContainer userData = (UserContainer) ingestFromFile("testDatabase/users.xml", UserContainer);
    userData.getUsers().stream().forEach(userRepo::save);
  }

  /**
   * Read in file from static resources dir
   * @param fileName
   * @return
   * @throws IOException
   * @throws JAXBException
   */
  private Object ingestFromFile(String fileName, Object classReference) throws IOException, JAXBException {
   Resource resource =  new ClassPathResource(fileName);
   return parseFile(resource.getFile());
  }

  /**
   * Takes input file from disk and parsed out contents by marshaling XML -> POJO
   * @param inputFile
   * @return
   * @throws JAXBException
   */
  private Object parseFile(File inputFile) throws JAXBException {
    JAXBContext jaxbContext = JAXBContext.newInstance(UserContainer.class);
    Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
    return jaxbUnmarshaller.unmarshal(inputFile);
  }
}
  • Use annotations. Scan packages looking for the class that has de annotations you made. Or do use JSR222 – Laiv May 20 '18 at 9:04
0

Why can't you just pass the a container class reference? Imagine:

private <T> T parseFile(String fileName, Class<T> targetClass) {
    JAXBContext jaxbContext = JAXBContext.newInstance(targetClass);
    Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
    // The cast must be prety safe.
    return targetClass.cast(jaxbUnmarshaller.unmarshal(inputFile));
}
  • I am now, it's just that I am going from loadAndStore and then calling parse file from that method, which forces me to pass the class reference to two methods. Does it seem like an anti pattern to do the method chain like that? – John Lippson Apr 20 '18 at 2:33
  • I would keep a mapping between a file name and a class, so that ingestFromFile('data/User-joe.xml') would pick the first word and mapped it to some my.project.models.User class. It's convenient to leave a free-form part to allow for several different files with data of the same class. – 9000 Apr 20 '18 at 2:51
  • perfect, that's exactly what I was thinking as well, thanks! – John Lippson Apr 20 '18 at 2:55
0

So all your information that you need to know regarding persisting data from files is written in the files except the pojo class representing it.

Logically, all you need to do is add this info to the file for it to be fully automatic. I would avoid having to pass in the class to the method unless you can obtain the class through some other means. You could always hard-code a mapping between the name of the file and its equivalent pojo class, and while it would work, it'd still be the only thing preventing you from fully automating it from information in the file, and therefore it remains awkward.

Ideally any and all info would be contained in your xml, but if you can't modify the xml itself, you could have a ".properties" file with the same name providing this information. When you load the xml, you also load its .properties file and determine the name of the implementing pojo class that way.

If you think you might reuse the same pojo class for many files, consider a single configuration file for housing a mapping between file and its pojo class. Rather than look for a configuration file with the same name with ".properties" extension, you'd merely load the mapping file for this info.

In any case, the objective should be to eliminate all traces of hard-coding. If you wanted to take it a step further still, you could load data into a Map containing key/value pairs rather than using a pojo class. This is the most adaptable, allowing you to save any data without first having to create its pojo class.

  • Thanks for the breakdown. I've successfully spent my entire night trying to make this as generic as possible. Unfortunately, I feel I have made the code extremely complex and it's not fully automatic :(. I'll look into a properties file approach next. – John Lippson Apr 20 '18 at 6:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.