/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * 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 org.kie.workbench.common.screens.examples.backend.server; import java.io.File; import java.io.FileOutputStream; import java.net.URL; import java.nio.file.FileSystems; import java.nio.file.SimpleFileVisitor; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Event; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.guvnor.common.services.backend.config.SafeSessionInfo; import org.guvnor.common.services.project.context.ProjectContextChangeEvent; import org.guvnor.common.services.project.events.NewProjectEvent; import org.guvnor.common.services.project.model.POM; import org.guvnor.common.services.project.model.Project; import org.guvnor.common.services.shared.metadata.MetadataService; import org.guvnor.structure.organizationalunit.OrganizationalUnit; import org.guvnor.structure.organizationalunit.OrganizationalUnitService; import org.guvnor.structure.repositories.Repository; import org.guvnor.structure.repositories.RepositoryEnvironmentConfigurations; import org.guvnor.structure.repositories.RepositoryService; import org.guvnor.structure.repositories.impl.git.GitRepository; import org.guvnor.structure.server.config.ConfigGroup; import org.guvnor.structure.server.config.ConfigurationFactory; import org.guvnor.structure.server.repositories.RepositoryFactory; import org.jboss.errai.bus.server.annotations.Service; import org.kie.workbench.common.screens.examples.model.ExampleOrganizationalUnit; import org.kie.workbench.common.screens.examples.model.ExampleProject; import org.kie.workbench.common.screens.examples.model.ExampleRepository; import org.kie.workbench.common.screens.examples.model.ExampleTargetRepository; import org.kie.workbench.common.screens.examples.model.ExamplesMetaData; import org.kie.workbench.common.screens.examples.service.ExamplesService; import org.kie.workbench.common.services.shared.project.KieProject; import org.kie.workbench.common.services.shared.project.KieProjectService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.uberfire.backend.server.util.Paths; import org.uberfire.backend.vfs.Path; import org.uberfire.commons.validation.PortablePreconditions; import org.uberfire.io.IOService; import org.uberfire.java.nio.IOException; import org.uberfire.java.nio.file.FileAlreadyExistsException; import org.uberfire.java.nio.file.FileVisitResult; import org.uberfire.java.nio.file.FileVisitor; import org.uberfire.java.nio.file.Files; import org.uberfire.java.nio.file.StandardCopyOption; import org.uberfire.java.nio.file.attribute.BasicFileAttributes; import org.uberfire.java.nio.fs.jgit.util.JGitUtil; import org.uberfire.rpc.SessionInfo; import static org.guvnor.structure.repositories.EnvironmentParameters.SCHEME; import static org.guvnor.structure.server.config.ConfigType.REPOSITORY; @Service @ApplicationScoped public class ExamplesServiceImpl implements ExamplesService { private static final Logger logger = LoggerFactory.getLogger(ExamplesServiceImpl.class); private static final String PROJECT_DESCRIPTON = "project.description"; private static final String KIE_WB_PLAYGROUND_ZIP = "org/kie/kie-wb-playground/kie-wb-playground.zip"; private final Set<Repository> clonedRepositories = new HashSet<Repository>(); private IOService ioService; private ConfigurationFactory configurationFactory; private RepositoryFactory repositoryFactory; private KieProjectService projectService; private RepositoryService repositoryService; private OrganizationalUnitService ouService; private Event<NewProjectEvent> newProjectEvent; private SafeSessionInfo sessionInfo; private MetadataService metadataService; private ExampleRepository playgroundRepository; public ExamplesServiceImpl() { //Zero-parameter Constructor for CDI proxies } @Inject public ExamplesServiceImpl(final @Named("ioStrategy") IOService ioService, final ConfigurationFactory configurationFactory, final RepositoryFactory repositoryFactory, final KieProjectService projectService, final RepositoryService repositoryService, final OrganizationalUnitService ouService, final MetadataService metadataService, final Event<NewProjectEvent> newProjectEvent, final SessionInfo sessionInfo) { this.ioService = ioService; this.configurationFactory = configurationFactory; this.repositoryFactory = repositoryFactory; this.projectService = projectService; this.repositoryService = repositoryService; this.ouService = ouService; this.metadataService = metadataService; this.newProjectEvent = newProjectEvent; this.sessionInfo = new SafeSessionInfo(sessionInfo); } @PostConstruct public void initPlaygroundRepository() { try { String userDir = System.getProperty("user.dir"); File playgroundDirectory = new File(userDir, ".kie-wb-playground"); if (playgroundDirectory.exists()) { cleanPlaygroundDirectory(playgroundDirectory.toPath()); } playgroundDirectory.mkdirs(); URL resource = getClass().getClassLoader().getResource(KIE_WB_PLAYGROUND_ZIP); if (resource == null) { logger.warn("Playground repository jar not found on classpath."); return; } try (ZipInputStream inputStream = new ZipInputStream(resource.openStream())) { ZipEntry zipEntry = null; while ((zipEntry = inputStream.getNextEntry()) != null) { byte[] buffer = new byte[1024]; File file = new File(playgroundDirectory, zipEntry.getName()); if (zipEntry.isDirectory()) { file.mkdirs(); } else { try (FileOutputStream fos = new FileOutputStream(file)) { int read = -1; while ((read = inputStream.read(buffer)) != -1) { fos.write(buffer, 0, read); } } } } Git git = JGitUtil.newRepository(playgroundDirectory, false); git.add().addFilepattern(".").call(); git.commit().setMessage("Initial commit").call(); String repositoryUrl = resolveRepositoryUrl(playgroundDirectory.getAbsolutePath()); playgroundRepository = new ExampleRepository(repositoryUrl); } } catch (java.io.IOException | GitAPIException e) { logger.error("Unable to initialize playground git repository. Only custom repository definition will be available in the Workbench.", e); } } private void cleanPlaygroundDirectory(java.nio.file.Path playgroundDirectoryPath) throws java.io.IOException { java.nio.file.Files.walkFileTree(playgroundDirectoryPath, new SimpleFileVisitor<java.nio.file.Path>() { @Override public java.nio.file.FileVisitResult visitFile(java.nio.file.Path file, java.nio.file.attribute.BasicFileAttributes attrs) throws java.io.IOException { file.toFile().delete(); return java.nio.file.FileVisitResult.CONTINUE; } @Override public java.nio.file.FileVisitResult postVisitDirectory(java.nio.file.Path dir, java.io.IOException exc) throws java.io.IOException { dir.toFile().delete(); return java.nio.file.FileVisitResult.CONTINUE; } }); } String resolveRepositoryUrl(final String playgroundDirectoryPath) { if ("\\".equals(getFileSeparator())) { return "file:///" + playgroundDirectoryPath.replaceAll("\\\\", "/"); } else { return "file://" + playgroundDirectoryPath; } } @Override public ExamplesMetaData getMetaData() { return new ExamplesMetaData(getPlaygroundRepository(), getExampleOrganizationalUnits()); } @Override public ExampleRepository getPlaygroundRepository() { return playgroundRepository; } Set<ExampleOrganizationalUnit> getExampleOrganizationalUnits() { final Collection<OrganizationalUnit> organizationalUnits = ouService.getOrganizationalUnits(); final Set<ExampleOrganizationalUnit> exampleOrganizationalUnits = new HashSet<ExampleOrganizationalUnit>(); for (OrganizationalUnit ou : organizationalUnits) { exampleOrganizationalUnits.add(new ExampleOrganizationalUnit(ou.getName())); } return exampleOrganizationalUnits; } @Override public Set<ExampleProject> getProjects(final ExampleRepository repository) { if (repository == null) { return Collections.emptySet(); } final String repositoryURL = repository.getUrl(); if (repositoryURL == null || repositoryURL.trim().isEmpty()) { return Collections.emptySet(); } // Avoid cloning playground repository multiple times Repository gitRepository = resolveGitRepository(repository); if (gitRepository == null) { return Collections.emptySet(); } final Set<Project> projects = projectService.getProjects(gitRepository, "master"); return convert(projects); } Repository resolveGitRepository(final ExampleRepository exampleRepository) { if (exampleRepository.equals(playgroundRepository)) { return clonedRepositories.stream().filter(r -> exampleRepository.getUrl().equals(r.getEnvironment().get("origin"))).findFirst().orElseGet(() -> cloneRepository(exampleRepository.getUrl())); } else { return cloneRepository(exampleRepository.getUrl()); } } private Repository cloneRepository(final String repositoryURL) { Repository repository = null; try { final String alias = getExampleAlias(repositoryURL); final Map<String, Object> env = new HashMap<String, Object>() {{ put("origin", repositoryURL); put(SCHEME, "git"); put("replaceIfExists", true); }}; final ConfigGroup repositoryConfig = configurationFactory.newConfigGroup(REPOSITORY, alias, ""); for (final Map.Entry<String, Object> entry : env.entrySet()) { repositoryConfig.addConfigItem(configurationFactory.newConfigItem(entry.getKey(), entry.getValue())); } repository = repositoryFactory.newRepository(repositoryConfig); clonedRepositories.add(repository); return repository; } catch (final Exception e) { logger.error("Error during create repository", e); throw new RuntimeException(e); } } String getExampleAlias(final String repositoryURL) { String alias = repositoryURL; alias = alias.substring(alias.lastIndexOf("/") + 1); final int lastDotIndex = alias.lastIndexOf('.'); if (lastDotIndex > 0) { alias = alias.substring(0, lastDotIndex); } return "examples-" + alias; } String getFileSeparator() { return FileSystems.getDefault().getSeparator(); } private Set<ExampleProject> convert(final Set<Project> projects) { return projects.stream() .map(p -> new ExampleProject(p.getRootPath(), p.getProjectName(), readDescription(p), getTags(p))) .collect(Collectors.toSet()); } private String readDescription(final Project project) { final Path root = project.getRootPath(); final POM pom = project.getPom(); final org.uberfire.java.nio.file.Path nioRoot = Paths.convert(root); final org.uberfire.java.nio.file.Path nioDescription = nioRoot.resolve(PROJECT_DESCRIPTON); String description = "Example '" + project.getProjectName() + "' project"; if (ioService.exists(nioDescription)) { description = ioService.readAllString(nioDescription); } else if (pom != null && pom.getDescription() != null && !pom.getDescription().isEmpty()) { description = pom.getDescription(); } return description; } private List<String> getTags(final Project project) { List<String> tags = metadataService.getTags(project.getPomXMLPath()); tags.sort((t1, t2) -> t1.compareTo(t2)); return tags; } @Override public boolean validateRepositoryName(final String name) { return repositoryService.validateRepositoryName(name); } @Override public ProjectContextChangeEvent setupExamples(final ExampleOrganizationalUnit exampleTargetOU, final ExampleTargetRepository exampleTarget, final String branch, final List<ExampleProject> exampleProjects) { PortablePreconditions.checkNotNull("exampleTargetOU", exampleTargetOU); PortablePreconditions.checkNotNull("exampleTarget", exampleTarget); PortablePreconditions.checkNotNull("branch", branch); PortablePreconditions.checkNotNull("exampleProjects", exampleProjects); PortablePreconditions.checkCondition("Must have at least one ExampleProject", exampleProjects.size() > 0); //Retrieve or create Organizational Unit final String targetOUName = exampleTargetOU.getName(); OrganizationalUnit targetOU = ouService.getOrganizationalUnit(targetOUName); if (targetOU == null) { targetOU = createOrganizationalUnit(targetOUName); } //Retrieve or create target Repository final String targetRepositoryAlias = exampleTarget.getAlias(); Repository targetRepository = repositoryService.getRepository(targetRepositoryAlias); if (targetRepository == null) { targetRepository = createTargetRepository(targetOU, targetRepositoryAlias); } final Path targetRepositoryRoot = targetRepository.getBranchRoot(branch); final org.uberfire.java.nio.file.Path nioTargetRepositoryRoot = Paths.convert(targetRepositoryRoot); KieProject firstExampleProject = null; try { ioService.startBatch(nioTargetRepositoryRoot.getFileSystem()); for (ExampleProject exampleProject : exampleProjects) { final Path exampleProjectRoot = exampleProject.getRoot(); final org.uberfire.java.nio.file.Path nioExampleProjectRoot = Paths.convert(exampleProjectRoot); final org.uberfire.java.nio.file.Path nioTargetProjectRoot = nioTargetRepositoryRoot.resolve(exampleProject.getName()); final RecursiveCopier copier = new RecursiveCopier(nioExampleProjectRoot, nioTargetProjectRoot); Files.walkFileTree(nioExampleProjectRoot, copier); // Signal creation of new Project (Creation of OU and Repository, if applicable, // are already handled in the corresponding services). final Path targetProjectRoot = Paths.convert(nioTargetProjectRoot); final KieProject project = projectService.resolveProject(targetProjectRoot); newProjectEvent.fire(new NewProjectEvent(project, sessionInfo.getId(), sessionInfo.getIdentity().getIdentifier())); //Store first new example project if (firstExampleProject == null) { firstExampleProject = project; } } } catch (IOException ioe) { logger.error("Unable to create Example(s).", ioe); } finally { ioService.endBatch(); } return new ProjectContextChangeEvent(targetOU, targetRepository, targetRepository.getDefaultBranch(), firstExampleProject); } private OrganizationalUnit createOrganizationalUnit(final String name) { final OrganizationalUnit ou = ouService.createOrganizationalUnit(name, "", ""); return ou; } private Repository createTargetRepository(final OrganizationalUnit ou, final String alias) { final RepositoryEnvironmentConfigurations configuration = new RepositoryEnvironmentConfigurations(); configuration.setManaged(false); final Repository repository = repositoryService.createRepository(ou, GitRepository.SCHEME, alias, configuration); return repository; } @Override public int priority() { return 0; } @Override public void dispose() { for (Repository repository : clonedRepositories) { try { ioService.delete(Paths.convert(repository.getRoot()).getFileSystem().getPath(null)); } catch (Exception e) { logger.warn("Unable to remove transient Repository '" + repository.getAlias() + "'.", e); } } } static class RecursiveCopier implements FileVisitor<org.uberfire.java.nio.file.Path> { private final org.uberfire.java.nio.file.Path source; private final org.uberfire.java.nio.file.Path target; RecursiveCopier(final org.uberfire.java.nio.file.Path source, final org.uberfire.java.nio.file.Path target) { this.source = source; this.target = target; } @Override public FileVisitResult preVisitDirectory(final org.uberfire.java.nio.file.Path src, final BasicFileAttributes attrs) { final org.uberfire.java.nio.file.Path tgt = target.resolve(source.relativize(src)); try { Files.copy(src, tgt, StandardCopyOption.REPLACE_EXISTING); } catch (FileAlreadyExistsException x) { //Swallow } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(final org.uberfire.java.nio.file.Path file, final BasicFileAttributes attrs) { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(final org.uberfire.java.nio.file.Path dir, final IOException exc) { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(final org.uberfire.java.nio.file.Path file, final IOException exc) { return FileVisitResult.CONTINUE; } } // Test getters and setters Set<Repository> getClonedRepositories() { return clonedRepositories; } void setPlaygroundRepository(final ExampleRepository playgroundRepository) { this.playgroundRepository = playgroundRepository; } }