/*
* Copyright (C) 2005-2012 BetaCONCEPT Limited
*
* This file is part of Astroboa.
*
* Astroboa is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Astroboa 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Astroboa. If not, see <http://www.gnu.org/licenses/>.
*/
package org.betaconceptframework.astroboa.resourceapi.resource;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.betaconceptframework.astroboa.api.model.definition.CmsDefinition;
import org.betaconceptframework.astroboa.api.model.io.ResourceRepresentationType;
import org.betaconceptframework.astroboa.api.service.DefinitionService;
import org.betaconceptframework.astroboa.client.AstroboaClient;
import org.betaconceptframework.astroboa.resourceapi.utility.ContentApiUtils;
import org.betaconceptframework.astroboa.resourceapi.utility.XmlSchemaGenerator;
import org.betaconceptframework.astroboa.serializer.ModelSerializer;
import org.betaconceptframework.astroboa.util.CmsConstants;
import org.betaconceptframework.astroboa.util.ResourceApiURLUtils;
import org.betaconceptframework.astroboa.util.UrlProperties;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Gregory Chomatas (gchomatas@betaconcept.com)
* @author Savvas Triantafyllou (striantafyllou@betaconcept.com)
*
*/
public class DefinitionResource extends AstroboaResource{
private final Logger logger = LoggerFactory.getLogger(getClass());
public DefinitionResource(AstroboaClient astroboaClient) {
super(astroboaClient);
}
// The methods which produce JSON or XML allow "callback" as one extra query parameter
// in order to support XML with Padding or JSON with Padding (JSONP) and overcome the SPO restriction of browsers
// This means that if a "callback" query parameter is provided then the XML or JSON result will be wrapped inside a "callback" script
@GET
@Produces("*/*")
@Path("/{propertyPath: " + CmsConstants.PROPERTY_PATH_REG_EXP_FOR_RESTEASY + "}")
public Response getDefinition(
@PathParam("propertyPath") String propertyPath,
@QueryParam("output") String output,
@QueryParam("callback") String callback,
@QueryParam("prettyPrint") String prettyPrint){
boolean prettyPrintEnabled = ContentApiUtils.isPrettyPrintEnabled(prettyPrint);
if (output == null){
if (propertyPath!=null && propertyPath.endsWith(".xsd")){
//user has provided a file name and not a property path
return getDefinitionInternal(propertyPath, Output.XSD, callback, prettyPrintEnabled);
}
return getDefinitionInternal(propertyPath, Output.XML, callback, prettyPrintEnabled);
}
Output outputEnum = Output.valueOf(output.toUpperCase());
if (outputEnum != Output.XSD && asrtoboaBuiltInModelIsRequested(propertyPath)){
//User has requested astroboa-model or astroboa-api built in schemata
//but at the same time, the "output" request parameter was not XSD
//In this case an HTTP NOT FOUND error should be returned.
throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND);
}
return getDefinitionInternal(propertyPath, outputEnum, callback,prettyPrintEnabled);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/{propertyPath: " + CmsConstants.PROPERTY_PATH_REG_EXP_FOR_RESTEASY + "}")
public Response getDefinitionAsJson(
@PathParam("propertyPath") String propertyPath,
@QueryParam("output") String output,
@QueryParam("callback") String callback,
@QueryParam("prettyPrint") String prettyPrint) {
boolean prettyPrintEnabled = ContentApiUtils.isPrettyPrintEnabled(prettyPrint);
// URL-based negotiation overrides any Accept header sent by the client
//i.e. if the url specifies the desired response type in the "output" parameter this method
// will return the media type specified in "output" request parameter.
Output outputEnum = Output.JSON;
if (StringUtils.isNotBlank(output)) {
outputEnum = Output.valueOf(output.toUpperCase());
if (outputEnum != Output.XSD && asrtoboaBuiltInModelIsRequested(propertyPath)){
//User has requested astroboa-model or astroboa-api built in schemata
//but at the same time, the "output" request parameter was not XSD
//In this case an HTTP NOT FOUND error should be returned.
throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND);
}
}
return getDefinitionInternal(propertyPath, outputEnum, callback, prettyPrintEnabled);
}
private boolean asrtoboaBuiltInModelIsRequested(String propertyPath) {
return StringUtils.equals(propertyPath, CmsConstants.ASTROBOA_MODEL_SCHEMA_FILENAME_WITH_VERSION) ||
StringUtils.equals(propertyPath, CmsConstants.ASTROBOA_API_SCHEMA_FILENAME_WITH_VERSION) ;
}
@GET
@Produces(MediaType.APPLICATION_XML)
@Path("/{propertyPath: " + CmsConstants.PROPERTY_PATH_REG_EXP_FOR_RESTEASY + "}")
public Response getDefinitionAsXml(
@PathParam("propertyPath") String propertyPath,
@QueryParam("output") String output,
@QueryParam("callback") String callback,
@QueryParam("prettyPrint") String prettyPrint,
@Context UriInfo uriInfo) {
boolean prettyPrintEnabled = ContentApiUtils.isPrettyPrintEnabled(prettyPrint);
// URL-based negotiation overrides any Accept header sent by the client
//i.e. if the url specifies the desired response type in the "output" parameter this method
// will return the media type specified in "output" request parameter.
Output outputEnum = Output.XML;
if (StringUtils.isNotBlank(output)) {
outputEnum = Output.valueOf(output.toUpperCase());
if (outputEnum != Output.XSD && asrtoboaBuiltInModelIsRequested(propertyPath)){
//User has requested astroboa-model or astroboa-api built in schemata
//but at the same time, the "output" request parameter was not XSD
//In this case an HTTP NOT FOUND error should be returned.
throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND);
}
}
else{
//User did not provide value for the "output" parameter therefore
//the output at this point is XML. However there is one case where the
//user may have provide the suffix ".xsd" in the URL, requesting this
//way the output to be an XML Schema. This case can only be traced by
//examining the path of the request and whether it ends in ".xsd" or not
String path = uriInfo.getPath();
if (StringUtils.endsWith(path, ".xsd")){
//User has provided the suffix ".xsd".
//Therefore an XML Schema sholud be returned
//We have to attach the suffix to the propertyPath variable
//since the user may have specified a filename instead of
//a property path.
outputEnum = Output.XSD;
if (propertyPath != null){
propertyPath += ".xsd";
}
}
}
return getDefinitionInternal(propertyPath, outputEnum, callback, prettyPrintEnabled);
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response postDefinition(String source) {
try {
ObjectMapper mapper = new ObjectMapper();
Map<String,Object> objectType = mapper.readValue(source, Map.class);
XmlSchemaGenerator xmlSchemaTemplate = new XmlSchemaGenerator();
String schema = xmlSchemaTemplate.generateXmlSchema(objectType);
//Save schema to schemata folder
String objectTypeName = (String)objectType.get("name");
File schemaFile = new File(astroboaClient.getRepositoryService().getCurrentConnectedRepository().getRepositoryHomeDirectory()+File.separator+"astroboa_schemata", objectTypeName+".xsd");
FileUtils.writeStringToFile(schemaFile,schema);
//Call definition service to force Astroboa to load newly created definition
CmsDefinition newlyCreatedDefinition = astroboaClient.getDefinitionService().getCmsDefinition(objectTypeName, ResourceRepresentationType.DEFINITION_INSTANCE, false);
if (newlyCreatedDefinition == null){
throw new Exception("Object type "+objectTypeName+ " was not created.");
}
//Generate Response
ResponseBuilder responseBuilder = Response.status(Status.CREATED);
UrlProperties urlProperties = new UrlProperties();
urlProperties.setResourceRepresentationType(null);
urlProperties.setFriendly(false);
urlProperties.setRelative(false);
urlProperties.setIdentifier(newlyCreatedDefinition.getName());
responseBuilder.location(URI.create(ResourceApiURLUtils.generateUrlForEntity(newlyCreatedDefinition, urlProperties)));
return Response.ok().build();
} catch (Exception e) {
e.printStackTrace();
return ContentApiUtils.createResponseForException(Status.BAD_REQUEST, e, true, "Definition source "+source);
}
}
private Response getDefinitionInternal(String propertyPath, Output output, String callback, boolean prettyPrint){
try {
if (StringUtils.isBlank(propertyPath)){
throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND);
}
DefinitionService definitionService = astroboaClient.getDefinitionService();
StringBuilder definitionAsXMLOrJSONorXSD = new StringBuilder();
switch (output) {
case XML:{
String xml = definitionService.getCmsDefinition(propertyPath, ResourceRepresentationType.XML, prettyPrint);
if (StringUtils.isBlank(xml)){
throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND);
}
if (StringUtils.isBlank(callback)) {
definitionAsXMLOrJSONorXSD.append(xml);
}
else {
ContentApiUtils.generateXMLP(definitionAsXMLOrJSONorXSD, xml, callback);
}
break;
}
case JSON:
String json = definitionService.getCmsDefinition(propertyPath, ResourceRepresentationType.JSON, prettyPrint);
if (StringUtils.isBlank(json)){
throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND);
}
if (StringUtils.isBlank(callback)) {
definitionAsXMLOrJSONorXSD.append(json);
}
else {
ContentApiUtils.generateJSONP(definitionAsXMLOrJSONorXSD, json, callback);
}
break;
case XSD:
String xsd = definitionService.getCmsDefinition(propertyPath, ResourceRepresentationType.XSD, prettyPrint);
if (StringUtils.isBlank(xsd)){
throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND);
}
definitionAsXMLOrJSONorXSD.append(xsd);
if (StringUtils.isNotBlank(callback)) {
logger.warn("Callback {} is ignored in {} output", callback, output);
}
break;
}
return ContentApiUtils.createResponse(definitionAsXMLOrJSONorXSD, output, callback, null);
}
catch(WebApplicationException e){
throw e;
}
catch(Exception e){
logger.error("Definition For property path: " + propertyPath, e);
throw new WebApplicationException(HttpURLConnection.HTTP_BAD_REQUEST);
}
}
@GET
@Produces(MediaType.APPLICATION_XML)
public Response getModelAsXml(
@QueryParam("output") String output,
@QueryParam("callback") String callback,
@QueryParam("prettyPrint") String prettyPrint){
boolean prettyPrintEnabled = ContentApiUtils.isPrettyPrintEnabled(prettyPrint);
Output outputEnum = ContentApiUtils.getOutputType(output, Output.XML);
return getModelInternal(callback, prettyPrintEnabled, outputEnum);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getModelAsJson(
@QueryParam("output") String output,
@QueryParam("callback") String callback,
@QueryParam("prettyPrint") String prettyPrint){
boolean prettyPrintEnabled = ContentApiUtils.isPrettyPrintEnabled(prettyPrint);
Output outputEnum = ContentApiUtils.getOutputType(output, Output.JSON);
return getModelInternal(callback, prettyPrintEnabled, outputEnum);
}
@GET
@Produces("*/*")
public Response getModel(
@QueryParam("output") String output,
@QueryParam("callback") String callback,
@QueryParam("prettyPrint") String prettyPrint){
boolean prettyPrintEnabled = ContentApiUtils.isPrettyPrintEnabled(prettyPrint);
Output outputEnum = ContentApiUtils.getOutputType(output, Output.XML);
return getModelInternal(callback, prettyPrintEnabled, outputEnum);
}
private Response getModelInternal(String callback,
boolean prettyPrintEnabled, Output outputEnum) {
try{
if (outputEnum != Output.XML && outputEnum != Output.JSON){
throw new Exception("All definitions are exported only in XML and JSON format. "+outputEnum + " is not supported");
}
String allDefinitions = new ModelSerializer(prettyPrintEnabled, outputEnum == Output.JSON, astroboaClient.getDefinitionService(), astroboaClient.getConnectedRepositoryId()).serialize();
if (StringUtils.isBlank(allDefinitions)){
throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND);
}
StringBuilder definitionAsXMLOrJSONorXSD = new StringBuilder();
if (StringUtils.isBlank(callback)) {
definitionAsXMLOrJSONorXSD.append(allDefinitions);
}
else {
switch (outputEnum) {
case XML:{
ContentApiUtils.generateXMLP(definitionAsXMLOrJSONorXSD, allDefinitions, callback);
break;
}
case JSON:
ContentApiUtils.generateJSONP(definitionAsXMLOrJSONorXSD, allDefinitions, callback);
break;
}
}
return ContentApiUtils.createResponse(definitionAsXMLOrJSONorXSD, outputEnum, callback, null);
}
catch(Exception e){
logger.error("When exporting all Definitions", e);
throw new WebApplicationException(HttpURLConnection.HTTP_BAD_REQUEST);
}
}
}