First cut of the AJAX framework

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@3327 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Gavin Cornwell
2006-07-14 19:08:42 +00:00
parent 521be49ef9
commit 9f0066637a
9 changed files with 6111 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
package org.alfresco.web.app.servlet.ajax;
import java.io.IOException;
import javax.faces.context.FacesContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Interface for all Ajax commands executed by this servlet.
*
* The method is responsible for invoking the underlying managed bean
* and dealing with the response.
*
* @author gavinc
*/
public interface AjaxCommand
{
/**
* Invokes the relevant method on the bean represented by the given
* expression. Parameters required to call the method can be retrieved
* from the request.
*
* Currently the content type of the response will always be text/html, in the
* future sublcasses may provide a mechanism to allow the content type to be set
* dynamically.
*
* @param facesContext FacesContext
* @param expression The binding expression
* @param request The request
* @param response The response
*/
public abstract void execute(FacesContext facesContext, String expression,
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException;
}

View File

@@ -0,0 +1,176 @@
package org.alfresco.web.app.servlet.ajax;
import java.io.IOException;
import java.util.Enumeration;
import java.util.StringTokenizer;
import javax.faces.context.FacesContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.web.app.servlet.AuthenticationStatus;
import org.alfresco.web.app.servlet.BaseServlet;
import org.alfresco.web.app.servlet.FacesHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Servlet responsible for processing AJAX requests.
*
* The URL to the servlet should be in the form:
* <pre>/alfresco/ajax/command/Bean.binding.expression</pre>
* <p>
* where 'command' is one of 'invoke', 'get' or 'set'.
* <p>
* TODO: Explain what the commands do...
* <p>
* Like most Alfresco servlets, the URL may be followed by a valid 'ticket' argument for authentication:
* ?ticket=1234567890
*
* @author gavinc
*/
public class AjaxServlet extends BaseServlet
{
public static final String AJAX_LOG_KEY = "alfresco.ajax";
protected enum Command { invoke, get, set};
private static final long serialVersionUID = -7654769105419391840L;
private static Log logger = LogFactory.getLog(AJAX_LOG_KEY);
private static Log headersLogger = LogFactory.getLog(AJAX_LOG_KEY + ".headers");
/**
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
try
{
String uri = request.getRequestURI();
if (logger.isDebugEnabled())
{
String queryString = request.getQueryString();
logger.debug("Processing URL: " + uri +
((queryString != null && queryString.length() > 0) ? ("?" + queryString) : ""));
}
// dump the request headers
if (headersLogger.isDebugEnabled())
{
Enumeration headers = request.getHeaderNames();
while (headers.hasMoreElements())
{
String name = (String)headers.nextElement();
headersLogger.debug(name + ": " + request.getHeader(name));
}
}
// ************
// TODO: Need to send in a flag to method to stop it from redirecting
// to login page, we can then throw an error in here!!
AuthenticationStatus status = servletAuthenticate(request, response);
if (status == AuthenticationStatus.Failure)
{
return;
}
uri = uri.substring(request.getContextPath().length());
StringTokenizer t = new StringTokenizer(uri, "/");
int tokenCount = t.countTokens();
if (tokenCount < 3)
{
throw new AlfrescoRuntimeException("Servlet URL did not contain all required args: " + uri);
}
// skip the servlet name
t.nextToken();
// retrieve the command from the URL
String commandName = t.nextToken();
// retrieve the binding expression from the URL
String expression = t.nextToken();
// setup the faces context
FacesContext facesContext = FacesHelper.getFacesContext(request, response, getServletContext());
// instantiate the relevant command
AjaxCommand command = null;
if (Command.invoke.toString().equals(commandName))
{
command = new InvokeCommand();
}
else if (Command.get.toString().equals(commandName))
{
command = new GetCommand();
}
// else if (Command.set.toString().equals(commandName))
// {
// command = new SetCommand();
// }
else
{
throw new AlfrescoRuntimeException("Unrecognised command received: " + commandName);
}
// execute the command
command.execute(facesContext, expression, request, response);
}
catch (RuntimeException error)
{
handleError(response, error);
}
}
/**
* Handles any error that occurs during the execution of the servlet
*
* @param response The response
* @param cause The cause of the error
*/
protected void handleError(HttpServletResponse response, RuntimeException cause)
throws ServletException, IOException
{
// if we can send back the 500 error with the error from the top of the
// stack as the error status message.
// NOTE: if we use the built in support for generating error pages for
// 500 errors we can tailor the output for AJAX calls so that the
// body of the response can be used to show the error details.
if (!response.isCommitted())
{
// dump the error if debugging is enabled
if (logger.isDebugEnabled())
{
logger.error(cause);
Throwable theCause = cause.getCause();
if (theCause != null)
{
logger.error("caused by: ", theCause);
}
}
// extract a message from the exception
String msg = cause.getMessage();
if (msg == null)
{
msg = cause.toString();
}
// send the error
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg);
}
else
{
// the response has already been comitted, not much we can do but
// let the error through and let the container deal with it
throw cause;
}
}
}

View File

@@ -0,0 +1,19 @@
package org.alfresco.web.app.servlet.ajax;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Base class for all Ajax based commands
*
* @author gavinc
*/
public abstract class BaseAjaxCommand implements AjaxCommand
{
protected static Log logger = LogFactory.getLog(AjaxServlet.AJAX_LOG_KEY);
public String makeBindingExpression(String expression)
{
return "#{" + expression + "}";
}
}

View File

@@ -0,0 +1,66 @@
package org.alfresco.web.app.servlet.ajax;
import java.io.IOException;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.UserTransaction;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.web.bean.repository.Repository;
/**
* Command that executes the given value binding expression.
* <p>
* This command is intended to be used for calling existing managed
* bean methods. The result of the value binding is added to
* the response as is i.e. by calling toString().
* The content type of the response is always text/html.
*
* @author gavinc
*/
public class GetCommand extends BaseAjaxCommand
{
public void execute(FacesContext facesContext, String expression,
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
// create the JSF binding expression
String bindingExpr = makeBindingExpression(expression);
if (logger.isDebugEnabled())
logger.debug("Retrieving value from value binding: " + bindingExpr);
UserTransaction tx = null;
try
{
// create the value binding
ValueBinding binding = facesContext.getApplication().
createValueBinding(bindingExpr);
if (binding != null)
{
// setup the transaction
tx = Repository.getUserTransaction(facesContext, true);
tx.begin();
// get the value from the value binding
Object value = binding.getValue(facesContext);
response.getWriter().write(value.toString());
// commit
tx.commit();
}
}
catch (Throwable err)
{
// rollback the transaction
try { if (tx != null) {tx.rollback();} } catch (Exception ex) {}
throw new AlfrescoRuntimeException("Failed to retrieve value: " + err.getMessage(), err);
}
}
}

View File

@@ -0,0 +1,97 @@
package org.alfresco.web.app.servlet.ajax;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import javax.faces.FactoryFinder;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.el.MethodBinding;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.UserTransaction;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.web.bean.repository.Repository;
/**
* Command that invokes the method represented by the expression.
* <p>
* The managed bean method called is responsible for writing the response
* by getting hold of the JSF ResponseWriter. Parameters can also be
* retrieved via the JSF ExternalContext object.
* <p>
* In a future release (if required) annotations may be used to state
* what content type to use for the response.
*
* @author gavinc
*/
public class InvokeCommand extends BaseAjaxCommand
{
public void execute(FacesContext facesContext, String expression,
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
// setup the JSF response writer.
// NOTE: it doesn't seem to matter what the content type of the response is (at least with Dojo),
// it determines it's behaviour from the mimetype specified in the AJAX call on the client,
// therefore, for now we will always return a content type of text/html.
// In the future we may use annotations on the method to be called to specify what content
// type should be used for the response.
OutputStream os = response.getOutputStream();
UIViewRoot viewRoot = facesContext.getViewRoot();
RenderKitFactory renderFactory = (RenderKitFactory)FactoryFinder.
getFactory(FactoryFinder.RENDER_KIT_FACTORY);
RenderKit renderKit = renderFactory.getRenderKit(facesContext,
viewRoot.getRenderKitId());
ResponseWriter writer = renderKit.createResponseWriter(
new OutputStreamWriter(os), MimetypeMap.MIMETYPE_HTML, "UTF-8");
facesContext.setResponseWriter(writer);
response.setContentType(writer.getContentType());
// create the JSF binding expression
String bindingExpr = makeBindingExpression(expression);
if (logger.isDebugEnabled())
logger.debug("Invoking method represented by " + bindingExpr);
UserTransaction tx = null;
try
{
// create the method binding from the expression
MethodBinding binding = facesContext.getApplication().createMethodBinding(
bindingExpr, new Class[] {});
if (binding != null)
{
// setup the transaction
tx = Repository.getUserTransaction(facesContext);
tx.begin();
// invoke the method
binding.invoke(facesContext, new Object[] {});
// commit
tx.commit();
}
}
catch (Throwable err)
{
// rollback the transaction
try { if (tx != null) {tx.rollback();} } catch (Exception ex) {}
throw new AlfrescoRuntimeException("Failed to execute method: " + err.getMessage(), err);
}
// force the output back to the client
writer.close();
}
}

View File

@@ -0,0 +1,51 @@
package org.alfresco.web.bean.ajax;
import java.io.IOException;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
/**
* Base class for all Ajax managed beans.
*
* It is not necessary to extend this bean but it provides
* helper methods.
*
* @author gavinc
*/
public abstract class BaseAjaxBean
{
private ResponseWriter writer;
/**
* Writes the given string to the response writer for this bean
*
* @param str The string to send back to the client
*/
public void write(String str)
{
try
{
getWriter().write(str);
}
catch (IOException ioe)
{
// not much we can do here, ignore
}
}
/**
* Returns the ResponseWriter for this bean
*
* @return The JSF ResponseWriter
*/
public ResponseWriter getWriter()
{
if (this.writer == null)
{
this.writer = FacesContext.getCurrentInstance().getResponseWriter();
}
return this.writer;
}
}

View File

@@ -155,6 +155,11 @@
<servlet-class>org.alfresco.web.app.servlet.CommandServlet</servlet-class> <servlet-class>org.alfresco.web.app.servlet.CommandServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>ajaxServlet</servlet-name>
<servlet-class>org.alfresco.web.app.servlet.ajax.AjaxServlet</servlet-class>
</servlet>
<servlet> <servlet>
<servlet-name>axis</servlet-name> <servlet-name>axis</servlet-name>
<servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class> <servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class>
@@ -205,6 +210,11 @@
<url-pattern>/command/*</url-pattern> <url-pattern>/command/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>ajaxServlet</servlet-name>
<url-pattern>/ajax/*</url-pattern>
</servlet-mapping>
<servlet-mapping> <servlet-mapping>
<servlet-name>axis</servlet-name> <servlet-name>axis</servlet-name>
<url-pattern>/api/*</url-pattern> <url-pattern>/api/*</url-pattern>

View File

@@ -0,0 +1,62 @@
//
// Alfresco AJAX support library
// Gavin Cornwell 14-07-2006
//
/**
* Makes an AJAX request to the server using POST. A text/html response
* is presumed.
*
* @param context The name of the application, normally "/alfresco"
* @param command The AJAX command to call, either 'invoke', 'get' or 'set'
* @param expression The managed bean expression
* @param callbackHandler The function to callback when the request completes
*/
function ajaxPostRequest(context, command, expression, callbackHandler)
{
makeAjaxRequest(context, command, expression, null, callbackHandler,
"post", "text/html");
}
/**
* Makes an AJAX request to the server using POST.
*
* @param context The name of the application, normally "/alfresco"
* @param command The AJAX command to call, either 'invoke', 'get' or 'set'
* @param expression The managed bean expression
* @param parameters Set of parameters to pass with the request
* @param callbackHandler The function to callback when the request completes
* @param method The HTTP method to use for the request either "get" or "post"
* @param contentType The mimetype to expect from the server
*/
function makeAjaxRequest(context, command, expression, parameters,
callbackHandler, method, contentType)
{
// use dojo to do the actual work
dojo.io.bind({
method: method,
url: context + "/ajax/" + command + "/" + expression,
content: parameters,
load: callbackHandler,
error: handleErrorDojo,
mimetype: contentType
});
}
/**
* Default handler for errors
*/
function handleErrorDojo(type, errObj)
{
// remove the dojo prefix from the message
var errorStart = "XMLHttpTransport Error: 500 ";
var msg = errObj.message;
if (msg.indexOf(errorStart) != -1)
{
msg = msg.substring(errorStart.length);
}
// TODO: Show a nicer error page, an alert will do for now!
alert(msg);
}

5593
source/web/scripts/dojo.js vendored Normal file

File diff suppressed because it is too large Load Diff