Jak odczytać kilka (plikowych) wejść o tej samej nazwie z formularza wieloczęściowego z Jersey?

Z powodzeniem opracowałem usługę, w której czytam pliki przesłane w postaci wieloczęściowej w Jersey. Oto bardzo uproszczona wersja tego, co robiłem:

@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("file") InputStream uploadedInputStream,
        @FormDataParam("file") FormDataContentDisposition fileDetail) throws IOException {
    //handle the file
}

To działa dobrze, ale otrzymałem nowe wymaganie. Oprócz przesyłanego przeze mnie pliku muszę obsługiwać dowolną liczbę zasobów. Załóżmy, że są to pliki obrazów.

Pomyślałem, że po prostu dostarczę klientowi formularz z jednym wejściem dla pliku, jednym wejściem dla pierwszego obrazu i przyciskiem umożliwiającym dodanie kolejnych danych do formularza (przy użyciu AJAX lub po prostu zwykłego JavaScript).

<form action="blahblahblah" method="post" enctype="multipart/form-data">
   <input type="file" name="file" />
   <input type="file" name="image" />
   <input type="button" value="add another image" />
   <input type="submit"  />
</form>

Użytkownik może więc dołączyć formularz z większą liczbą danych wejściowych dla obrazów, takich jak:

<form action="blahblahblah" method="post" enctype="multipart/form-data">
   <input type="file" name="file" />
   <input type="file" name="image" />
   <input type="file" name="image" />
   <input type="file" name="image" />
   <input type="button" value="add another image" />
   <input type="submit"  />
</form>

Miałem nadzieję, że wystarczy przeczytać pola o tej samej nazwie co kolekcja. Zrobiłem to z powodzeniem przy wprowadzaniu tekstu w MVC .NET i myślałem, że nie będzie trudniej na Jersey. Okazuje się, że się myliłem.

Nie znalazłem żadnych samouczków na ten temat i zacząłem eksperymentować.

Aby zobaczyć, jak to zrobić, ograniczyłem problem do prostych wejść tekstowych.

<form action="blahblabhblah" method="post" enctype="multipart/form-data">
   <fieldset>
       <legend>Multiple inputs with the same name</legend>
       <input type="text" name="test" />
       <input type="text" name="test" />
       <input type="text" name="test" />
       <input type="text" name="test" />
       <input type="submit" value="Upload It" />
   </fieldset>
</form>

Oczywiście musiałem mieć jakiś rodzaj kolekcji jako parametr mojej metody. Oto, co próbowałem, pogrupowane według typu kolekcji.

Szyk

Najpierw sprawdziłem, czy Jersey jest wystarczająco inteligentny, aby obsłużyć prostą tablicę:

@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("test") String[] inputs) {
    //handle the request
}

ale tablica nie została wstrzyknięta zgodnie z oczekiwaniami.

MultiValuedMap

Po nieudanej porażce przypomniałem sobie toMultiValuedMap obiekty mogą być obsługiwane z pudełka.

@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(MultiValuedMap<String, String> formData) {
    //handle the request
}

ale to też nie działa. Tym razem mam wyjątek

SEVERE: A message body reader for Java class javax.ws.rs.core.MultivaluedMap, 
and Java type javax.ws.rs.core.MultivaluedMap<java.lang.String, java.lang.String>, 
and MIME media type multipart/form-data; 
boundary=----WebKitFormBoundaryxgxeXiWk62fcLALU was not found.

Powiedziano mi, że tego wyjątku można się pozbyć, włączającmimepull więc dodałem następującą zależność do mojego pom:

    <dependency>
        <groupId>org.jvnet</groupId>
        <artifactId>mimepull</artifactId>
        <version>1.3</version>
    </dependency>

Niestety problem nadal występuje. Prawdopodobnie jest to kwestia wyboru odpowiedniego czytnika ciała i używania różnych parametrów dla ogólnego. Nie jestem pewien, jak to zrobić. Chcę konsumować zarówno pliki, jak i pliki tekstowe, a także niektóre inne (głównieLong wartości i niestandardowe klasy parametrów).

FormDataMultipart

Po kilku dalszych badaniach znalazłemFormDataMultiPart klasa. Z powodzeniem użyłem go do wyodrębnienia wartości ciągu z mojego formularza

@POST
@Path("upload2")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadMultipart(FormDataMultiPart multiPart){
    List<FormDataBodyPart> fields = multiPart.getFields("test");
    System.out.println("Name\tValue");
    for(FormDataBodyPart field : fields){
        System.out.println(field.getName() + "\t" + field.getValue());
        //handle the values
    }
    //prepare the response
}

Problem polega na tym, że jest to rozwiązanie uproszczonej wersji mojego problemu. Chociaż wiem, że każdy pojedynczy parametr wstrzykiwany przez Jersey jest tworzony przez parsowanie łańcucha w pewnym momencie (nic dziwnego, że to HTTP), a ja mam pewne doświadczenie w pisaniu własnych klas parametrów, tak naprawdę nie wiem jak przekonwertować te pola naInputStream lubFile instancje do dalszego przetwarzania.

Dlatego przed nurkowaniem w kodzie źródłowym Jersey, aby zobaczyć, jak te obiekty są tworzone, zdecydowałem się zapytać, czy istnieje łatwiejszy sposób odczytania zbioru (o nieznanym rozmiarze) plików. Czy wiesz, jak rozwiązać tę zagadkę?

questionAnswers(3)

yourAnswerToTheQuestion