web-dev-qa-db-fra.com

Puis-je servir des JSP depuis l'intérieur d'un JAR dans lib, ou y a-t-il une solution?

J'ai une application Web déployée en tant que fichier WAR dans Tomcat 7. L'application est conçue comme un projet multi-modules:

  • core - empaqueté en JAR, contient la plupart du code backend
  • core-api - emballé sous forme de JAR, contient des interfaces vers le noyau
  • webapp - emballé comme WAR, contient du code frontal et dépend du noyau
  • extensions client - module optionnel, conditionné en JAR

Normalement, nous pouvons mettre nos fichiers JSP dans le projet webapp, et les référencer par rapport au contexte:

/WEB-INF/jsp/someMagicalPage.jsp

La question est de savoir ce que nous faisons des fichiers JSP spécifiques au projet d'extensions client, qui ne doivent pas toujours être inclus dans le WAR. Malheureusement, je ne peux pas me référer aux JSP dans les fichiers JAR, il apparaît. La tentative de classpath:jsp/customerMagicalPage.jsp Entraîne un fichier introuvable dans le JspServlet, car il utilise ServletContext.getResource().

Traditionnellement, nous "résolvions" ce problème en maven déballant le JAR des extensions client, localisant les JSP et les plaçant dans le WAR lors de sa construction. Mais une situation idéale est où vous déposez simplement un JAR dans la guerre éclatée de Tomcat et l'extension est découverte - qui fonctionne pour tout sauf les JSP.

Est-ce qu'il y a un moyen de résoudre ceci? Une méthode standard, une méthode spécifique à Tomcat, un hack ou une solution de contournement? Par exemple, j'ai pensé à déballer les JSP au démarrage de l'application ...

61
waxwing

Servlet 3.0 pris en charge par Tomcat 7 inclut la possibilité de regrouper jsps dans un bocal.

Tu dois:

  • placez votre jsps dans META-INF/resources répertoire de votre pot
  • inclure éventuellement un web-fragment.xml dans le META-INF répertoire de votre pot
  • placez le pot dans WEB-INF/lib répertoire de votre guerre

Vous devriez alors pouvoir référencer vos jsps dans votre contexte. Par exemple, si vous avez un jsp META-INF/resources/test.jsp vous devriez pouvoir référencer ceci à la racine de votre contexte comme test.jsp

67
scarba05

Pour contourner ce problème, j'ai créé une classe qui ouvre un fichier jar, recherche les fichiers correspondant à un certain modèle et extrait ces fichiers à un emplacement donné par rapport au chemin du contexte.

import Java.io.File;
import Java.io.FileNotFoundException;
import Java.io.FileOutputStream;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.OutputStream;
import Java.net.MalformedURLException;
import Java.net.URL;
import Java.util.Enumeration;
import Java.util.jar.JarEntry;
import Java.util.jar.JarFile;

import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;

import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.ServletContextAware;

/**
 * Allows extraction of contents of a JAR file. All files matching a given Ant path pattern will be extracted into a
 * specified path.
 */
public class JarFileResourcesExtractor implements ServletContextAware {

    private String resourcePathPattern;
    private String jarFile;
    private String destination;
    private ServletContext servletContext;
    private AntPathMatcher pathMatcher = new AntPathMatcher();

    /**
     * Creates a new instance of the JarFileResourcesExtractor
     * 
     * @param resourcePathPattern
     *            The Ant style path pattern (supports wildcards) of the resources files to extract
     * @param jarFile
     *            The jar file (located inside WEB-INF/lib) to search for resources
     * @param destination
     *            Target folder of the extracted resources. Relative to the context.
     */
    private JarFileResourcesExtractor(String resourcePathPattern, String jarFile, String destination) {
        this.resourcePathPattern = resourcePathPattern;
        this.jarFile = jarFile;
        this.destination = destination;
    }

    /** 
     * Extracts the resource files found in the specified jar file into the destination path
     * 
     * @throws IOException
     *             If an IO error occurs when reading the jar file
     * @throws FileNotFoundException
     *             If the jar file cannot be found
     */
    @PostConstruct
    public void extractFiles() throws IOException {
        try {
            String path = servletContext.getRealPath("/WEB-INF/lib/" + jarFile);
            JarFile jarFile = new JarFile(path);

            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (pathMatcher.match(resourcePathPattern, entry.getName())) {
                    String fileName = entry.getName().replaceFirst(".*\\/", "");
                    File destinationFolder = new File(servletContext.getRealPath(destination));
                    InputStream inputStream = jarFile.getInputStream(entry);
                    File materializedJsp = new File(destinationFolder, fileName);
                    FileOutputStream outputStream = new FileOutputStream(materializedJsp);
                    copyAndClose(inputStream, outputStream);
                }
            }

        }
        catch (MalformedURLException e) {
            throw new FileNotFoundException("Cannot find jar file in libs: " + jarFile);
        }
        catch (IOException e) {
            throw new IOException("IOException while moving resources.", e);
        }
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public static int IO_BUFFER_SIZE = 8192;

    private static void copyAndClose(InputStream in, OutputStream out) throws IOException {
        try {
            byte[] b = new byte[IO_BUFFER_SIZE];
            int read;
            while ((read = in.read(b)) != -1) {
                out.write(b, 0, read);
            }
        } finally {
            in.close();
            out.close();
        }
    }
}

Et puis je le configure comme un bean dans mon XML Spring:

<bean id="jspSupport" class="se.waxwing.util.JarFileResourcesExtractor">
   <constructor-arg index="0" value="jsp/*.jsp"/>
   <constructor-arg index="1" value="myJarFile-1.1.0.jar"/>
   <constructor-arg index="2" value="WEB-INF/classes/jsp"/>
</bean>

Ce n'est pas une solution optimale à un problème vraiment ennuyeux. La question devient maintenant, est-ce que le gars qui maintient ce code viendra et assassinez-moi pendant que je dors pour ça?

6
waxwing

Il existe une telle solution - vous pouvez précompiler vos JSP en servlets. Ainsi, vous obtiendrez des fichiers .class que vous pouvez placer dans JAR et mapper dans web.xml à certaines URL.

4
Paul Lysak

L'équipe Struts 2 a ajouté un plugin pour JSP intégré. Peut-être qu'il peut être utilisé sur une base.

https://struts.Apache.org/plugins/embedded-jsp/

4
apetrelli

Ceci est une réponse à waxwing, que j'ai utilisée car nous avons utilisé un serveur qui ne pouvait rien faire de plus que le servlet 2.5.

J'ai ajouté une méthode qui supprime les fichiers ajoutés lorsque le bean est détruit.

import Java.io.File;
import Java.io.FileNotFoundException;
import Java.io.FileOutputStream;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.OutputStream;
import Java.net.MalformedURLException;
import Java.net.URL;
import Java.util.ArrayList;
import Java.util.Enumeration;
import Java.util.List;
import Java.util.jar.JarEntry;
import Java.util.jar.JarFile;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.ServletContext;

import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.ServletContextAware;


import com.sap.tc.logging.Location;

/**
 * Allows extraction of contents of a JAR file. All files matching a given Ant path pattern will be extracted into a
 * specified path.
 * Copied from http://stackoverflow.com/questions/5013917/can-i-serve-jsps-from-inside-a-jar-in-lib-or-is-there-a-workaround
 */
public class JarFileResourcesExtractor implements ServletContextAware {

    private final transient Location logger = Location.getLocation(JarFileResourcesExtractor.class);

    private String resourcePathPattern;
    private String jarFile;
    private String destination;
    private ServletContext servletContext;
    private AntPathMatcher pathMatcher = new AntPathMatcher();
    private List<File> listOfCopiedFiles = new ArrayList<File>();

    /**
     * Creates a new instance of the JarFileResourcesExtractor
     * 
     * @param resourcePathPattern
     *            The Ant style path pattern (supports wildcards) of the resources files to extract
     * @param jarFile
     *            The jar file (located inside WEB-INF/lib) to search for resources
     * @param destination
     *            Target folder of the extracted resources. Relative to the context.
     */
    public JarFileResourcesExtractor(String resourcePathPattern, String jarFile, String destination) {
        this.resourcePathPattern = resourcePathPattern;
        this.jarFile = jarFile;
        this.destination = destination;
    }


    @PreDestroy
    public void removeAddedFiles() throws IOException{
        logger.debugT("I removeAddedFiles()");
        for (File fileToRemove : listOfCopiedFiles) {
            if(fileToRemove.delete()){
                logger.debugT("Tagit bort filen " + fileToRemove.getAbsolutePath());
            }
        }
    }


    /** 
     * Extracts the resource files found in the specified jar file into the destination path
     * 
     * @throws IOException
     *             If an IO error occurs when reading the jar file
     * @throws FileNotFoundException
     *             If the jar file cannot be found
     */
    @PostConstruct
    public void extractFiles() throws IOException {
        try {
            String path = servletContext.getRealPath("/WEB-INF/lib/" + jarFile);
            JarFile jarFile = new JarFile(path);

            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (pathMatcher.match(resourcePathPattern, entry.getName())) {
                    String fileName = entry.getName().replaceFirst(".*\\/", "");
                    File destinationFolder = new File(servletContext.getRealPath(destination));
                    InputStream inputStream = jarFile.getInputStream(entry);
                    File materializedJsp = new File(destinationFolder, fileName);
                    listOfCopiedFiles.add(materializedJsp);
                    FileOutputStream outputStream = new FileOutputStream(materializedJsp);
                    copyAndClose(inputStream, outputStream);
                }
            }

        }
        catch (MalformedURLException e) {
            throw new FileNotFoundException("Cannot find jar file in libs: " + jarFile);
        }
        catch (IOException e) {
            throw new IOException("IOException while moving resources.", e);
        }
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public static int IO_BUFFER_SIZE = 8192;

    private static void copyAndClose(InputStream in, OutputStream out) throws IOException {
        try {
            byte[] b = new byte[IO_BUFFER_SIZE];
            int read;
            while ((read = in.read(b)) != -1) {
                out.write(b, 0, read);
            }
        } finally {
            in.close();
            out.close();
        }
    }
}

Ensuite, j'ai changé le constructeur pour pouvoir utiliser toutes les configurations Java:

@Bean 
public JarFileResourcesExtractor jspSupport(){
    final JarFileResourcesExtractor extractor = new JarFileResourcesExtractor("WEB-INF/pages/*.jsp","myJarFile-1.1.0.jar","WEB-INF/pages" );
    return extractor;
}

J'espère que quelqu'un aide quelqu'un!

1