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 scriptStatusMap = new HashMap(); @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 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); } } }