package com.collabinate.server.webserver;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import org.apache.commons.configuration.Configuration;
import org.restlet.Application;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.data.ChallengeScheme;
import org.restlet.resource.Directory;
import org.restlet.routing.Filter;
import org.restlet.routing.Router;
import org.restlet.routing.Template;
import org.restlet.security.Authenticator;
import org.restlet.security.ChallengeAuthenticator;
import org.restlet.security.SecretVerifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.collabinate.server.Collabinate;
import com.collabinate.server.adminresources.*;
import com.collabinate.server.engine.CollabinateAdmin;
import com.collabinate.server.engine.CollabinateReader;
import com.collabinate.server.engine.CollabinateWriter;
import com.collabinate.server.resources.*;
import com.google.common.base.Splitter;
/**
* Main Restlet application
*
* @author mafuba
*
*/
public class CollabinateApplication extends Application
{
private CollabinateReader reader;
private CollabinateWriter writer;
private CollabinateAdmin admin;
private Authenticator authenticator;
/**
* Static logger.
*/
private static final Logger logger =
LoggerFactory.getLogger(CollabinateApplication.class);
/**
* Sets the application properties.
*/
public CollabinateApplication(
CollabinateReader reader,
CollabinateWriter writer,
CollabinateAdmin admin,
Authenticator authenticator)
{
if (null == reader)
throw new IllegalArgumentException("reader must not be null");
if (null == writer)
throw new IllegalArgumentException("writer must not be null");
setName("Collabinate");
this.reader = reader;
this.writer = writer;
this.admin = admin;
this.authenticator = authenticator;
}
@Override
public Restlet createInboundRoot()
{
if (null == reader || null == writer || null == authenticator)
{
throw new IllegalStateException(
"reader, writer, and authenticator must not be null");
}
getContext().getAttributes().put("collabinateReader", reader);
getContext().getAttributes().put("collabinateWriter", writer);
if (null != admin)
getContext().getAttributes().put("collabinateAdmin", admin);
// primary router is the in-bound root - the first router
Router primaryRouter = new Router(getContext());
// admin resources are handled specially
Authenticator adminAuthenticator = getAdminAuthenticator();
primaryRouter.attach("/{apiVersion}/admin", adminAuthenticator)
.setMatchingMode(Template.MODE_STARTS_WITH);
Router adminRouter = new Router(getContext());
adminRouter.attach("/database", DatabaseResource.class);
adminRouter.attach("/tenants/{tenantId}", TenantResource.class);
adminRouter.attach("/tenants/{tenantId}/data",
TenantDataResource.class);
adminRouter.attach("/tenants/{tenantId}/keys/{key}",
TenantKeyResource.class);
adminRouter.attach("/tenants/{tenantId}/keys",
TenantKeysResource.class);
adminRouter.attach("/tenants", TenantsResource.class);
adminRouter.attach("/service/resetrequest", ResetRequestResource.class);
adminAuthenticator.setNext(adminRouter);
// resource router handles the routing for post-authentication resources
Router resourceRouter = new Router(getContext());
resourceRouter.attach("/entities/{entityId}/stream/{activityId}",
ActivityResource.class);
resourceRouter.attach("/entities/{entityId}/stream",
StreamResource.class);
resourceRouter.attach("/entities/{entityId}/followers",
FollowersResource.class);
resourceRouter.attach(
"/entities/{entityId}/stream/{activityId}/comments",
CommentsResource.class);
resourceRouter.attach(
"/entities/{entityId}/stream/{activityId}/comments/{commentId}",
CommentResource.class);
resourceRouter.attach(
"/entities/{entityId}/stream/{activityId}/likes",
LikesResource.class);
resourceRouter.attach("/entities/{entityId}", EntityResource.class);
resourceRouter.attach("/users/{userId}/following",
FollowingResource.class);
resourceRouter.attach("/users/{userId}/following/{entityId}",
FollowingEntityResource.class);
resourceRouter.attach("/users/{userId}/feed", FeedResource.class);
resourceRouter.attach("/users/{userId}/likes/{entityId}/{activityId}",
LikeResource.class);
// plugin resource paths skip the authenticator
addPlugins(primaryRouter, resourceRouter);
// normal resource paths go through the authenticator
primaryRouter.attach("/{apiVersion}/{tenantId}", authenticator)
.setMatchingMode(Template.MODE_STARTS_WITH);
// trace resource for client debugging
primaryRouter.attach("/trace", TraceResource.class);
// directory resource for static content
primaryRouter.attach("/", getStaticDirectoryResource());
authenticator.setNext(resourceRouter);
return primaryRouter;
}
/**
* Provides an authenticator for administration resources.
*
* @return An authenticator for securing administration resources.
*/
private Authenticator getAdminAuthenticator()
{
final String adminUsername = Collabinate.getConfiguration()
.getString(ADMIN_USERNAME, "");
final String adminPassword = Collabinate.getConfiguration()
.getString(ADMIN_PASSWORD, "");
// only authenticate if the admin credentials are configured
if (adminUsername.equals(""))
{
return new Authenticator(null) {
@Override
protected boolean authenticate(Request request, Response response)
{
return true;
}
};
}
return new ChallengeAuthenticator(
getContext(),
false,
ChallengeScheme.HTTP_BASIC,
"Collabinate",
new SecretVerifier() {
@Override
public int verify(String identifier, char[] secret)
{
return ((adminUsername.equals(identifier)) &&
compare(adminPassword.toCharArray(), secret)) ?
RESULT_VALID : RESULT_INVALID;
}
});
}
/**
* Loads filter plugins as specified in configuration.
*
* @param primaryRouter The router which will pass control to the filter.
* @param resourceRouter The router to which the filter will attach.
*/
private void addPlugins(Router primaryRouter, Router resourceRouter)
{
// get the configuration strings that define the plugins
String[] pluginsList = Collabinate.getConfiguration()
.getStringArray(FILTER_PLUGINS);
logger.debug("Found {} filter plugins", pluginsList.length);
// loop over each plugin
for (String pluginConfig : pluginsList)
{
// separate the class name from the URL pattern
List<String> pluginValues = Splitter.on(";").omitEmptyStrings()
.trimResults().splitToList(pluginConfig);
if (2 != pluginValues.size())
{
logger.error("Invalid plugin configuration: {}", pluginConfig);
continue;
}
String pluginClassName = pluginValues.get(0);
String pluginUrlPattern = pluginValues.get(1);
try
{
// instantiate the plugin
Class<?> pluginClass = getClass().getClassLoader()
.loadClass(pluginClassName);
Constructor<?> constructor = pluginClass
.getConstructor(Context.class, Configuration.class);
Filter plugin =
(Filter)constructor.newInstance(
getContext(), Collabinate.getConfiguration());
// set up the routing path with the plugin
plugin.setNext(resourceRouter);
primaryRouter.attach(pluginUrlPattern, plugin);
logger.info("Loaded plugin: {} for path: {}", pluginClassName,
pluginUrlPattern);
}
catch (ClassNotFoundException e)
{
logger.error("Plugin class not found: " + pluginClassName, e);
}
catch (NoSuchMethodException e)
{
logger.error("Invalid constructor (requires Context, " +
"Configuration) for plugin class: " + pluginClassName, e);
}
catch (InvocationTargetException e)
{
logger.error("Problem instantiating plugin: " +
pluginClassName, e);
}
catch (IllegalAccessException e)
{
logger.error("Problem instantiating plugin: " +
pluginClassName, e);
}
catch (InstantiationException e)
{
logger.error("Problem instantiating plugin: " +
pluginClassName, e);
}
}
}
/**
* Creates a directory resource used for static content.
*
* @return A Directory used for static content.
*/
private Directory getStaticDirectoryResource()
{
URL staticFolderUrl;
try
{
staticFolderUrl = (new File("static")).toURI().toURL();
}
catch (MalformedURLException e)
{
throw new IllegalStateException(
"Could not find static content folder 'static'", e);
}
return new Directory(getContext(), staticFolderUrl.toString());
}
private static final String ADMIN_USERNAME =
"collabinate.server.webserver.admin.username";
private static final String ADMIN_PASSWORD =
"collabinate.server.webserver.admin.password";
private static final String FILTER_PLUGINS =
"collabinate.server.webserver.filterplugins";
}