First draft of session manager using gcloud datastore.
This commit is contained in:
parent
635c8ff7f6
commit
2f4f639735
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.gcloud</groupId>
|
||||
<artifactId>gcloud-parent</artifactId>
|
||||
<version>9.3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>gcloud-session-manager</artifactId>
|
||||
<name>Jetty :: GCloud :: Session Manager</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.gcloud</groupId>
|
||||
<artifactId>gcloud-java-datastore</artifactId>
|
||||
<version>${gcloud.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-servlet</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-test-helper</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.session</bundle-symbolic-name>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Export-Package>org.eclipse.jetty.gcloud.session.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}";</Export-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,184 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
|
||||
package org.eclipse.jetty.gcloud.session;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.eclipse.jetty.util.security.Password;
|
||||
|
||||
import com.google.gcloud.AuthCredentials;
|
||||
import com.google.gcloud.datastore.DatastoreOptions;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* GCloudConfiguration
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class GCloudConfiguration
|
||||
{
|
||||
public static final String PROJECT_ID = "projectId";
|
||||
public static final String P12 = "p12";
|
||||
public static final String PASSWORD = "password";
|
||||
public static final String SERVICE_ACCOUNT = "serviceAccount";
|
||||
|
||||
private String _projectId;
|
||||
private File _p12File;
|
||||
private String _serviceAccount;
|
||||
private String _password;
|
||||
private AuthCredentials _authCredentials;
|
||||
private DatastoreOptions _options;
|
||||
|
||||
/**
|
||||
* Generate a configuration from a properties file
|
||||
*
|
||||
* @param propsFile
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static GCloudConfiguration fromFile(String propsFile)
|
||||
throws IOException
|
||||
{
|
||||
if (propsFile == null)
|
||||
throw new IllegalArgumentException ("Null properties file");
|
||||
|
||||
File f = new File(propsFile);
|
||||
if (!f.exists())
|
||||
throw new IllegalArgumentException("No such file "+f.getAbsolutePath());
|
||||
Properties props = new Properties();
|
||||
try (FileInputStream is=new FileInputStream(f))
|
||||
{
|
||||
props.load(is);
|
||||
}
|
||||
|
||||
GCloudConfiguration config = new GCloudConfiguration();
|
||||
config.setProjectId(props.getProperty(PROJECT_ID));
|
||||
config.setP12File(props.getProperty(P12));
|
||||
config.setPassword(props.getProperty(PASSWORD));
|
||||
config.setServiceAccount(props.getProperty(SERVICE_ACCOUNT));
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getProjectId()
|
||||
{
|
||||
return _projectId;
|
||||
}
|
||||
|
||||
public File getP12File()
|
||||
{
|
||||
return _p12File;
|
||||
}
|
||||
|
||||
public String getServiceAccount()
|
||||
{
|
||||
return _serviceAccount;
|
||||
}
|
||||
|
||||
|
||||
public void setProjectId(String projectId)
|
||||
{
|
||||
checkForModification();
|
||||
_projectId = projectId;
|
||||
}
|
||||
|
||||
public void setP12File (String file)
|
||||
{
|
||||
checkForModification();
|
||||
_p12File = new File(file);
|
||||
}
|
||||
|
||||
|
||||
public void setServiceAccount (String serviceAccount)
|
||||
{
|
||||
checkForModification();
|
||||
_serviceAccount = serviceAccount;
|
||||
}
|
||||
|
||||
|
||||
public void setPassword (String pwd)
|
||||
{
|
||||
checkForModification();
|
||||
Password p = new Password(pwd);
|
||||
_password = p.toString();
|
||||
}
|
||||
|
||||
|
||||
public DatastoreOptions getDatastoreOptions ()
|
||||
throws Exception
|
||||
{
|
||||
if (_options == null)
|
||||
{
|
||||
_options = DatastoreOptions.builder()
|
||||
.projectId(_projectId)
|
||||
.authCredentials(getAuthCredentials())
|
||||
.build();
|
||||
}
|
||||
return _options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public AuthCredentials getAuthCredentials()
|
||||
throws Exception
|
||||
{
|
||||
if (_authCredentials == null)
|
||||
{
|
||||
if (_password == null)
|
||||
throw new IllegalStateException("No password");
|
||||
if (_projectId == null)
|
||||
throw new IllegalStateException("No project id");
|
||||
|
||||
if (_projectId == null)
|
||||
throw new IllegalStateException("No project id");
|
||||
|
||||
if (_p12File == null || !_p12File.exists())
|
||||
throw new IllegalStateException("No p12 file: "+(_p12File==null?"null":_p12File.getAbsolutePath()));
|
||||
|
||||
if (_serviceAccount == null)
|
||||
throw new IllegalStateException("No service account");
|
||||
|
||||
char[] pwdChars = _password.toCharArray();
|
||||
KeyStore keystore = KeyStore.getInstance("PKCS12");
|
||||
keystore.load(new FileInputStream(getP12File()), pwdChars);
|
||||
PrivateKey privateKey = (PrivateKey) keystore.getKey("privatekey", pwdChars);
|
||||
_authCredentials = AuthCredentials.createFor(getServiceAccount(), privateKey);
|
||||
}
|
||||
return _authCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
protected void checkForModification () throws IllegalStateException
|
||||
{
|
||||
if (_authCredentials != null || _options != null)
|
||||
throw new IllegalStateException("Cannot modify auth configuration after datastore initialized");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,323 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.gcloud.session;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.SessionManager;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.server.session.AbstractSession;
|
||||
import org.eclipse.jetty.server.session.AbstractSessionIdManager;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
import com.google.gcloud.datastore.Datastore;
|
||||
import com.google.gcloud.datastore.DatastoreFactory;
|
||||
import com.google.gcloud.datastore.Entity;
|
||||
import com.google.gcloud.datastore.Key;
|
||||
import com.google.gcloud.datastore.KeyFactory;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* GCloudSessionIdManager
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class GCloudSessionIdManager extends AbstractSessionIdManager
|
||||
{
|
||||
private final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
|
||||
public static final int DEFAULT_IDLE_EXPIRY_MULTIPLE = 2;
|
||||
public static final String KIND = "GCloudSessionId";
|
||||
private Server _server;
|
||||
private Datastore _datastore;
|
||||
private KeyFactory _keyFactory;
|
||||
private GCloudConfiguration _config;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param server
|
||||
*/
|
||||
public GCloudSessionIdManager(Server server)
|
||||
{
|
||||
super();
|
||||
_server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param server
|
||||
* @param random
|
||||
*/
|
||||
public GCloudSessionIdManager(Server server, Random random)
|
||||
{
|
||||
super(random);
|
||||
_server = server;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Start the id manager.
|
||||
* @see org.eclipse.jetty.server.session.AbstractSessionIdManager#doStart()
|
||||
*/
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
if (_config == null)
|
||||
throw new IllegalStateException("No gcloud configuration specified");
|
||||
|
||||
|
||||
_datastore = DatastoreFactory.instance().get(_config.getDatastoreOptions());
|
||||
_keyFactory = _datastore.newKeyFactory().kind(KIND);
|
||||
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Stop the id manager
|
||||
* @see org.eclipse.jetty.server.session.AbstractSessionIdManager#doStop()
|
||||
*/
|
||||
@Override
|
||||
protected void doStop() throws Exception
|
||||
{
|
||||
super.doStop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check to see if the given session id is being
|
||||
* used by a session in any context.
|
||||
*
|
||||
* This method will consult the cluster.
|
||||
*
|
||||
* @see org.eclipse.jetty.server.SessionIdManager#idInUse(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean idInUse(String id)
|
||||
{
|
||||
if (id == null)
|
||||
return false;
|
||||
|
||||
String clusterId = getClusterId(id);
|
||||
|
||||
//ask the cluster - this should also tickle the idle expiration timer on the sessionid entry
|
||||
//keeping it valid
|
||||
try
|
||||
{
|
||||
return exists(clusterId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Problem checking inUse for id="+clusterId, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember a new in-use session id.
|
||||
*
|
||||
* This will save the in-use session id to the cluster.
|
||||
*
|
||||
* @see org.eclipse.jetty.server.SessionIdManager#addSession(javax.servlet.http.HttpSession)
|
||||
*/
|
||||
@Override
|
||||
public void addSession(HttpSession session)
|
||||
{
|
||||
if (session == null)
|
||||
return;
|
||||
|
||||
//insert into the store
|
||||
insert (((AbstractSession)session).getClusterId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public GCloudConfiguration getConfig()
|
||||
{
|
||||
return _config;
|
||||
}
|
||||
|
||||
public void setConfig(GCloudConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Remove a session id from the list of in-use ids.
|
||||
*
|
||||
* This will remvove the corresponding session id from the cluster.
|
||||
*
|
||||
* @see org.eclipse.jetty.server.SessionIdManager#removeSession(javax.servlet.http.HttpSession)
|
||||
*/
|
||||
@Override
|
||||
public void removeSession(HttpSession session)
|
||||
{
|
||||
if (session == null)
|
||||
return;
|
||||
|
||||
//delete from the cache
|
||||
delete (((AbstractSession)session).getClusterId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a session id. This compels all other contexts who have a session
|
||||
* with the same id to also remove it.
|
||||
*
|
||||
* @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void invalidateAll(String id)
|
||||
{
|
||||
//delete the session id from list of in-use sessions
|
||||
delete (id);
|
||||
|
||||
|
||||
//tell all contexts that may have a session object with this id to
|
||||
//get rid of them
|
||||
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
|
||||
for (int i=0; contexts!=null && i<contexts.length; i++)
|
||||
{
|
||||
SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
|
||||
if (sessionHandler != null)
|
||||
{
|
||||
SessionManager manager = sessionHandler.getSessionManager();
|
||||
|
||||
if (manager != null && manager instanceof GCloudSessionManager)
|
||||
{
|
||||
((GCloudSessionManager)manager).invalidateSession(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a session id.
|
||||
*
|
||||
* Typically this occurs when a previously existing session has passed through authentication.
|
||||
*
|
||||
* @see org.eclipse.jetty.server.session.AbstractSessionIdManager#renewSessionId(java.lang.String, java.lang.String, javax.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
@Override
|
||||
public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request)
|
||||
{
|
||||
//generate a new id
|
||||
String newClusterId = newSessionId(request.hashCode());
|
||||
|
||||
delete(oldClusterId);
|
||||
insert(newClusterId);
|
||||
|
||||
|
||||
//tell all contexts to update the id
|
||||
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
|
||||
for (int i=0; contexts!=null && i<contexts.length; i++)
|
||||
{
|
||||
SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
|
||||
if (sessionHandler != null)
|
||||
{
|
||||
SessionManager manager = sessionHandler.getSessionManager();
|
||||
|
||||
if (manager != null && manager instanceof GCloudSessionManager)
|
||||
{
|
||||
((GCloudSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Ask the datastore if a particular id exists.
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
protected boolean exists (String id)
|
||||
{
|
||||
if (_datastore == null)
|
||||
throw new IllegalStateException ("No DataStore");
|
||||
Key key = _keyFactory.newKey(id);
|
||||
return _datastore.get(key) != null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put a session id into the cluster.
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
protected void insert (String id)
|
||||
{
|
||||
if (_datastore == null)
|
||||
throw new IllegalStateException ("No DataStore");
|
||||
|
||||
Entity entity = Entity.builder(makeKey(id))
|
||||
.set("id", id).build();
|
||||
_datastore.put(entity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Remove a session id from the cluster.
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
protected void delete (String id)
|
||||
{
|
||||
if (_datastore == null)
|
||||
throw new IllegalStateException ("No DataStore");
|
||||
|
||||
_datastore.delete(makeKey(id));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generate a unique key from the session id.
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
protected Key makeKey (String id)
|
||||
{
|
||||
return _keyFactory.newKey(id);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.gcloud.session;
|
||||
|
||||
|
||||
|
||||
|
||||
import org.eclipse.jetty.security.HashLoginService;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
|
||||
public class GCloudSessionTester
|
||||
{
|
||||
public static void main( String[] args ) throws Exception
|
||||
{
|
||||
System.setProperty("org.eclipse.jetty.server.session.LEVEL", "DEBUG");
|
||||
|
||||
Server server = new Server(8080);
|
||||
HashLoginService loginService = new HashLoginService();
|
||||
loginService.setName( "Test Realm" );
|
||||
loginService.setConfig( "../../jetty-distribution/target/distribution/demo-base/resources/realm.properties" );
|
||||
server.addBean( loginService );
|
||||
|
||||
GCloudConfiguration config = new GCloudConfiguration();
|
||||
config.setProjectId("jetty9-work");
|
||||
config.setP12File("/tmp/jetty9-work-51c6d017a35b.p12");
|
||||
config.setPassword("OBF:1xtl1v2h1ym91rwd1vv11vu91rwh1ym51v1x1xtx");
|
||||
config.setServiceAccount("170642172088-gmvs56i7pkqoi7g9cha8o75n0jo3us2j@developer.gserviceaccount.com");
|
||||
|
||||
GCloudSessionIdManager idmgr = new GCloudSessionIdManager(server);
|
||||
idmgr.setConfig(config);
|
||||
idmgr.setWorkerName("w1");
|
||||
server.setSessionIdManager(idmgr);
|
||||
|
||||
|
||||
WebAppContext webapp = new WebAppContext();
|
||||
webapp.setContextPath("/");
|
||||
webapp.setResourceBase("../../jetty-distribution/target/distribution/demo-base/webapps/test/");
|
||||
webapp.addAliasCheck(new AllowSymLinkAliasChecker());
|
||||
GCloudSessionManager mgr = new GCloudSessionManager();
|
||||
mgr.setSessionIdManager(idmgr);
|
||||
webapp.setSessionHandler(new SessionHandler(mgr));
|
||||
|
||||
// A WebAppContext is a ContextHandler as well so it needs to be set to
|
||||
// the server so it is aware of where to send the appropriate requests.
|
||||
server.setHandler(webapp);
|
||||
|
||||
// Start things up!
|
||||
server.start();
|
||||
|
||||
|
||||
server.join();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>jetty-project</artifactId>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<version>9.3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.eclipse.jetty.gcloud</groupId>
|
||||
<artifactId>gcloud-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Jetty :: GCloud</name>
|
||||
|
||||
<properties>
|
||||
<gcloud.version>0.0.6</gcloud.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>gcloud-session-manager</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
Loading…
Reference in New Issue