/**
* Copyright © 2013 enioka. All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.enioka.jqm.api;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.SecurityContext;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReversedLinesFileReader;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.enioka.jqm.jdbc.DbConn;
import com.enioka.jqm.jdbc.NoResultException;
import com.enioka.jqm.model.Deliverable;
import com.enioka.jqm.model.Node;
/**
* A minimal API designed to interact well with CLI tools such as schedulers. Some of its methods (file retrieval) are also used by the two
* other sets of APIs.
*
*/
@Path("/simple")
public class ServiceSimple
{
private static Logger log = LoggerFactory.getLogger(ServiceSimple.class);
private @Context HttpServletResponse res;
private @Context SecurityContext security;
private Node n = null;
@Context
private ServletContext context;
// ///////////////////////////////////////////////////////////
// Constructor: determine if running on top of JQM or not
// ///////////////////////////////////////////////////////////
public ServiceSimple(@Context ServletContext context)
{
if (context.getInitParameter("jqmnodeid") != null)
{
// Running on a JQM node, not a standard servlet container.
DbConn cnx = null;
try
{
cnx = Helpers.getDbSession();
try
{
n = Node.select_single(cnx, "node_select_by_id", Integer.parseInt(context.getInitParameter("jqmnodeid")));
}
catch (NoResultException e)
{
throw new RuntimeException("invalid configuration: no node of ID " + context.getInitParameter("jqmnodeid"));
}
}
finally
{
Helpers.closeQuietly(cnx);
}
}
}
// ///////////////////////////////////////////////////////////
// The one and only really simple API
// ///////////////////////////////////////////////////////////
@GET
@Path("status")
@Produces(MediaType.TEXT_PLAIN)
public String getStatus(@QueryParam("id") int id)
{
return JqmClientFactory.getClient().getJob(id).getState().toString();
}
// ///////////////////////////////////////////////////////////
// File retrieval
// ///////////////////////////////////////////////////////////
@GET
@Path("stdout")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public InputStream getLogOut(@QueryParam("id") int id)
{
res.setHeader("Content-Disposition", "attachment; filename=" + id + ".stdout.txt");
return getFile(FilenameUtils.concat("./logs", StringUtils.leftPad("" + id, 10, "0") + ".stdout.log"));
}
@GET
@Path("stderr")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public InputStream getLogErr(@QueryParam("id") int id)
{
res.setHeader("Content-Disposition", "attachment; filename=" + id + ".stderr.txt");
return getFile(FilenameUtils.concat("./logs", StringUtils.leftPad("" + id, 10, "0") + ".stderr.log"));
}
@GET
@Path("file")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public InputStream getDeliverableStream(@QueryParam("id") String randomId)
{
if (n == null)
{
throw new ErrorDto("can only retrieve a file when the web app runs on top of JQM", "", 7, Status.BAD_REQUEST);
}
Deliverable d = null;
DbConn cnx = null;
try
{
cnx = Helpers.getDbSession();
List<Deliverable> dd = Deliverable.select(cnx, "deliverable_select_by_randomid", randomId);
if (dd.isEmpty())
{
throw new NoResultException("requested file does not exist");
}
d = dd.get(0);
}
catch (NoResultException e)
{
throw new ErrorDto("Deliverable does not exist", 8, e, Status.BAD_REQUEST);
}
catch (Exception e)
{
throw new ErrorDto("Could not retrieve Deliverable metadata from database", 9, e, Status.INTERNAL_SERVER_ERROR);
}
finally
{
Helpers.closeQuietly(cnx);
}
String ext = FilenameUtils.getExtension(d.getOriginalFileName());
res.setHeader("Content-Disposition", "attachment; filename=" + d.getFileFamily() + "." + d.getId() + "." + ext);
return getFile(FilenameUtils.concat(n.getDlRepo(), d.getFilePath()));
}
public InputStream getFile(String path)
{
try
{
log.debug("file retrieval service called by user " + getUserName() + " for file " + path);
return new FileInputStream(path);
}
catch (FileNotFoundException e)
{
throw new ErrorDto("Could not find the desired file", 8, e, Status.NO_CONTENT);
}
}
@GET
@Path("enginelog")
@Produces(MediaType.TEXT_PLAIN)
public String getEngineLog(@QueryParam("latest") int latest)
{
if (n == null)
{
throw new ErrorDto("can only retrieve a file when the web app runs on top of JQM", "", 7, Status.BAD_REQUEST);
}
// Failsafe
if (latest > 10000)
{
latest = 10000;
}
ReversedLinesFileReader r = null;
try
{
File f = new File(FilenameUtils.concat("./logs/", "jqm-" + context.getInitParameter("jqmnode") + ".log"));
r = new ReversedLinesFileReader(f);
StringBuilder sb = new StringBuilder(latest);
String buf = r.readLine();
int i = 1;
while (buf != null && i <= latest)
{
sb.append(buf);
sb.append(System.getProperty("line.separator"));
i++;
buf = r.readLine();
}
return sb.toString();
}
catch (Exception e)
{
throw new ErrorDto("Could not return the desired file", 8, e, Status.NO_CONTENT);
}
finally
{
IOUtils.closeQuietly(r);
}
}
private String getUserName()
{
if (security != null && security.getUserPrincipal() != null && security.getUserPrincipal().getName() != null)
{
return security.getUserPrincipal().getName();
}
else
{
return "anonymous";
}
}
// ///////////////////////////////////////////////////////////
// Enqueue - a form service...
// ///////////////////////////////////////////////////////////
@POST
@Path("ji")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.TEXT_PLAIN)
public String enqueue(@FormParam("applicationname") String applicationName, @FormParam("module") String module,
@FormParam("mail") String mail, @FormParam("keyword1") String keyword1, @FormParam("keyword2") String keyword2,
@FormParam("keyword3") String keyword3, @FormParam("parentid") Integer parentId, @FormParam("user") String user,
@FormParam("sessionid") String sessionId, @FormParam("parameterNames") List<String> prmNames,
@FormParam("parameterValues") List<String> prmValues)
{
if (user == null && security != null && security.getUserPrincipal() != null)
{
user = security.getUserPrincipal().getName();
}
JobRequest jd = new JobRequest(applicationName, user);
jd.setModule(module);
jd.setEmail(mail);
jd.setKeyword1(keyword1);
jd.setKeyword2(keyword2);
jd.setKeyword3(keyword3);
jd.setParentID(parentId);
jd.setSessionID(sessionId);
for (int i = 0; i < prmNames.size(); i++)
{
String name = prmNames.get(i);
String value = null;
try
{
value = prmValues.get(i);
}
catch (IndexOutOfBoundsException e)
{
throw new ErrorDto("There should be as many parameter names as parameter values", 6, e, Status.BAD_REQUEST);
}
jd.addParameter(name, value);
log.trace("Adding a parameter: " + name + " - " + value);
}
Integer i = JqmClientFactory.getClient().enqueue(jd);
return i.toString();
}
}