storagehub/src/main/java/org/gcube/data/access/storagehub/services/admin/ScriptManager.java

268 lines
8.9 KiB
Java

package org.gcube.data.access.storagehub.services.admin;
import static org.gcube.data.access.storagehub.Roles.INFRASTRUCTURE_MANAGER_ROLE;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.UUID;
import javax.jcr.Node;
import javax.jcr.Session;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.gcube.common.authorization.control.annotations.AuthorizationControl;
import org.gcube.common.security.AuthorizedTasks;
import org.gcube.common.security.providers.SecretManagerProvider;
import org.gcube.common.storagehub.model.Paths;
import org.gcube.data.access.storagehub.Constants;
import org.gcube.data.access.storagehub.PathUtil;
import org.gcube.data.access.storagehub.StorageHubAppllicationManager;
import org.gcube.data.access.storagehub.accounting.AccountingHandler;
import org.gcube.data.access.storagehub.handlers.items.ItemHandler;
import org.gcube.data.access.storagehub.handlers.items.builders.FileCreationParameters;
import org.gcube.data.access.storagehub.handlers.items.builders.ItemsParameterBuilder;
import org.gcube.data.access.storagehub.scripting.AbstractScript;
import org.gcube.data.access.storagehub.scripting.ScriptUtil;
import org.gcube.data.access.storagehub.services.RepositoryInitializer;
import org.gcube.data.access.storagehub.services.admin.ScriptStatus.Status;
import org.gcube.smartgears.ContextProvider;
import org.gcube.smartgears.context.application.ApplicationContext;
import org.gcube.smartgears.utils.InnerMethodName;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webcohesion.enunciate.metadata.rs.RequestHeader;
import com.webcohesion.enunciate.metadata.rs.RequestHeaders;
import jakarta.inject.Inject;
import jakarta.servlet.ServletContext;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
@Path("admin/script")
@RequestHeaders({
@RequestHeader( name = "Authorization", description = "Bearer token, see https://dev.d4science.org/how-to-access-resources"),
})
public class ScriptManager {
private static Logger log = LoggerFactory.getLogger(ScriptManager.class);
private RepositoryInitializer repository = StorageHubAppllicationManager.getRepository();
@Inject
AccountingHandler accountingHandler;
@Context
ServletContext context;
@Inject
ScriptUtil scriptUtil;
@Inject
ItemHandler itemHandler;
@Inject
PathUtil pathUtil;
private static HashMap<String, ScriptStatus> scriptStatusMap = new HashMap<String, ScriptStatus>();
@POST
@Path("execute")
@AuthorizationControl(allowedRoles = {INFRASTRUCTURE_MANAGER_ROLE})
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public ScriptStatus run( @FormDataParam("name") String name,
@FormDataParam("asynch") @DefaultValue("false") Boolean asynch,
@FormDataParam("writeResult") @DefaultValue("false") Boolean writeResult ,
@FormDataParam("destinationFolderId") String destinationFolderId,
@FormDataParam("file") InputStream stream,
@FormDataParam("file") FormDataContentDisposition fileDetail) {
try {
InnerMethodName.set("executeScript");
ScriptClassLoader scriptClassLoader = new ScriptClassLoader(Thread.currentThread().getContextClassLoader());
Class<?> scriptClass = uploadClass(stream, scriptClassLoader, fileDetail.getFileName().replace(".class", ""));
return internalRun(scriptClass, name, destinationFolderId, asynch, writeResult);
}catch(Throwable e) {
log.error("error executing script {}", name,e);
throw new WebApplicationException("error loading class",e);
}
}
@GET
@Path("{id}/status")
@AuthorizationControl(allowedRoles = {INFRASTRUCTURE_MANAGER_ROLE})
@Produces(MediaType.APPLICATION_JSON)
public ScriptStatus getStatus(@PathParam("id") String runningId) {
InnerMethodName.set("getScriptStatus");
if (!scriptStatusMap.containsKey(runningId)) {
log.error("script with id {} not found",runningId);
throw new WebApplicationException("id "+runningId+" not found", 404);
}
ScriptStatus status = scriptStatusMap.get(runningId);
if (status.getStatus()!= Status.Running)
scriptStatusMap.remove(runningId);
return status;
}
private Class<?> uploadClass(InputStream stream, ScriptClassLoader classLoader, String name) throws Throwable {
try(ByteArrayOutputStream buffer = new ByteArrayOutputStream()){
int nRead;
byte[] data = new byte[1024];
while ((nRead = stream.read(data, 0, data.length)) != -1)
buffer.write(data, 0, nRead);
buffer.flush();
byte[] byteArray = buffer.toByteArray();
return classLoader.findClass(name, byteArray);
}
}
private ScriptStatus internalRun(Class<?> clazz, String name, String destinationFolderId, boolean asynch, boolean writeResult) throws Throwable {
String login = SecretManagerProvider.get().getOwner().getId();
log.info("script {} called by {}", clazz.getSimpleName(), login);
try {
String resultPath= null;
Node parentNode = null;
if (writeResult) {
JackrabbitSession ses = null;
try {
ses = (JackrabbitSession) repository.getRepository().login(Constants.JCR_CREDENTIALS);
String parentId = destinationFolderId!=null ? destinationFolderId : ses.getNode(pathUtil.getWorkspacePath(login).toPath()).getIdentifier();
parentNode = ses.getNodeByIdentifier(parentId);
resultPath = Paths.append(Paths.getPath(parentNode.getPath()), name).toPath();
}finally {
if (ses!=null)
ses.logout();
}
}
if (AbstractScript.class.isAssignableFrom(clazz)) {
AbstractScript scriptInstance = (AbstractScript) clazz.getDeclaredConstructor().newInstance();
ApplicationContext appContext = ContextProvider.get();
String serverHost = appContext.container().configuration().hostname();
String runningId = UUID.randomUUID().toString();
ScriptStatus status = new ScriptStatus(runningId, resultPath, serverHost);
RealRun realRun = new RealRun(scriptInstance, login, parentNode, name, writeResult, status);
if (asynch) {
scriptStatusMap.put(runningId, status);
new Thread(AuthorizedTasks.bind(realRun)).start();
return status;
}else {
realRun.run();
return status;
}
} else throw new Exception("class "+clazz.getSimpleName()+" not implements AbstractScript");
}catch (Throwable e) {
throw e;
}
}
class RealRun implements Runnable{
AbstractScript instance;
String login;
Node parentNode;
String name;
boolean writeResult = true;
ScriptStatus status;
public RealRun(AbstractScript instance, String login, Node parentNode, String name, boolean writeResult, ScriptStatus status) {
super();
this.instance = instance;
this.login = login;
this.parentNode = parentNode;
this.name = name;
this.writeResult = writeResult;
this.status = status;
}
@Override
public void run() {
String result ="";
try {
JackrabbitSession executeSession = null;
try {
executeSession = (JackrabbitSession) repository.getRepository().login(Constants.JCR_CREDENTIALS);
result = instance.run(executeSession, null, scriptUtil);
status.setSuccess();
}catch(Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
t.printStackTrace(pw);
status.setFailed(sw.toString());
result+= "\n"+sw.toString();
log.warn("error executing script {}",instance.getClass().getSimpleName(), t);
}finally {
if (executeSession !=null && executeSession.isLive())
executeSession.logout();
}
if (this.writeResult) {
Session writeSession = null;
try( InputStream stream = new ByteArrayInputStream(result.getBytes())){
writeSession = repository.getRepository().login(Constants.JCR_CREDENTIALS);
ItemsParameterBuilder<FileCreationParameters> builder = FileCreationParameters.builder().name(name).description("result of script execution "+name)
.stream(stream).on(parentNode.getIdentifier()).with(writeSession).author(login);
itemHandler.create(builder.build());
} catch (Throwable e) {
log.error("error saving script result {} in the Workspace",name, e);
} finally {
if (writeSession!=null)
writeSession.logout();
}
}
}catch (Exception e) {
log.error("unexpected error executing script {}",instance.getClass().getSimpleName(),e);
}
}
}
class ScriptClassLoader extends ClassLoader{
public ScriptClassLoader(ClassLoader parent) {
super(parent);
}
public Class<?> findClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
}