Java tutorial
/******************************************************************************* * Copyright (c) 2012-2014 University of Stuttgart. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and the Apache License 2.0 which both accompany this distribution, * and are available at http://www.eclipse.org/legal/epl-v10.html * and http://www.apache.org/licenses/LICENSE-2.0 * * Contributors: * Oliver Kopp - initial API and implementation *******************************************************************************/ /* * Modifications Copyright 2016 ZTE Corporation. */ package org.eclipse.winery.repository.backend.filebased; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.charset.Charset; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.nio.file.spi.FileSystemProvider; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.SortedSet; import java.util.TreeSet; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import javax.ws.rs.core.MediaType; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.lang3.SystemUtils; import org.eclipse.winery.common.RepositoryFileReference; import org.eclipse.winery.common.Util; import org.eclipse.winery.common.ids.GenericId; import org.eclipse.winery.common.ids.Namespace; import org.eclipse.winery.common.ids.XMLId; import org.eclipse.winery.common.ids.definitions.ArtifactTemplateId; import org.eclipse.winery.common.ids.definitions.ArtifactTypeId; import org.eclipse.winery.common.ids.definitions.CapabilityTypeId; import org.eclipse.winery.common.ids.definitions.NodeTypeId; import org.eclipse.winery.common.ids.definitions.NodeTypeImplementationId; import org.eclipse.winery.common.ids.definitions.PolicyTemplateId; import org.eclipse.winery.common.ids.definitions.PolicyTypeId; import org.eclipse.winery.common.ids.definitions.RelationshipTypeId; import org.eclipse.winery.common.ids.definitions.RelationshipTypeImplementationId; import org.eclipse.winery.common.ids.definitions.RequirementTypeId; import org.eclipse.winery.common.ids.definitions.ServiceTemplateId; import org.eclipse.winery.common.ids.definitions.TOSCAComponentId; import org.eclipse.winery.common.ids.elements.TOSCAElementId; import org.eclipse.winery.common.servicetemplate.FileInfo; import org.eclipse.winery.repository.Constants; import org.eclipse.winery.repository.backend.AbstractRepository; import org.eclipse.winery.repository.backend.BackendUtils; import org.eclipse.winery.repository.backend.IRepository; import org.eclipse.winery.repository.backend.IRepositoryAdministration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * When it comes to a storage of plain files, we use Java 7's nio internally. Therefore, we intend * to expose the stream types offered by java.nio.Files: BufferedReader/BufferedWriter */ public class FilebasedRepository extends AbstractRepository implements IRepository, IRepositoryAdministration { private static final Logger logger = LoggerFactory.getLogger(FilebasedRepository.class); protected final Path repositoryRoot; // convenience variables to have a clean code private final FileSystem fileSystem; private final FileSystemProvider provider; private Path makeAbsolute(Path relativePath) { return this.repositoryRoot.resolve(relativePath); } @Override public boolean flagAsExisting(GenericId id) { Path path = this.id2AbsolutePath(id); try { FileUtils.createDirectory(path); } catch (IOException e) { FilebasedRepository.logger.debug(e.toString()); return false; } return true; } private Path id2AbsolutePath(GenericId id) { Path relativePath = this.fileSystem.getPath(BackendUtils.getPathInsideRepo(id)); return this.makeAbsolute(relativePath); } /** * Converts the given reference to an absolute path of the underlying FileSystem */ public Path ref2AbsolutePath(RepositoryFileReference ref) { return this.id2AbsolutePath(ref.getParent()).resolve(ref.getFileName()); } /** * * @param repositoryLocation a string pointing to a location on the file system. May be null. */ public FilebasedRepository(String repositoryLocation) { this.repositoryRoot = this.determineRepositoryPath(repositoryLocation); this.fileSystem = this.repositoryRoot.getFileSystem(); this.provider = this.fileSystem.provider(); } private Path determineRepositoryPath(String repositoryLocation) { Path repositoryPath; if (repositoryLocation == null) { if (SystemUtils.IS_OS_WINDOWS) { if (new File(Constants.GLOBAL_REPO_PATH_WINDOWS).exists()) { repositoryLocation = Constants.GLOBAL_REPO_PATH_WINDOWS; File repo = new File(repositoryLocation); try { org.apache.commons.io.FileUtils.forceMkdir(repo); } catch (IOException e) { FilebasedRepository.logger.error("Could not create repository directory", e); } repositoryPath = repo.toPath(); } else { repositoryPath = this.createDefaultRepositoryPath(); } } else { repositoryPath = this.createDefaultRepositoryPath(); } } else { File repo = new File(repositoryLocation); try { org.apache.commons.io.FileUtils.forceMkdir(repo); } catch (IOException e) { FilebasedRepository.logger.error("Could not create repository directory", e); } repositoryPath = repo.toPath(); } return repositoryPath; } public static File getDefaultRepositoryFilePath() { String userDirPath = System.getProperty("user.dir"); String tomcatRootPath = userDirPath; if (userDirPath.lastIndexOf("tomcat") != -1) { tomcatRootPath = userDirPath.substring(0, userDirPath.lastIndexOf("tomcat") + 6); } File tomcatRootDir = new File(tomcatRootPath); return new File(tomcatRootDir, Constants.DEFAULT_REPO_NAME); } private Path createDefaultRepositoryPath() { File repo = null; boolean operationalFileSystemAccess; try { repo = FilebasedRepository.getDefaultRepositoryFilePath(); operationalFileSystemAccess = true; } catch (NullPointerException e) { // it seems, we run at a system, where we do not have any filesystem // access operationalFileSystemAccess = false; } // operationalFileSystemAccess = false; Path repositoryPath; if (operationalFileSystemAccess) { try { org.apache.commons.io.FileUtils.forceMkdir(repo); } catch (IOException e) { FilebasedRepository.logger.error("Could not create directory", e); } repositoryPath = repo.toPath(); } else { assert (!operationalFileSystemAccess); // we do not have access to the file system throw new IllegalStateException("No write access to file system"); } return repositoryPath; } @Override public void forceDelete(RepositoryFileReference ref) throws IOException { Path relativePath = this.fileSystem.getPath(BackendUtils.getPathInsideRepo(ref)); Path fileToDelete = this.makeAbsolute(relativePath); try { this.provider.delete(fileToDelete); // Quick hack for deletion of the mime type information // Alternative: superclass: protected void // deleteMimeTypeInformation(RepositoryFileReference ref) throws IOException // However, this would again call this method, where we would have to check for the // extension, too. // Therefore, we directly delete the information file Path mimeTypeFile = fileToDelete.getParent().resolve(ref.getFileName() + Constants.SUFFIX_MIMETYPE); this.provider.delete(mimeTypeFile); } catch (IOException e) { if (!(e instanceof NoSuchFileException)) { // only if file did exist and something else went wrong: complain :) // (otherwise, silently ignore the error) FilebasedRepository.logger.debug("Could not delete file", e); throw e; } } } @Override public void forceDelete(GenericId id) throws IOException { try { FileUtils.forceDelete(this.id2AbsolutePath(id)); } catch (IOException e) { FilebasedRepository.logger.debug("Could not delete id", id); throw e; } } @Override public boolean exists(GenericId id) { Path absolutePath = this.id2AbsolutePath(id); boolean result = Files.exists(absolutePath); return result; } /** * {@inheritDoc} */ @Override public void putContentToFile(RepositoryFileReference ref, String content, MediaType mediaType) throws IOException { if (mediaType == null) { // quick hack for storing mime type called this method assert (ref.getFileName().endsWith(Constants.SUFFIX_MIMETYPE)); // we do not need to store the mime type of the file containing the mime type // information } else { this.setMimeType(ref, mediaType); } Path path = this.ref2AbsolutePath(ref); FileUtils.createDirectory(path.getParent()); Files.write(path, content.getBytes()); } /** * {@inheritDoc} */ @Override public void putContentToFile(RepositoryFileReference ref, InputStream inputStream, MediaType mediaType) throws IOException { if (mediaType == null) { // quick hack for storing mime type called this method assert (ref.getFileName().endsWith(Constants.SUFFIX_MIMETYPE)); // we do not need to store the mime type of the file containing the mime type // information } else { this.setMimeType(ref, mediaType); } Path targetPath = this.ref2AbsolutePath(ref); // ensure that parent directory exists FileUtils.createDirectory(targetPath.getParent()); try { Files.copy(inputStream, targetPath, StandardCopyOption.REPLACE_EXISTING); } catch (IllegalStateException e) { FilebasedRepository.logger.debug("Guessing that stream with length 0 is to be written to a file", e); // copy throws an "java.lang.IllegalStateException: Stream already closed" if the // InputStream contains 0 bytes // For instance, this case happens if SugarCE-6.4.2.zip.removed is tried to be uploaded // We work around the Java7 issue and create an empty file if (Files.exists(targetPath)) { // semantics of putContentToFile: existing content is replaced without notification Files.delete(targetPath); } Files.createFile(targetPath); } } @Override public boolean exists(RepositoryFileReference ref) { return Files.exists(this.ref2AbsolutePath(ref)); } @Override public <T extends TOSCAComponentId> SortedSet<T> getAllTOSCAComponentIds(Class<T> idClass) { SortedSet<T> res = new TreeSet<T>(); String rootPathFragment = Util.getRootPathFragment(idClass); Path dir = this.repositoryRoot.resolve(rootPathFragment); if (!Files.exists(dir)) { // return empty list if no ids are available return res; } assert (Files.isDirectory(dir)); final OnlyNonHiddenDirectories onhdf = new OnlyNonHiddenDirectories(); // list all directories contained in this directory try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir, onhdf)) { for (Path nsP : ds) { // the current path is the namespace Namespace ns = new Namespace(nsP.getFileName().toString(), true); try (DirectoryStream<Path> idDS = Files.newDirectoryStream(nsP, onhdf)) { for (Path idP : idDS) { XMLId xmlId = new XMLId(idP.getFileName().toString(), true); Constructor<T> constructor; try { constructor = idClass.getConstructor(Namespace.class, XMLId.class); } catch (Exception e) { FilebasedRepository.logger.debug("Internal error at determining id constructor", e); // abort everything, return invalid result return res; } T id; try { id = constructor.newInstance(ns, xmlId); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { FilebasedRepository.logger.debug("Internal error at invocation of id constructor", e); // abort everything, return invalid result return res; } res.add(id); } } } } catch (IOException e) { FilebasedRepository.logger.debug("Cannot close ds", e); } return res; } @Override public SortedSet<RepositoryFileReference> getContainedFiles(GenericId id) { Path dir = this.id2AbsolutePath(id); SortedSet<RepositoryFileReference> res = new TreeSet<RepositoryFileReference>(); if (!Files.exists(dir)) { return res; } assert (Files.isDirectory(dir)); // list all directories contained in this directory try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir, new OnlyNonHiddenFiles())) { for (Path p : ds) { RepositoryFileReference ref = new RepositoryFileReference(id, p.getFileName().toString()); res.add(ref); } } catch (IOException e) { FilebasedRepository.logger.debug("Cannot close ds", e); } return res; } @Override public Configuration getConfiguration(RepositoryFileReference ref) { Path path = this.ref2AbsolutePath(ref); PropertiesConfiguration configuration = new PropertiesConfiguration(); if (Files.exists(path)) { try (Reader r = Files.newBufferedReader(path, Charset.defaultCharset())) { configuration.load(r); } catch (ConfigurationException | IOException e) { FilebasedRepository.logger.error("Could not read config file", e); throw new IllegalStateException("Could not read config file", e); } } configuration.addConfigurationListener(new AutoSaveListener(path, configuration)); // We do NOT implement reloading as the configuration is only accessed // in JAX-RS resources, which are created on a per-request basis return configuration; } /** * @return null if an error occurred */ @Override public Date getLastUpdate(RepositoryFileReference ref) { Path path = this.ref2AbsolutePath(ref); Date res; if (Files.exists(path)) { FileTime lastModifiedTime; try { lastModifiedTime = Files.getLastModifiedTime(path); res = new Date(lastModifiedTime.toMillis()); } catch (IOException e) { FilebasedRepository.logger.debug(e.getMessage(), e); res = null; } } else { // this branch is taken if the resource directory exists, but the // configuration itself does not exist. // For instance, this happens if icons are manually put for a node // type, but no color configuration is made. res = Constants.LASTMODIFIEDDATE_FOR_404; } return res; } @Override public <T extends TOSCAElementId> SortedSet<T> getNestedIds(GenericId ref, Class<T> idClass) { Path dir = this.id2AbsolutePath(ref); SortedSet<T> res = new TreeSet<T>(); if (!Files.exists(dir)) { // the id has been generated by the exporter without existance test. // This test is done here. return res; } assert (Files.isDirectory(dir)); // list all directories contained in this directory try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir, new OnlyNonHiddenDirectories())) { for (Path p : ds) { XMLId xmlId = new XMLId(p.getFileName().toString(), true); @SuppressWarnings("unchecked") Constructor<T>[] constructors = (Constructor<T>[]) idClass.getConstructors(); assert (constructors.length == 1); Constructor<T> constructor = constructors[0]; assert (constructor.getParameterTypes().length == 2); T id; try { id = constructor.newInstance(ref, xmlId); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { FilebasedRepository.logger.debug("Internal error at invocation of id constructor", e); // abort everything, return invalid result return res; } res.add(id); } } catch (IOException e) { FilebasedRepository.logger.debug("Cannot close ds", e); } return res; } @Override // below, toscaComponents is an array, which is used in an iterator // As Java does not allow generic arrays, we have to suppress the warning when fetching an // element out of the list @SuppressWarnings("unchecked") public Collection<Namespace> getUsedNamespaces() { // @formatter:off @SuppressWarnings("rawtypes") Class[] toscaComponentIds = { ArtifactTemplateId.class, ArtifactTypeId.class, CapabilityTypeId.class, NodeTypeId.class, NodeTypeImplementationId.class, PolicyTemplateId.class, PolicyTypeId.class, RelationshipTypeId.class, RelationshipTypeImplementationId.class, RequirementTypeId.class, ServiceTemplateId.class }; // @formatter:on // we use a HashSet to avoid reporting duplicate namespaces Collection<Namespace> res = new HashSet<Namespace>(); for (Class<? extends TOSCAComponentId> id : toscaComponentIds) { String rootPathFragment = Util.getRootPathFragment(id); Path dir = this.repositoryRoot.resolve(rootPathFragment); if (!Files.exists(dir)) { continue; } assert (Files.isDirectory(dir)); final OnlyNonHiddenDirectories onhdf = new OnlyNonHiddenDirectories(); // list all directories contained in this directory try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir, onhdf)) { for (Path nsP : ds) { // the current path is the namespace Namespace ns = new Namespace(nsP.getFileName().toString(), true); res.add(ns); } } catch (IOException e) { FilebasedRepository.logger.debug("Cannot close ds", e); } } return res; } @Override public void doDump(OutputStream out) throws IOException { final ZipOutputStream zout = new ZipOutputStream(out); final int cutLength = this.repositoryRoot.toString().length() + 1; Files.walkFileTree(this.repositoryRoot, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { if (dir.endsWith(".git")) { return FileVisitResult.SKIP_SUBTREE; } else { return FileVisitResult.CONTINUE; } } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { String name = file.toString().substring(cutLength); ZipEntry ze = new ZipEntry(name); try { ze.setTime(Files.getLastModifiedTime(file).toMillis()); ze.setSize(Files.size(file)); zout.putNextEntry(ze); Files.copy(file, zout); zout.closeEntry(); } catch (IOException e) { FilebasedRepository.logger.debug(e.getMessage()); } return FileVisitResult.CONTINUE; } }); zout.close(); } /** * Removes all files and dirs except the .git directory */ @Override public void doClear() { try { DirectoryStream.Filter<Path> noGitDirFilter = new DirectoryStream.Filter<Path>() { @Override public boolean accept(Path entry) throws IOException { return !(entry.getFileName().toString().equals(".git")); } }; DirectoryStream<Path> ds = Files.newDirectoryStream(this.repositoryRoot, noGitDirFilter); for (Path p : ds) { FileUtils.forceDelete(p); } } catch (IOException e) { FilebasedRepository.logger.error(e.getMessage()); e.printStackTrace(); } } @Override public void doImport(InputStream in) { ZipInputStream zis = new ZipInputStream(in); ZipEntry entry; try { while ((entry = zis.getNextEntry()) != null) { if (!entry.isDirectory()) { Path path = this.repositoryRoot.resolve(entry.getName()); FileUtils.createDirectory(path.getParent()); Files.copy(zis, path); } } } catch (IOException e) { FilebasedRepository.logger.error(e.getMessage()); } } /** * {@inheritDoc} */ @Override public long getSize(RepositoryFileReference ref) throws IOException { return Files.size(this.ref2AbsolutePath(ref)); } /** * {@inheritDoc} */ @Override public FileTime getLastModifiedTime(RepositoryFileReference ref) throws IOException { Path path = this.ref2AbsolutePath(ref); FileTime res = Files.getLastModifiedTime(path); return res; } /** * {@inheritDoc} */ @Override public InputStream newInputStream(RepositoryFileReference ref) throws IOException { Path path = this.ref2AbsolutePath(ref); InputStream res = Files.newInputStream(path); return res; } @Override public String getRepositoryRootPath() { String path = repositoryRoot.toString(); String os = System.getProperties().getProperty("os.name"); String result; if (os != null && os.startsWith("Windows")) { result = path.replaceAll("\\\\", "/"); } else { result = "/" + path; } return result; } @Override public String getRelativePath(RepositoryFileReference ref) { Path path = this.fileSystem.getPath(BackendUtils.getPathInsideRepo(ref)); Path relativePath = this.makeAbsolute(path); return relativePath.toString(); } @Override public SortedSet<FileInfo> getAllContainedFiles(GenericId id) { Path dir = this.id2AbsolutePath(id); SortedSet<FileInfo> res = new TreeSet<FileInfo>(); getFiles(dir, "", res); return res; } private static void getFiles(Path dir, String path, SortedSet<FileInfo> res) { if (!Files.exists(dir)) { return; } assert (Files.isDirectory(dir)); try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) { for (Path p : ds) { if (Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) { getFiles(p, path + "/" + p.getFileName(), res); } else if (!p.getFileName().toString().endsWith(".mimetype")) { res.add(new FileInfo(path, p.getFileName().toString())); } } } catch (IOException e) { FilebasedRepository.logger.debug("Cannot close ds", e); } } }