001    /*
002     * Databinder: a simple bridge from Wicket to Hibernate
003     * Copyright (C) 2007  Nathan Hamblen nathan@technically.us
004     *
005     * This library is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU Lesser General Public
007     * License as published by the Free Software Foundation; either
008     * version 2.1 of the License, or (at your option) any later version.
009     * 
010     * This library is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013     * Lesser General Public License for more details.
014     * 
015     * You should have received a copy of the GNU Lesser General Public
016     * License along with this library; if not, write to the Free Software
017     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
018     */
019    package net.databinder.web;
020    
021    import java.io.File;
022    import java.net.URL;
023    import java.net.URLClassLoader;
024    import java.util.ArrayList;
025    import java.util.List;
026    import java.util.regex.Matcher;
027    import java.util.regex.Pattern;
028    
029    import org.apache.wicket.util.string.Strings;
030    import org.mortbay.jetty.Connector;
031    import org.mortbay.jetty.Server;
032    import org.mortbay.jetty.ajp.Ajp13SocketConnector;
033    import org.mortbay.jetty.handler.MovedContextHandler;
034    import org.mortbay.jetty.nio.SelectChannelConnector;
035    import org.mortbay.jetty.webapp.WebAppContext;
036    import org.slf4j.Logger;
037    import org.slf4j.LoggerFactory;
038    
039    /**
040     * Optional starter class for embedded Jetty. Client applications may pass
041     * this classname to the Java runtime to serve with no other configuration. The webroot 
042     * defaults to src/main/webapp, and the server to a context named after the project directory 
043     * with HTTP on port 8080. <tt>jetty.warpath</tt>, <tt>jetty.contextpath</tt>,
044     * <tt>jetty.port</tt>, and <tt>jetty.ajp.port</tt> system properties
045     * may be used to override (e.g. <tt>-Djetty.port=80</tt> as a command line parameter). AJP
046     * is enabled by specifying a port, and HTTP disabled by setting jetty.port to 0.
047     * 
048     * <p>Some customization can be accomplished by extending this class and overriding
049     * the {@link DataServer#configure(Server, WebAppContext)} method. The subclass
050     * will need a static main(args) method that constructs an instance of itself, similar
051     * to {@link DataServer#main(String[])}  
052     * @author Nathan Hamblen
053     */
054    public class DataServer {
055            private static final Logger log = LoggerFactory.getLogger(DataServer.class);
056    
057            /** Constructs DataServer, kicking off server. */
058            public static void main(String[] args)
059            {
060                    new DataServer();
061            }
062            
063            /** Starts web sever using any parameters supplied. Calls 
064             * {DataServer{@link #configure(Server, WebAppContext)} immediately before 
065             * starting server.
066             */
067            public DataServer() {
068                    try {
069                            Server server = new Server();
070            
071                            WebAppContext web = new WebAppContext();
072                            
073                            URL classes = null;
074                            String projectDir = null;
075                            try {
076                                    // look for project's classes directory
077                                    URL[] urls = ((URLClassLoader)DataServer.class.getClassLoader()).getURLs();
078                                    for (URL url : urls)
079                                            if (url.getPath().endsWith("classes/")) {
080                                                    classes = url;
081                                                    break;
082                                            }
083                            } catch (Exception e) { 
084                                    log.info("unable to find project path by classloader", e);
085                            }
086            
087                            if (classes == null) {
088                                    projectDir = new File(".").getCanonicalPath();
089                                    log.info("project path fram current directory: " + projectDir);
090                            } else {
091                                    projectDir = classes.toURI().resolve("../..").getPath();
092                                    log.info("project path as found by classloader: " + projectDir);
093                            }
094            
095                            String contextPath = System.getProperty("jetty.contextpath", 
096                                            System.getProperty("jetty.contextPath"));
097                            if (Strings.isEmpty(contextPath)) {
098                                    Matcher m = Pattern.compile("(\\/[^\\/]+)/?$").matcher(projectDir);
099                                    if (!m.find())
100                                            throw new RuntimeException("Project path not as expected: " + projectDir);
101                                    contextPath = m.group(1);
102                                    log.info("context path by project directory: " + contextPath);
103                            }
104                            else
105                                    log.info("jetty.contextPath property: " + contextPath);
106                            web.setContextPath(contextPath);
107                            
108                            String warPath = System.getProperty("jetty.warpath", 
109                                            System.getProperty("jetty.warPath"));
110                            if (Strings.isEmpty(warPath)) warPath = projectDir + "src/main/webapp";
111                            
112                            if (!new File(warPath).isDirectory()) {
113                                    log.error("Unable to find webapps path: " + warPath +
114                                            " \nPlease ensure that this project is the first on its " +
115                                            "classpath, or set a valid jetty.warpath JVM property.");
116                                    return;
117                            }
118            
119                            
120                            else log.info("jetty.warPath property: " + warPath);
121                            web.setWar(warPath);
122                            
123                            server.addHandler(web);
124                            
125                            if (!contextPath.equals("/"))
126                                    server.addHandler(new MovedContextHandler(server, "/", contextPath));
127            
128                            List<Connector> conns = new ArrayList<Connector>(2);
129                            
130                            int httpPort = 8080;
131                            try {
132                                    httpPort = Integer.valueOf(System.getProperty("jetty.port"));
133                                    log.info("jetty.port property: " + httpPort);
134                            } catch (NumberFormatException e) { }
135                            
136                            if (httpPort != 0) {
137                                    SelectChannelConnector httpConn = new SelectChannelConnector();
138                                    httpConn.setPort(httpPort);
139                                    conns.add(httpConn);
140                            }
141            
142                            int ajpPort = 0;
143                            try {
144                                    ajpPort = Integer.valueOf(System.getProperty("jetty.ajp.port"));
145                                    log.info("jetty.ajp.port property: " + ajpPort);
146                            } catch (NumberFormatException e) { }
147                            
148                            if (ajpPort != 0) {
149                                    Ajp13SocketConnector ajpConn = new Ajp13SocketConnector();
150                                    ajpConn.setPort(ajpPort);
151                                    conns.add(ajpConn);
152                            }
153                            
154                            server.setConnectors(conns.toArray(new Connector[conns.size()]));
155                            server.setStopAtShutdown(true);
156                            
157                            configure(server, web);
158            
159                            server.start();
160                            if (httpPort != 0)
161                                    log.info("Ready at http://localhost:" + httpPort + contextPath);
162                            if (ajpPort != 0)
163                                    log.info("Ready at ajp://localhost:" + ajpPort + contextPath);
164                            server.join();
165                            
166                            stopped(server, web);
167                    } catch (Exception e) {
168                            throw new RuntimeException(e);
169                    }
170            }
171            
172            /**
173             * Override to customize the server and context objects.
174             * @see DataServer#DataServer()
175             */
176            protected void configure(Server server, WebAppContext context) throws Exception { }
177            
178            /**
179             * Override to perform action after server stops
180             * @see DataServer#DataServer()
181             */
182            protected void stopped(Server server, WebAppContext context) throws Exception {  }
183    }