/*==========================================================================*\
| $Id: EntityResourceRequestHandler.java,v 1.11 2011/12/25 02:24:54 stedwar2 Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2010-2011 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU Affero General Public License as published
| by the Free Software Foundation; either version 3 of the License, or
| (at your option) any later version.
|
| Web-CAT is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package org.webcat.core;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.webcat.core.Application;
import org.webcat.core.EntityResourceRequestHandler;
import org.webcat.core.EntityResourceHandler;
import org.webcat.core.Session;
import org.webcat.woextensions.ECAction;
import static org.webcat.woextensions.ECAction.run;
import org.apache.log4j.Logger;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WORequestHandler;
import com.webobjects.appserver.WOResponse;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOUtilities;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSMutableDictionary;
import er.extensions.eof.ERXQ;
//-------------------------------------------------------------------------
/**
* <p>
* A request handler that allows subsystems to make file-system resources that
* they generate and associate with EOs directly visible on the web and allow
* resources associated with the same EO to be relatively linked.
* </p><p>
* URLs should be of the form:
* <pre>http://server/Web-CAT.wo/er/[session id]/[EO type]/[EO id]/[path/to/the/file]</pre>
* where "er" is this request handler's key, "session id" is the session
* identifier (which is optional), "EO type" is the name of the entity
* whose resources are being requested, "EO id" is the numeric ID of the
* entity, and "path/to/the/file" is the path to the resource, relative to
* whatever file-system location the particular entity deems fit for its
* related resources.
* </p>
*
* @author Tony Allevato
* @author Last changed by $Author: stedwar2 $
* @version $Revision: 1.11 $, $Date: 2011/12/25 02:24:54 $
*/
public class EntityResourceRequestHandler
extends WORequestHandler
{
//~ Methods ...............................................................
// ----------------------------------------------------------
/**
* Gets a relative URL to access an entity-related resource.
*
* @param context the request context
* @param eo the EO whose resource should be accessed
* @param relativePath the entity-relative path to the resource
* @return the URL to the resource
*/
public static String urlForEntityResource(WOContext context,
EOEnterpriseObject eo, String relativePath)
{
String entityName = eo.entityName();
Number id = (Number) eo.valueForKey("id");
StringBuffer buffer = new StringBuffer();
String sessionString = null;
if (context.session().storesIDsInURLs())
{
sessionString = "wosid=" + context.session().sessionID();
}
buffer.append(entityName);
buffer.append("/");
buffer.append(id);
if (relativePath != null && relativePath.length() > 0)
{
buffer.append("/");
buffer.append(relativePath);
}
return context.urlWithRequestHandlerKey(REQUEST_HANDLER_KEY,
buffer.toString(), sessionString);
}
// ----------------------------------------------------------
/**
* Registers an entity resource handler with the request handler.
*
* @param entityClass the Java class of the EO
* @param handler a resource handler
*/
public static void registerHandler(Class<?> entityClass,
EntityResourceHandler<?> handler)
{
log.debug("Registering entity resource handler for class "
+ entityClass.getCanonicalName());
resourceHandlers.setObjectForKey(handler, entityClass);
}
// ----------------------------------------------------------
/**
* Finds the entity-related resource from the given request and creates a
* response containing its data.
*
* @param request the request
* @return the response
*/
@Override
public WOResponse handleRequest(WORequest request)
{
log.debug("Received request: " + request.requestHandlerPath());
WOContext context =
Application.application().createContextForRequest(request);
WOResponse response =
Application.application().createResponseInContext(context);
// Get the user's session, if possible. We'll use it later for
// resources that require logins (for user validation) in order to be
// accessed.
Session session = null;
String sessionId = request.sessionID();
if (sessionId != null)
{
try
{
session = (Session)
Application.application().restoreSessionWithID(
sessionId, context);
}
catch (Exception e)
{
session = null;
}
}
try
{
// Perform the actual request handling.
_handleRequest(request, context, response, session);
}
catch (Exception e)
{
log.warn("(404) An exception occurred when handling the request "
+ "for " + request.requestHandlerPath(), e);
response.setContent("");
response.setStatus(WOResponse.HTTP_STATUS_NOT_FOUND);
}
finally
{
if (session != null)
{
Application.application().saveSessionForContext(context);
}
}
return response;
}
// ----------------------------------------------------------
private void _handleRequest(
final WORequest request,
final WOContext context,
final WOResponse response,
final Session session)
{
final String handlerPath = request.requestHandlerPath();
// Parse the request path into its entity, object ID, and resource
// path.
final EntityRequestInfo entityRequest =
EntityRequestInfo.fromRequestHandlerPath(handlerPath);
if (entityRequest == null
|| !validatePath(entityRequest.resourcePath()))
{
log.warn("(404) The request path was malformed: "
+ request.requestHandlerPath());
response.setStatus(WOResponse.HTTP_STATUS_NOT_FOUND);
return;
}
run(new ECAction() { public void action() {
EntityResourceHandler<EOEnterpriseObject> handler =
handlerForEntityNamed(entityRequest.entityName(), ec);
if (handler != null)
{
log.debug("Found handler for entity "
+ entityRequest.entityName());
if (handler.requiresLogin() && session == null)
{
log.warn("(403) Handler requires log-in, but no session "
+ "found with id " + request.sessionID());
response.setStatus(WOResponse.HTTP_STATUS_FORBIDDEN);
}
else
{
EOEnterpriseObject object = fetchObject(
entityRequest, handler, ec);
if (object != null)
{
if (canAccessObject(object, handler, session))
{
generateResponse(response, handler, object,
entityRequest.resourcePath());
}
else
{
String userName = (session != null
? session.user().userName() : "<null>");
log.warn("(403) User " + userName
+ " tried to access entity resource "
+ "without permission");
response.setStatus(
WOResponse.HTTP_STATUS_FORBIDDEN);
}
}
else
{
log.warn("(404) Attempted to access entity resource "
+ "for an object that does not exist: "
+ entityRequest.entityName()
+ ":" + entityRequest.objectID());
response.setStatus(WOResponse.HTTP_STATUS_NOT_FOUND);
}
}
}
else
{
log.warn("(404) No entity request handler was found for "
+ entityRequest.entityName());
response.setStatus(WOResponse.HTTP_STATUS_NOT_FOUND);
}
}});
}
// ----------------------------------------------------------
/**
* Gets the {@link EntityResourceHandler} for the entity with the specified
* name. This method returns null if the handler could not be found (either
* because there is no entity with that name or because no handler was
* registered for that entity).
*
* @param entityName the entity name
* @param ec an editing context
*
* @return an entity resource handler, or null
*/
private EntityResourceHandler<EOEnterpriseObject> handlerForEntityNamed(
String entityName, EOEditingContext ec)
{
EOEntity ent = EOUtilities.entityNamed(ec, entityName);
Class<?> entityClass = null;
if (ent != null)
{
try
{
entityClass = Class.forName(ent.className());
}
catch (ClassNotFoundException e)
{
// Do nothing; error will be handled below.
}
}
if (entityClass != null)
{
return resourceHandlers.objectForKey(entityClass);
}
else
{
return null;
}
}
// ----------------------------------------------------------
/**
* A somewhat brainless check to make sure that the path provided does not
* go higher up into the file-system than it should. We verify that it is
* a relative path and that it does not contain any parent directory
* references that would move it above its origin.
*/
private static boolean validatePath(String path)
{
if (path == null)
{
return true;
}
File file = new File(path);
int level = 0;
String[] components = file.getPath().split("/");
for (String component : components)
{
if (component.equals(".."))
{
level--;
}
else if (!component.equals("."))
{
level++;
}
if (level < 0)
{
log.warn("Attempted to access bad relative path (" + path
+ ") in entity resource handler");
return false;
}
}
return true;
}
// ----------------------------------------------------------
private EOEnterpriseObject fetchObject(EntityRequestInfo entityRequest,
EntityResourceHandler<?> handler,
EOEditingContext ec)
{
EOFetchSpecification fspec = null;
try
{
long id = Long.parseLong(entityRequest.objectID());
fspec = new EOFetchSpecification(entityRequest.entityName(),
ERXQ.is("id", id), null);
}
catch (NumberFormatException e)
{
fspec = handler.fetchSpecificationForFriendlyName(
entityRequest.objectID());
}
if (fspec != null)
{
@SuppressWarnings("unchecked")
NSArray<? extends EOEnterpriseObject> objects =
ec.objectsWithFetchSpecification(fspec);
if (objects != null && objects.count() > 0)
{
return objects.objectAtIndex(0);
}
}
return null;
}
// ----------------------------------------------------------
private boolean canAccessObject(
EOEnterpriseObject object,
EntityResourceHandler<EOEnterpriseObject> handler,
Session session)
{
if (!handler.requiresLogin())
{
return true;
}
else if (session == null)
{
return false;
}
else
{
User user = session.user();
return user.hasAdminPrivileges()
|| handler.userCanAccess(object, user);
}
}
// ----------------------------------------------------------
private void generateResponse(
WOResponse response,
EntityResourceHandler<EOEnterpriseObject> handler,
EOEnterpriseObject object,
String path)
{
File absolutePath = handler.pathForResource(object, path);
if (absolutePath != null && absolutePath.exists())
{
if (!absolutePath.isFile())
{
for (String indexFilename : indexFilenames)
{
File indexPath = new File(absolutePath, indexFilename);
if (indexPath.isFile())
{
log.debug("Found index file at "
+ indexPath.getAbsolutePath()
+ ", using this as response");
absolutePath = indexPath;
break;
}
else
{
log.debug("Could not find index file "
+ indexPath.getAbsolutePath());
}
}
}
if (!absolutePath.isFile())
{
log.warn("(403) Cannot generate response from directory " +
absolutePath.getAbsolutePath());
response.setStatus(WOResponse.HTTP_STATUS_FORBIDDEN);
}
else
{
log.debug("Generating response from contents of file: "
+ absolutePath.getAbsolutePath());
FileInputStream stream = null;
try
{
stream = new FileInputStream(absolutePath);
response.setContent(new NSData(stream, 0));
response.setStatus(WOResponse.HTTP_STATUS_OK);
}
catch (IOException e)
{
log.warn("(404) An exception occurred when loading the "
+ "content from " + absolutePath.getAbsolutePath());
response.setStatus(WOResponse.HTTP_STATUS_NOT_FOUND);
}
finally
{
if (stream != null)
{
try
{
stream.close();
}
catch (IOException e)
{
// Do nothing.
}
}
}
}
}
else
{
String absPath = (absolutePath == null ? "<null>"
: absolutePath.getAbsolutePath());
log.warn("(404) The path " + absPath + " does not exist");
response.setStatus(WOResponse.HTTP_STATUS_NOT_FOUND);
}
}
//~ Static/instance variables .............................................
public static final String REQUEST_HANDLER_KEY = "er";
private static final NSMutableDictionary<
Class<?>, EntityResourceHandler<EOEnterpriseObject>>
resourceHandlers = new NSMutableDictionary<
Class<?>, EntityResourceHandler<EOEnterpriseObject>>();
private static String[] indexFilenames = { "index.html", "index.htm" };
private static final Logger log = Logger.getLogger(
EntityResourceRequestHandler.class);
}