Java tutorial
/** * Copyright 2005-2015 Red Hat, Inc. * * Red Hat licenses this file to you 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 io.fabric8.forge.rest; import io.fabric8.forge.rest.dto.CommandInfoDTO; import io.fabric8.forge.rest.dto.CommandInputDTO; import io.fabric8.forge.rest.dto.ExecutionRequest; import io.fabric8.forge.rest.dto.ExecutionResult; import io.fabric8.forge.rest.dto.UICommands; import io.fabric8.forge.rest.dto.ValidationResult; import io.fabric8.forge.rest.dto.WizardResultsDTO; import io.fabric8.forge.rest.git.GitContext; import io.fabric8.forge.rest.git.GitLockManager; import io.fabric8.forge.rest.git.GitOperation; import io.fabric8.forge.rest.git.RepositoriesResource; import io.fabric8.forge.rest.git.RepositoryResource; import io.fabric8.forge.rest.hooks.CommandCompletePostProcessor; import io.fabric8.forge.rest.main.GitUserHelper; import io.fabric8.forge.rest.main.ProjectFileSystem; import io.fabric8.forge.rest.main.RepositoryCache; import io.fabric8.forge.rest.main.UserDetails; import io.fabric8.forge.rest.ui.RestUIContext; import io.fabric8.forge.rest.ui.RestUIFunction; import io.fabric8.forge.rest.ui.RestUIRuntime; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.utils.Objects; import io.fabric8.utils.Strings; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.Repository; import org.jboss.forge.addon.convert.ConverterFactory; import org.jboss.forge.addon.resource.Resource; import org.jboss.forge.addon.resource.ResourceFactory; import org.jboss.forge.addon.ui.command.CommandFactory; import org.jboss.forge.addon.ui.command.UICommand; import org.jboss.forge.addon.ui.controller.CommandController; import org.jboss.forge.addon.ui.controller.CommandControllerFactory; import org.jboss.forge.addon.ui.controller.WizardCommandController; import org.jboss.forge.addon.ui.output.UIMessage; import org.jboss.forge.addon.ui.result.Result; import org.jboss.forge.furnace.Furnace; import org.jboss.forge.furnace.addons.AddonRegistry; import org.jboss.forge.furnace.services.Imported; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ejb.Stateless; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.NotFoundException; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static io.fabric8.forge.rest.Constants.*; @Path("/api/forge") @Stateless public class CommandsResource { private static final transient Logger LOG = LoggerFactory.getLogger(CommandsResource.class); protected static final Set<String> ignoreCommands = new HashSet<>( Arrays.asList("devops-edit", "devops-new-build", // forge commands "build-and-install-an-addon", "install-an-addon", "install-an-addon-from-git", "remove-an-addon", "update-an-addon", // project commands "build", "project-new")); protected static final List<String> ignoreCommandPrefixes = Arrays.asList("addon-", "archetype-", "fabric8-", "git-", "camel-get-"); @Inject private Furnace furnace; @Inject private CommandControllerFactory commandControllerFactory; @Inject private CommandFactory commandFactory; @Inject private CommandCompletePostProcessor commandCompletePostProcessor; @Inject private ProjectFileSystem projectFileSystem; @Inject private GitUserHelper gitUserHelper; @Inject private RepositoryCache repositoryCache; @Inject private KubernetesClient kubernetes; @Inject private GitLockManager lockManager; @Context private HttpServletRequest request; private ConverterFactory converterFactory; @GET public String getInfo() { return furnace.getVersion().toString(); } @GET @Path("/commandNames") @Produces(MediaType.APPLICATION_JSON) public List<String> getCommandNames() { List<String> answer = new ArrayList<>(); try (RestUIContext context = new RestUIContext()) { for (String commandName : commandFactory.getCommandNames(context)) { answer.add(commandName); } } return answer; } @GET @Path("/commands") @Produces(MediaType.APPLICATION_JSON) public List<CommandInfoDTO> getCommands() throws Exception { return getCommands(null, null, null); } @GET @Path("/commands/{namespace}/{projectName}") @Produces(MediaType.APPLICATION_JSON) public List<CommandInfoDTO> getCommands(@PathParam("namespace") String namespace, @PathParam("projectName") String projectName) throws Exception { return getCommands(namespace, projectName, null); } @GET @Path("/commands/{namespace}/{projectName}/{path: .*}") @Produces(MediaType.APPLICATION_JSON) public List<CommandInfoDTO> getCommands(@PathParam("namespace") String namespace, @PathParam("projectName") String projectName, @PathParam("path") String resourcePath) throws Exception { return withUIContext(namespace, projectName, resourcePath, false, new RestUIFunction<List<CommandInfoDTO>>() { @Override public List<CommandInfoDTO> apply(RestUIContext context) { List<CommandInfoDTO> answer = new ArrayList<>(); for (String name : commandFactory.getCommandNames(context)) { try { CommandInfoDTO dto = createCommandInfoDTO(context, name); if (dto != null && dto.isEnabled()) { answer.add(dto); } } catch (Exception e) { LOG.warn("Ignored exception on command " + name + " probably due to missing project?: " + e, e); } } return answer; } }); } @GET @Path("/command/{name}") @Produces(MediaType.APPLICATION_JSON) public Response getCommandInfo(@PathParam("name") String name) throws Exception { return getCommandInfo(name, null, null, null); } @GET @Path("/command/{name}/{namespace}/{projectName}/{path: .*}") @Produces(MediaType.APPLICATION_JSON) public Response getCommandInfo(@PathParam("name") final String name, @PathParam("namespace") final String namespace, @PathParam("projectName") final String projectName, @PathParam("path") final String resourcePath) throws Exception { return withUIContext(namespace, projectName, resourcePath, false, new RestUIFunction<Response>() { @Override public Response apply(RestUIContext context) { CommandInfoDTO answer = createCommandInfoDTO(context, name); if (answer != null) { return Response.ok(answer).build(); } else { return Response.status(Status.NOT_FOUND).build(); } } }); } @GET @Path("/commandInput/{name}") @Produces(MediaType.APPLICATION_JSON) public Response getCommandInput(@PathParam("name") String name) throws Exception { return getCommandInput(name, null, null, null); } @GET @Path("/commandInput/{name}/{namespace}/{projectName}") @Produces(MediaType.APPLICATION_JSON) public Response getCommandInput(@PathParam("name") final String name, @PathParam("namespace") String namespace, @PathParam("projectName") String projectName) throws Exception { return getCommandInput(name, namespace, projectName, null); } @GET @Path("/commandInput/{name}/{namespace}/{projectName}/{path: .*}") @Produces(MediaType.APPLICATION_JSON) public Response getCommandInput(@PathParam("name") final String name, @PathParam("namespace") String namespace, @PathParam("projectName") String projectName, @PathParam("path") String resourcePath) throws Exception { return withUIContext(namespace, projectName, resourcePath, false, new RestUIFunction<Response>() { @Override public Response apply(RestUIContext context) throws Exception { CommandInputDTO answer = null; UICommand command = getCommandByName(context, name); if (command != null) { CommandController controller = createController(context, command); answer = UICommands.createCommandInputDTO(context, command, controller); } if (answer != null) { return Response.ok(answer).build(); } else { return Response.status(Status.NOT_FOUND).build(); } } }); } @POST @Path("/command/execute/{name}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response executeCommand(@PathParam("name") final String name, final ExecutionRequest executionRequest) throws Exception { try { final CommandCompletePostProcessor postProcessor = this.commandCompletePostProcessor; final UserDetails userDetails; if (postProcessor != null) { userDetails = postProcessor.preprocessRequest(name, executionRequest, request); } else { userDetails = null; } String namespace = executionRequest.getNamespace(); String projectName = executionRequest.getProjectName(); String resourcePath = executionRequest.getResource(); return withUIContext(namespace, projectName, resourcePath, true, new RestUIFunction<Response>() { @Override public Response apply(RestUIContext uiContext) throws Exception { userDetails.setAddress(uiContext.getCloneUrl()); return doExecute(name, executionRequest, postProcessor, userDetails, uiContext); } }); } catch (Throwable e) { LOG.warn("Failed to invoke command " + name + " on " + executionRequest + ". " + e, e); throw e; } } /** * This method is only used to warm up JBoss Forge so we can create a sample project on startup in a temporary directory */ public Response doExecute(String name, ExecutionRequest executionRequest, CommandCompletePostProcessor postProcessor, UserDetails userDetails, RestUIContext uiContext) throws Exception { try (RestUIContext context = uiContext) { UICommand command = getCommandByName(context, name); if (command == null) { return Response.status(Status.NOT_FOUND).build(); } List<Map<String, Object>> inputList = executionRequest.getInputList(); // lets ensure a valid targetLocation for new projects if (Objects.equal(PROJECT_NEW_COMMAND, name)) { if (inputList.size() > 0) { Map<String, Object> map = inputList.get(0); map.put(TARGET_LOCATION_PROPERTY, projectFileSystem.getUserProjectFolderLocation(userDetails)); } } CommandController controller = createController(context, command); configureAttributeMaps(userDetails, controller, executionRequest); ExecutionResult answer = null; if (controller instanceof WizardCommandController) { WizardCommandController wizardCommandController = (WizardCommandController) controller; List<WizardCommandController> controllers = new ArrayList<>(); List<CommandInputDTO> stepPropertiesList = new ArrayList<>(); List<ExecutionResult> stepResultList = new ArrayList<>(); List<ValidationResult> stepValidationList = new ArrayList<>(); controllers.add(wizardCommandController); WizardCommandController lastController = wizardCommandController; Result lastResult = null; int page = executionRequest.wizardStep(); int nextPage = page + 1; boolean canMoveToNextStep = false; for (Map<String, Object> inputs : inputList) { UICommands.populateController(inputs, lastController, getConverterFactory()); List<UIMessage> messages = lastController.validate(); ValidationResult stepValidation = UICommands.createValidationResult(context, lastController, messages); stepValidationList.add(stepValidation); if (!stepValidation.isValid()) { break; } canMoveToNextStep = lastController.canMoveToNextStep(); boolean valid = lastController.isValid(); if (!canMoveToNextStep) { if (lastController.canExecute()) { // lets assume we can execute now LOG.info("About to invoked command " + name + " stepValidation: " + stepValidation + " messages: " + messages + " with " + executionRequest); lastResult = lastController.execute(); LOG.debug("Invoked command " + name + " with " + executionRequest + " result: " + lastResult); ExecutionResult stepResults = UICommands.createExecutionResult(context, lastResult, false); stepResultList.add(stepResults); break; } else { stepValidation.addValidationError("Forge command failed with an internal error"); LOG.warn( "Cannot move to next step as canExecute() returns false but the validation seems to be fine!"); break; } } else if (!valid) { stepValidation.addValidationError( "Forge command is not valid but didn't report any validation errors!"); LOG.warn("Cannot move to next step as invalid despite the validation saying otherwise"); break; } WizardCommandController nextController = lastController.next(); if (nextController != null) { if (nextController == lastController) { LOG.warn("No idea whats going on ;)"); break; } lastController = nextController; lastController.initialize(); controllers.add(lastController); CommandInputDTO stepDto = UICommands.createCommandInputDTO(context, command, lastController); stepPropertiesList.add(stepDto); } else { int i = 0; for (WizardCommandController stepController : controllers) { Map<String, Object> stepControllerInputs = inputList.get(i++); UICommands.populateController(stepControllerInputs, stepController, getConverterFactory()); lastResult = stepController.execute(); LOG.debug("Invoked command " + name + " with " + executionRequest + " result: " + lastResult); ExecutionResult stepResults = UICommands.createExecutionResult(context, lastResult, false); stepResultList.add(stepResults); } break; } } answer = UICommands.createExecutionResult(context, lastResult, canMoveToNextStep); WizardResultsDTO wizardResultsDTO = new WizardResultsDTO(stepPropertiesList, stepValidationList, stepResultList); answer.setWizardResults(wizardResultsDTO); } else { Map<String, Object> inputs = inputList.get(0); UICommands.populateController(inputs, controller, getConverterFactory()); Result result = controller.execute(); LOG.debug("Invoked command " + name + " with " + executionRequest + " result: " + result); answer = UICommands.createExecutionResult(context, result, false); } if (answer.isCommandCompleted() && postProcessor != null) { postProcessor.firePostCompleteActions(name, executionRequest, context, controller, answer, request); } context.setCommitMessage(ExecutionRequest.createCommitMessage(name, executionRequest)); return Response.ok(answer).build(); } } protected void configureAttributeMaps(UserDetails userDetails, CommandController controller, ExecutionRequest executionRequest) { Map<Object, Object> attributeMap = controller.getContext().getAttributeMap(); if (userDetails != null) { attributeMap.put("gitUser", userDetails.getUser()); attributeMap.put("gitPassword", userDetails.getPassword()); attributeMap.put("gitAuthorEmail", userDetails.getEmail()); attributeMap.put("gitAddress", userDetails.getAddress()); attributeMap.put("gitUrl", userDetails.getAddress()); attributeMap.put("gitBranch", userDetails.getBranch()); attributeMap.put("projectName", executionRequest.getProjectName()); attributeMap.put("buildName", executionRequest.getProjectName()); attributeMap.put("namespace", executionRequest.getNamespace()); attributeMap.put("jenkinsWorkflowFolder", projectFileSystem.getJenkinsWorkflowFolder()); projectFileSystem.asyncCloneOrPullJenkinsWorkflows(userDetails); } } @POST @Path("/command/validate/{name}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response validateCommand(@PathParam("name") final String name, final ExecutionRequest executionRequest) throws Exception { try { final UserDetails userDetails; if (commandCompletePostProcessor != null) { userDetails = commandCompletePostProcessor.preprocessRequest(name, executionRequest, request); } else { userDetails = null; } final String namespace = executionRequest.getNamespace(); final String projectName = executionRequest.getProjectName(); final String resourcePath = executionRequest.getResource(); GitContext gitContext = new GitContext(); gitContext.setRequirePull(false); return withUIContext(namespace, projectName, resourcePath, false, new RestUIFunction<Response>() { @Override public Response apply(RestUIContext uiContext) throws Exception { return doValidate(name, executionRequest, userDetails, uiContext); } }, gitContext); } catch (Throwable e) { LOG.warn("Failed to invoke command " + name + " on " + executionRequest + ". " + e, e); throw e; } } /** * Helper method used purely to pre-load and warm up JBoss Forge */ public Response doValidate(String name, ExecutionRequest executionRequest, UserDetails userDetails, RestUIContext uiContext) throws Exception { try (RestUIContext context = uiContext) { UICommand command = getCommandByName(context, name); if (command == null) { return Response.status(Status.NOT_FOUND).build(); } List<Map<String, Object>> inputList = executionRequest.getInputList(); CommandController controller = createController(context, command); configureAttributeMaps(userDetails, controller, executionRequest); ValidationResult answer = null; if (controller instanceof WizardCommandController) { WizardCommandController wizardCommandController = (WizardCommandController) controller; List<WizardCommandController> controllers = new ArrayList<>(); List<CommandInputDTO> stepPropertiesList = new ArrayList<>(); List<ValidationResult> stepResultList = new ArrayList<>(); List<ValidationResult> stepValidationList = new ArrayList<>(); controllers.add(wizardCommandController); WizardCommandController lastController = wizardCommandController; List<UIMessage> lastResult = null; int page = executionRequest.wizardStep(); int nextPage = page + 1; boolean canMoveToNextStep = false; for (Map<String, Object> inputs : inputList) { UICommands.populateController(inputs, lastController, getConverterFactory()); CommandInputDTO stepDto = UICommands.createCommandInputDTO(context, command, lastController); stepPropertiesList.add(stepDto); canMoveToNextStep = lastController.canMoveToNextStep(); boolean valid = lastController.isValid(); if (!canMoveToNextStep) { // lets assume we can execute now lastResult = lastController.validate(); LOG.debug( "Invoked command " + name + " with " + executionRequest + " result: " + lastResult); ValidationResult stepResults = UICommands.createValidationResult(context, controller, lastResult); stepResultList.add(stepResults); break; } else if (!valid) { LOG.warn("Cannot move to next step as invalid despite the validation saying otherwise"); break; } WizardCommandController nextController = lastController.next(); if (nextController != null) { if (nextController == lastController) { LOG.warn("No idea whats going on ;)"); break; } lastController = nextController; lastController.initialize(); controllers.add(lastController); } else { int i = 0; for (WizardCommandController stepController : controllers) { Map<String, Object> stepControllerInputs = inputList.get(i++); UICommands.populateController(stepControllerInputs, stepController, getConverterFactory()); lastResult = stepController.validate(); LOG.debug("Invoked command " + name + " with " + executionRequest + " result: " + lastResult); ValidationResult stepResults = UICommands.createValidationResult(context, controller, lastResult); stepResultList.add(stepResults); } break; } } answer = UICommands.createValidationResult(context, controller, lastResult); // TODO do we need stepValidationList? //WizardResultsDTO wizardResultsDTO = new WizardResultsDTO(stepPropertiesList, stepValidationList, stepResultList); WizardResultsDTO wizardResultsDTO = new WizardResultsDTO(stepPropertiesList, stepResultList, new ArrayList<ExecutionResult>()); answer.setWizardResults(wizardResultsDTO); } else { Map<String, Object> inputs = inputList.get(0); UICommands.populateController(inputs, controller, getConverterFactory()); List<UIMessage> result = controller.validate(); LOG.debug("Invoked command " + name + " with " + executionRequest + " result: " + result); answer = UICommands.createValidationResult(context, controller, result); } return Response.ok(answer).build(); } } protected CommandInfoDTO createCommandInfoDTO(RestUIContext context, String name) { CommandInfoDTO answer = null; if (isValidCommandName(name)) { UICommand command = getCommandByName(context, name); if (command != null) { answer = UICommands.createCommandInfoDTO(context, command); } } return answer; } /** * Returns true if the name is valid. Lets filter out commands which are not suitable to run inside fabric8-forge */ protected boolean isValidCommandName(String name) { if (Strings.isNullOrBlank(name) || ignoreCommands.contains(name)) { return false; } for (String prefix : ignoreCommandPrefixes) { if (name.startsWith(prefix)) { return false; } } return true; } protected UICommand getCommandByName(RestUIContext context, String name) { return commandFactory.getCommandByName(context, name); } protected CommandController createController(RestUIContext context, UICommand command) throws Exception { RestUIRuntime runtime = new RestUIRuntime(); CommandController controller = commandControllerFactory.createController(context, runtime, command); controller.initialize(); return controller; } protected <T> T withUIContext(String namespace, String projectName, String resourcePath, boolean write, RestUIFunction<T> function) throws Exception { return withUIContext(namespace, projectName, resourcePath, write, function, new GitContext()); } protected <T> T withUIContext(final String namespace, final String projectName, String resourcePath, boolean write, final RestUIFunction<T> function, final GitContext gitContext) throws Exception { final ResourceFactory resourceFactory = getResourceFactory(); if (Strings.isNotBlank(namespace) && Strings.isNotBlank(projectName) && resourceFactory != null) { RepositoriesResource repositoriesResource = new RepositoriesResource(gitUserHelper, repositoryCache, projectFileSystem, lockManager, kubernetes); repositoriesResource.setRequest(request); final RepositoryResource projectResource = repositoriesResource.projectRepositoryResource(namespace, projectName); if (projectResource == null) { throw new NotFoundException("Could not find git project for namespace: " + namespace + " and projectName: " + projectName); } else { GitOperation<T> operation = new GitOperation<T>() { @Override public T call(Git git, GitContext gitContext) throws Exception { Repository repository = git.getRepository(); File gitDir = repository.getDirectory(); File directory = gitDir.getParentFile(); LOG.debug("using repository directory: " + directory.getAbsolutePath()); Resource<?> selection = resourceFactory.create(directory); String cloneUrl = projectResource.getCloneUrl(); try (RestUIContext context = new RestUIContext(selection, namespace, projectName, cloneUrl)) { T answer = function.apply(context); String commitMessage = context.getCommitMessage(); if (Strings.isNotBlank(commitMessage)) { projectResource.setMessage(commitMessage); } return answer; } } }; if (write) { return projectResource.gitWriteOperation(operation); } else { return projectResource.gitReadOperation(operation); } } } else { try (RestUIContext context = new RestUIContext(null)) { return function.apply(context); } } } public RestUIContext createUIContext(File file) { ResourceFactory resourceFactory = getResourceFactory(); Resource<File> selection = resourceFactory.create(file); return new RestUIContext(selection); } protected ResourceFactory getResourceFactory() { AddonRegistry addonRegistry = furnace.getAddonRegistry(); Imported<ResourceFactory> resourceFactoryImport = addonRegistry.getServices(ResourceFactory.class); ResourceFactory resourceFactory = null; try { resourceFactory = resourceFactoryImport.get(); } catch (Exception e) { LOG.warn("Failed to get ResourceFactory injected: " + e, e); } if (resourceFactory == null) { // lets try one more time - might work this time? resourceFactory = resourceFactoryImport.get(); } return resourceFactory; } public ConverterFactory getConverterFactory() { if (converterFactory == null) { AddonRegistry addonRegistry = furnace.getAddonRegistry(); Imported<ConverterFactory> converterFactoryImport = addonRegistry.getServices(ConverterFactory.class); converterFactory = converterFactoryImport.get(); } return converterFactory; } public void setConverterFactory(ConverterFactory converterFactory) { this.converterFactory = converterFactory; } }