diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2023-01-22 00:30:51 +0000 |
|---|---|---|
| committer | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2023-02-01 22:01:19 +0000 |
| commit | 6927c51885bb3073b310150c4f40c64eea44a919 (patch) | |
| tree | a3da33f2e1653be23008dba15e9b3ed7044ae921 /opendc-web/opendc-web-server/src/main/java | |
| parent | e64487cb57ca75d17fe5a8a664c1e8247c7b5168 (diff) | |
refactor(web/server): Convert resources to Java
This change converts the resource classes of the OpenDC web server to
use Java, The Quarkus integration for Java is more mature and the
programming quality of experience is not that much worse.
Diffstat (limited to 'opendc-web/opendc-web-server/src/main/java')
14 files changed, 924 insertions, 23 deletions
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java new file mode 100644 index 00000000..0fd58182 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.rest; + +import java.util.List; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +/** + * A resource representing the available schedulers that can be used during experiments. + */ +@Produces("application/json") +@Path("/schedulers") +public final class SchedulerResource { + /** + * Obtain all available schedulers. + */ + @GET + public List<String> getAll() { + return List.of( + "mem", + "mem-inv", + "core-mem", + "core-mem-inv", + "active-servers", + "active-servers-inv", + "provisioned-cores", + "provisioned-cores-inv", + "random"); + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/TraceService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/TraceResource.java index 94b8340b..2b1efb02 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/TraceService.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/TraceResource.java @@ -20,30 +20,45 @@ * SOFTWARE. */ -package org.opendc.web.server.service; +package org.opendc.web.server.rest; import java.util.List; -import javax.enterprise.context.ApplicationScoped; +import java.util.stream.Stream; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; import org.opendc.web.server.model.Trace; /** - * Service for managing {@link Trace}s. + * A resource representing the workload traces available in the OpenDC instance. */ -@ApplicationScoped -public final class TraceService { +@Produces("application/json") +@Path("/traces") +public final class TraceResource { /** - * Obtain all available workload traces. + * Obtain all available traces. */ - public List<org.opendc.web.proto.Trace> findAll() { - List<Trace> entities = Trace.listAll(); - return entities.stream().map(TraceService::toUserDto).toList(); + @GET + public List<org.opendc.web.proto.Trace> getAll() { + Stream<Trace> entities = Trace.streamAll(); + return entities.map(TraceResource::toUserDto).toList(); } /** - * Obtain a workload trace by identifier. + * Obtain trace information by identifier. */ - public org.opendc.web.proto.Trace findById(String id) { - return toUserDto(Trace.findById(id)); + @GET + @Path("{id}") + public org.opendc.web.proto.Trace get(@PathParam("id") String id) { + Trace trace = Trace.findById(id); + + if (trace == null) { + throw new WebApplicationException("Trace not found", 404); + } + + return toUserDto(trace); } /** diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java new file mode 100644 index 00000000..3b6be42e --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.rest.error; + +import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import org.opendc.web.proto.ProtocolError; + +/** + * An [ExceptionMapper] for [MissingKotlinParameterException] thrown by Jackson. + */ +@Provider +public final class MissingKotlinParameterExceptionMapper implements ExceptionMapper<MissingKotlinParameterException> { + @Override + public Response toResponse(MissingKotlinParameterException exception) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ProtocolError( + Response.Status.BAD_REQUEST.getStatusCode(), + "Field " + exception.getParameter().getName() + " is missing from body.")) + .type(MediaType.APPLICATION_JSON) + .build(); + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java new file mode 100644 index 00000000..ad1bb05e --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.rest.error; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import org.opendc.web.proto.ProtocolError; + +/** + * Helper class to transform a {@link WebApplicationException} into an JSON error response. + */ +@Provider +public final class WebApplicationExceptionMapper implements ExceptionMapper<WebApplicationException> { + @Override + public Response toResponse(WebApplicationException exception) { + int code = exception.getResponse().getStatus(); + + String message = exception.getMessage(); + if (message == null) { + message = "Unknown error"; + } + + return Response.status(code) + .entity(new ProtocolError(code, message)) + .type(MediaType.APPLICATION_JSON) + .build(); + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java new file mode 100644 index 00000000..134c6814 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.rest.runner; + +import java.util.List; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.validation.Valid; +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.WebApplicationException; +import org.opendc.web.server.service.JobService; + +/** + * A resource representing the available simulation jobs. + */ +@Produces("application/json") +@Path("/jobs") +@RolesAllowed("runner") +public final class JobResource { + /** + * The {@link JobService} responsible for managing the jobs. + */ + private final JobService jobService; + + /** + * Construct a {@link JobResource} instance. + * + * @param jobService The {@link JobService} responsible for managing the jobs. + */ + public JobResource(JobService jobService) { + this.jobService = jobService; + } + + /** + * Obtain all pending simulation jobs. + */ + @GET + public List<org.opendc.web.proto.runner.Job> queryPending() { + return jobService.listPending(); + } + + /** + * Get a job by identifier. + */ + @GET + @Path("{job}") + public org.opendc.web.proto.runner.Job get(@PathParam("job") long id) { + org.opendc.web.proto.runner.Job job = jobService.findById(id); + if (job == null) { + throw new WebApplicationException("Job not found", 404); + } + + return job; + } + + /** + * Atomically update the state of a job. + */ + @POST + @Path("{job}") + @Consumes("application/json") + @Transactional + public org.opendc.web.proto.runner.Job update( + @PathParam("job") long id, @Valid org.opendc.web.proto.runner.Job.Update update) { + try { + var job = jobService.updateState(id, update.getState(), update.getRuntime(), update.getResults()); + if (job == null) { + throw new WebApplicationException("Job not found", 404); + } + + return job; + } catch (IllegalArgumentException e) { + throw new WebApplicationException(e, 400); + } catch (IllegalStateException e) { + throw new WebApplicationException(e, 409); + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java new file mode 100644 index 00000000..e8e05f97 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.rest.user; + +import io.quarkus.security.identity.SecurityIdentity; +import java.util.List; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.validation.Valid; +import javax.ws.rs.DELETE; +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.WebApplicationException; +import org.opendc.web.server.service.PortfolioService; + +/** + * A resource representing the portfolios of a project. + */ +@Produces("application/json") +@Path("/projects/{project}/portfolios") +@RolesAllowed("openid") +public final class PortfolioResource { + /** + * The service for managing the user portfolios. + */ + private final PortfolioService portfolioService; + + /** + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link PortfolioResource}. + * + * @param portfolioService The {@link PortfolioService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. + */ + public PortfolioResource(PortfolioService portfolioService, SecurityIdentity identity) { + this.portfolioService = portfolioService; + this.identity = identity; + } + + /** + * Get all portfolios that belong to the specified project. + */ + @GET + public List<org.opendc.web.proto.user.Portfolio> getAll(@PathParam("project") long projectId) { + return portfolioService.findByUser(identity.getPrincipal().getName(), projectId); + } + + /** + * Create a portfolio for this project. + */ + @POST + @Transactional + public org.opendc.web.proto.user.Portfolio create( + @PathParam("project") long projectId, @Valid org.opendc.web.proto.user.Portfolio.Create request) { + var portfolio = portfolioService.create(identity.getPrincipal().getName(), projectId, request); + if (portfolio == null) { + throw new WebApplicationException("Project not found", 404); + } + + return portfolio; + } + + /** + * Obtain a portfolio by its identifier. + */ + @GET + @Path("{portfolio}") + public org.opendc.web.proto.user.Portfolio get( + @PathParam("project") long projectId, @PathParam("portfolio") int number) { + var portfolio = portfolioService.findByUser(identity.getPrincipal().getName(), projectId, number); + if (portfolio == null) { + throw new WebApplicationException("Portfolio not found", 404); + } + + return portfolio; + } + + /** + * Delete a portfolio. + */ + @DELETE + @Path("{portfolio}") + @Transactional + public org.opendc.web.proto.user.Portfolio delete( + @PathParam("project") long projectId, @PathParam("portfolio") int number) { + var portfolio = portfolioService.delete(identity.getPrincipal().getName(), projectId, number); + if (portfolio == null) { + throw new WebApplicationException("Portfolio not found", 404); + } + + return portfolio; + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java new file mode 100644 index 00000000..a6db7c54 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.rest.user; + +import io.quarkus.security.identity.SecurityIdentity; +import java.util.List; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.validation.Valid; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.WebApplicationException; +import org.opendc.web.proto.user.Scenario; +import org.opendc.web.server.service.ScenarioService; + +/** + * A resource representing the scenarios of a portfolio. + */ +@Path("/projects/{project}/portfolios/{portfolio}/scenarios") +@RolesAllowed("openid") +public final class PortfolioScenarioResource { + /** + * The service for managing the user scenarios. + */ + private final ScenarioService scenarioService; + + /** + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link PortfolioScenarioResource}. + * + * @param scenarioService The {@link ScenarioService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. + */ + public PortfolioScenarioResource(ScenarioService scenarioService, SecurityIdentity identity) { + this.scenarioService = scenarioService; + this.identity = identity; + } + + /** + * Get all scenarios that belong to the specified portfolio. + */ + @GET + public List<Scenario> get(@PathParam("project") long projectId, @PathParam("portfolio") int portfolioNumber) { + return scenarioService.findAll(identity.getPrincipal().getName(), projectId, portfolioNumber); + } + + /** + * Create a scenario for this portfolio. + */ + @POST + @Transactional + public org.opendc.web.proto.user.Scenario create( + @PathParam("project") long projectId, + @PathParam("portfolio") int portfolioNumber, + @Valid org.opendc.web.proto.user.Scenario.Create request) { + var scenario = scenarioService.create(identity.getPrincipal().getName(), projectId, portfolioNumber, request); + if (scenario == null) { + throw new WebApplicationException("Portfolio not found", 404); + } + + return scenario; + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java new file mode 100644 index 00000000..b0b8eb4e --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.rest.user; + +import io.quarkus.security.identity.SecurityIdentity; +import java.util.List; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.validation.Valid; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +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.WebApplicationException; +import org.opendc.web.server.service.ProjectService; + +/** + * A resource representing the created projects. + */ +@Produces("application/json") +@Path("/projects") +@RolesAllowed("openid") +public final class ProjectResource { + /** + * The service for managing the user projects. + */ + private final ProjectService projectService; + + /** + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link ProjectResource}. + * + * @param projectService The {@link ProjectService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. + */ + public ProjectResource(ProjectService projectService, SecurityIdentity identity) { + this.projectService = projectService; + this.identity = identity; + } + + /** + * Obtain all the projects of the current user. + */ + @GET + public List<org.opendc.web.proto.user.Project> getAll() { + return projectService.findByUser(identity.getPrincipal().getName()); + } + + /** + * Create a new project for the current user. + */ + @POST + @Transactional + @Consumes("application/json") + public org.opendc.web.proto.user.Project create(@Valid org.opendc.web.proto.user.Project.Create request) { + return projectService.create(identity.getPrincipal().getName(), request.getName()); + } + + /** + * Obtain a single project by its identifier. + */ + @GET + @Path("{project}") + public org.opendc.web.proto.user.Project get(@PathParam("project") long id) { + var project = projectService.findByUser(identity.getPrincipal().getName(), id); + if (project == null) { + throw new WebApplicationException("Project not found", 404); + } + + return project; + } + + /** + * Delete a project. + */ + @DELETE + @Path("{project}") + @Transactional + public org.opendc.web.proto.user.Project delete(@PathParam("project") long id) { + try { + var project = projectService.delete(identity.getPrincipal().getName(), id); + if (project == null) { + throw new WebApplicationException("Project not found", 404); + } + + return project; + } catch (IllegalArgumentException e) { + throw new WebApplicationException(e.getMessage(), 403); + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java new file mode 100644 index 00000000..a6838148 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.rest.user; + +import io.quarkus.security.identity.SecurityIdentity; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import org.opendc.web.server.service.ScenarioService; + +/** + * A resource representing the scenarios of a portfolio. + */ +@Produces("application/json") +@Path("/projects/{project}/scenarios") +@RolesAllowed("openid") +public final class ScenarioResource { + /** + * The service for managing the user scenarios. + */ + private final ScenarioService scenarioService; + + /** + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link ScenarioResource}. + * + * @param scenarioService The {@link ScenarioService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. + */ + public ScenarioResource(ScenarioService scenarioService, SecurityIdentity identity) { + this.scenarioService = scenarioService; + this.identity = identity; + } + + /** + * Obtain a scenario by its identifier. + */ + @GET + @Path("{scenario}") + public org.opendc.web.proto.user.Scenario get( + @PathParam("project") long projectId, @PathParam("scenario") int number) { + var scenario = scenarioService.findOne(identity.getPrincipal().getName(), projectId, number); + if (scenario == null) { + throw new WebApplicationException("Scenario not found", 404); + } + + return scenario; + } + + /** + * Delete a scenario. + */ + @DELETE + @Path("{scenario}") + @Transactional + public org.opendc.web.proto.user.Scenario delete( + @PathParam("project") long projectId, @PathParam("scenario") int number) { + var scenario = scenarioService.delete(identity.getPrincipal().getName(), projectId, number); + if (scenario == null) { + throw new WebApplicationException("Scenario not found", 404); + } + + return scenario; + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java new file mode 100644 index 00000000..54afc1ce --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.rest.user; + +import io.quarkus.security.identity.SecurityIdentity; +import java.util.List; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.validation.Valid; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import org.opendc.web.server.service.TopologyService; + +/** + * A resource representing the constructed datacenter topologies. + */ +@Produces("application/json") +@Path("/projects/{project}/topologies") +@RolesAllowed("openid") +public final class TopologyResource { + /** + * The service for managing the user topologies. + */ + private final TopologyService topologyService; + + /** + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link TopologyResource}. + * + * @param topologyService The {@link TopologyService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. + */ + public TopologyResource(TopologyService topologyService, SecurityIdentity identity) { + this.topologyService = topologyService; + this.identity = identity; + } + + /** + * Get all topologies that belong to the specified project. + */ + @GET + public List<org.opendc.web.proto.user.Topology> getAll(@PathParam("project") long projectId) { + return topologyService.findAll(identity.getPrincipal().getName(), projectId); + } + + /** + * Create a topology for this project. + */ + @POST + @Consumes("application/json") + @Transactional + public org.opendc.web.proto.user.Topology create( + @PathParam("project") long projectId, @Valid org.opendc.web.proto.user.Topology.Create request) { + var topology = topologyService.create(identity.getPrincipal().getName(), projectId, request); + + if (topology == null) { + throw new WebApplicationException("Topology not found", 404); + } + + return topology; + } + + /** + * Obtain a topology by its number. + */ + @GET + @Path("{topology}") + public org.opendc.web.proto.user.Topology get( + @PathParam("project") long projectId, @PathParam("topology") int number) { + var topology = topologyService.findOne(identity.getPrincipal().getName(), projectId, number); + + if (topology == null) { + throw new WebApplicationException("Topology not found", 404); + } + + return topology; + } + + /** + * Update the specified topology by its number. + */ + @PUT + @Path("{topology}") + @Consumes("application/json") + @Transactional + public org.opendc.web.proto.user.Topology update( + @PathParam("project") long projectId, + @PathParam("topology") int number, + @Valid org.opendc.web.proto.user.Topology.Update request) { + var topology = topologyService.update(identity.getPrincipal().getName(), projectId, number, request); + + if (topology == null) { + throw new WebApplicationException("Topology not found", 404); + } + + return topology; + } + + /** + * Delete the specified topology. + */ + @Path("{topology}") + @DELETE + @Transactional + public org.opendc.web.proto.user.Topology delete( + @PathParam("project") long projectId, @PathParam("topology") int number) { + var topology = topologyService.delete(identity.getPrincipal().getName(), projectId, number); + + if (topology == null) { + throw new WebApplicationException("Topology not found", 404); + } + + return topology; + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/UserService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserResource.java index b46b799b..c3fb2866 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/UserService.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserResource.java @@ -20,36 +20,51 @@ * SOFTWARE. */ -package org.opendc.web.server.service; +package org.opendc.web.server.rest.user; import io.quarkus.security.identity.SecurityIdentity; -import javax.enterprise.context.ApplicationScoped; +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; import org.opendc.web.proto.user.User; import org.opendc.web.proto.user.UserAccounting; +import org.opendc.web.server.service.UserAccountingService; /** - * Service for managing {@link User}s. + * A resource representing the active user. */ -@ApplicationScoped -public final class UserService { +@Produces("application/json") +@Path("/users") +@RolesAllowed("openid") +public final class UserResource { /** * The service for managing the user accounting. */ private final UserAccountingService accountingService; /** - * Construct a {@link UserService} instance. + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link UserResource}. * * @param accountingService The {@link UserAccountingService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. */ - public UserService(UserAccountingService accountingService) { + public UserResource(UserAccountingService accountingService, SecurityIdentity identity) { this.accountingService = accountingService; + this.identity = identity; } /** - * Obtain the {@link User} object for the specified <code>identity</code>. + * Get the current active user data. */ - public User getUser(SecurityIdentity identity) { + @GET + @Path("me") + public User get() { String userId = identity.getPrincipal().getName(); UserAccounting accounting = accountingService.getAccounting(userId); diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java index eb0982ec..47f44d27 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java @@ -61,7 +61,13 @@ public final class JobService { * Find a job by its identifier. */ public org.opendc.web.proto.runner.Job findById(long id) { - return toRunnerDto(Job.findById(id)); + Job job = Job.findById(id); + + if (job == null) { + return null; + } + + return toRunnerDto(job); } /** diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java index bf5206af..6a70db1e 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java @@ -219,6 +219,13 @@ public final class ScenarioService { * Convert a {@link Workload} entity into a DTO. */ public static org.opendc.web.proto.Workload toDto(Workload workload) { - return new org.opendc.web.proto.Workload(TraceService.toUserDto(workload.trace), workload.samplingFraction); + return new org.opendc.web.proto.Workload(toDto(workload.trace), workload.samplingFraction); + } + + /** + * Convert a {@link Trace] entity into a {@link org.opendc.web.proto.Trace} DTO. + */ + public static org.opendc.web.proto.Trace toDto(Trace trace) { + return new org.opendc.web.proto.Trace(trace.id, trace.name, trace.type); } } diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java new file mode 100644 index 00000000..c30edcbf --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.module.kotlin.KotlinModule; +import io.quarkus.jackson.ObjectMapperCustomizer; +import javax.inject.Singleton; + +/** + * Helper class to register the Kotlin Jackson module. + */ +@Singleton +public final class KotlinModuleCustomizer implements ObjectMapperCustomizer { + @Override + public void customize(ObjectMapper objectMapper) { + objectMapper.registerModule(new KotlinModule.Builder().build()); + } +} |
