package org.juxtasoftware.resource;
import java.io.Reader;
import java.util.Map;
import org.juxtasoftware.Constants;
import org.juxtasoftware.JuxtaWS;
import org.juxtasoftware.JuxtaWsApplication;
import org.juxtasoftware.dao.WorkspaceDao;
import org.juxtasoftware.model.Workspace;
import org.juxtasoftware.model.WorkspaceMember;
import org.restlet.Request;
import org.restlet.data.CharacterSet;
import org.restlet.data.Encoding;
import org.restlet.data.Language;
import org.restlet.data.MediaType;
import org.restlet.data.Preference;
import org.restlet.data.Status;
import org.restlet.engine.application.EncodeRepresentation;
import org.restlet.ext.freemarker.TemplateRepresentation;
import org.restlet.representation.ReaderRepresentation;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.ClientResource;
import org.restlet.resource.ResourceException;
import org.restlet.resource.ServerResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import freemarker.template.Configuration;
/**
* Base class for all JuxtaWS resources. It defines the DB transaction
* boundary to be one request. To do som it must override the default
* handle method and annotate it as transactional. It is the only external
* call that is mave via the cglib proxy during the request so here is where
* the transaction must be made. Also must
* override the default doCatch behavior and rethrow the exception in order
* for the transaction to be rolled back.
*
* @author loufoster
*
*/
public class BaseResource extends ServerResource {
private static final String FTL_ROOT = "clap://class/templates/ftl/";
private static final Configuration FTL_CONFIG;
protected static final Logger LOG = LoggerFactory.getLogger( Constants.WS_LOGGER_NAME );
private boolean zipSupported = false;
protected boolean embedded = false;
protected Workspace workspace;
@Autowired protected WorkspaceDao workspaceDao;
static {
FTL_CONFIG = new Configuration();
FTL_CONFIG.setClassForTemplateLoading(JuxtaWS.class, "/templates/ftl");
FTL_CONFIG.setNumberFormat("computer");
}
@Override
protected void doInit() throws ResourceException {
if (getQuery().getValuesMap().containsKey("embed") ) {
this.embedded = true;
}
if ( getRequestAttributes().containsKey("workspace") ) {
String name = (String) getRequestAttributes().get("workspace");
this.workspace = this.workspaceDao.find( name );
if ( this.workspace == null ) {
setStatus(Status.CLIENT_ERROR_NOT_FOUND, "Workspace not found");
return;
}
} else {
this.workspace = this.workspaceDao.getPublic();
}
JuxtaWsApplication app = (JuxtaWsApplication) getApplication();
if ( app.authenticate(getRequest(), getResponse()) == false ) {
setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
return;
}
// See if this client can handle zipped responses
Request r = getRequest();
for ( Preference<Encoding> enc : r.getClientInfo().getAcceptedEncodings() ) {
if ( enc.getMetadata().equals( Encoding.GZIP ) ) {
this.zipSupported = true;
break;
}
}
super.doInit();
}
protected Long getIdFromAttributes( final String name ) {
Long val = null;
if ( getRequestAttributes().containsKey(name) ) {
String strVal = (String)getRequestAttributes().get(name);
try {
val = Long.parseLong(strVal);
} catch (NumberFormatException e) {
setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Invalid identifier specified");
}
} else {
setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Missing required "+name+" parameter");
}
return val;
}
protected boolean isZipSupported() {
return this.zipSupported;
}
/**
* Ensure that any model data used by this resource exists
* and is accessible within the specified workspace
*
* @param model
*/
protected boolean validateModel( final WorkspaceMember model ) {
if ( model == null ) {
LOG.error("Resource is null");
setStatus(Status.CLIENT_ERROR_NOT_FOUND,
"Invalid resource identifier specified");
return false;
} else if ( model.isMemberOf( this.workspace) == false ) {
LOG.error("Resource "+model.getId()+" is not a member of workspace "+this.workspace.getName());
setStatus(Status.CLIENT_ERROR_NOT_FOUND,
"Resource "+model.getId()+" does not exist in workspace " +
this.workspace.getName());
return false;
}
return true;
}
/**
* Override and append transactional annotation. This defines DB
* trasaction boundary.
*/
@Override
@Transactional
public Representation handle() {
return super.handle();
}
/**
* Must override here re-throw exceptions. This allows transactions
* to be rolled back on errors
*/
@Override
protected void doCatch(Throwable throwable) {
super.doCatch(throwable);
LOG.error("Request Failed", throwable);
throw new RuntimeException(throwable);
}
/**
* Get the trimmed and lowercased workspace name to be used in URLs
* @return
*/
public String getWorkspace() {
return this.workspace.getName().toLowerCase().trim();
}
/**
* Convert the data in msg to a plain text, UTF-8 representation
* @param msg string message to convert
* @return
*/
public Representation toTextRepresentation( final String msg ) {
return new StringRepresentation(msg,
MediaType.TEXT_PLAIN,
Language.DEFAULT,
CharacterSet.UTF_8);
}
/**
* Convert the Reader into an HTML representation, zipping if possible
* @param reader Reader containing the html data
* @return
*/
public Representation toHtmlRepresentation( final Reader reader) {
Representation r = new ReaderRepresentation(reader, MediaType.TEXT_HTML);
if ( this.zipSupported ) {
return new EncodeRepresentation(Encoding.GZIP, r);
}
return r;
}
/**
* Convert the Reader into an XML representation, zipping if possible
* @param reader Reader containing the html data
* @return
*/
public Representation toXmlRepresentation( final String content ) {
Representation r = new StringRepresentation(content,
MediaType.TEXT_PLAIN,
Language.DEFAULT,
CharacterSet.UTF_8);
if ( this.zipSupported ) {
return new EncodeRepresentation(Encoding.GZIP, r);
}
return r;
}
/**
* Convert the JSON data in jsonString to aa application/json,
* UTF-8 representation
* @param jsonString JSON data in string format
* @return
*/
public Representation toJsonRepresentation( final String jsonString ) {
Representation r = new StringRepresentation(jsonString,
MediaType.APPLICATION_JSON,
Language.DEFAULT,
CharacterSet.UTF_8);
if ( this.zipSupported ) {
return new EncodeRepresentation(Encoding.GZIP, r);
}
return r;
}
/**
* Using the freemarker template <code>ftlName</code> and the supporting data
* found in <code>map</code>, generate a UTF-8 encoded HTML represenation.
* using a standard juxta layout as the base template.
*
* @param ftlName name of the content template
* @param map map of name-value pairs that will be used to fill in the template
* @return
*/
public Representation toHtmlRepresentation( final String ftlName, Map<String,Object> map) {
return toHtmlRepresentation(ftlName, map, true);
}
/**
* Using the freemarker template <code>ftlName</code> and the supporting data
* found in <code>map</code>, generate a UTF-8 encoded HTML represenation.
* If the <code>useLayout</code> flag is true, this representation will be
* embedded as content within the base juxta layout. If false, it will
* stand on its own.
*
* @param ftlName name of the content template
* @param map map of name-value pairs that will be used to fill in the template
* @param useLayout Flag to indicate if this template will be embeddded within
* the standard juxta layout.
* @return
*/
public Representation toHtmlRepresentation( final String ftlName, Map<String,Object> map, boolean useLayout ) {
return toHtmlRepresentation(ftlName, map, useLayout, true);
}
public Representation toHtmlRepresentation( final String ftlName, Map<String,Object> map, boolean useLayout, boolean gzip ) {
Representation ftlRepresentation = null;
if ( useLayout == false ) {
ftlRepresentation = getTemplate(ftlName);
} else {
ftlRepresentation = getTemplate("layout.ftl");
map.put("content", "/"+ftlName);
map.put("embedded", this.embedded);
if ( map.containsKey("workspace") == false) {
map.put("workspace", this.workspace.getName().toLowerCase().trim());
map.put("workspaceId", this.workspace.getId());
map.put("workspaceCount", this.workspaceDao.getWorkspaceCount());
map.put("workspaces", this.workspaceDao.list());
}
}
map.put("baseUrl", getRequest().getHostRef().toString()+"/juxta");
Representation r = new TemplateRepresentation(ftlRepresentation, FTL_CONFIG, map, MediaType.TEXT_HTML);
if ( this.zipSupported && gzip ) {
return new EncodeRepresentation(Encoding.GZIP, r);
}
return r;
}
private final Representation getTemplate( String ftlName ) {
return new ClientResource(FTL_ROOT+ftlName).get();
}
}