/*
# Copyright (c) 2007-2013 Cyrus Daboo. All rights reserved.
#
# 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.bedework.dumprestore;
import org.bedework.calfacade.BwPrincipal;
import org.bedework.calfacade.base.BwDbentity;
import org.bedework.calfacade.exc.CalFacadeException;
import org.bedework.util.misc.Logged;
import org.bedework.util.misc.Util;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.file.CopyOption;
import java.nio.file.FileSystemLoopException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Formatter;
import java.util.Properties;
import java.util.function.Function;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
@SuppressWarnings("WeakerAccess")
public class Utils extends Logged {
public Utils() {
}
/** for principal "/principals/user/mike/" will return <br/>
* /m/principals/user/mike <br/>
*
* <p>The directory with the name "m" is the first lowercased character
* of the principal if in the set 0-9a-z. Anything else goes in "_". This
* is all to reduce the number of files in directories so it is navigable</p>
*
* @param pr the principal
* @return a directory path in which to dump all the data
*/
public static String principalDirPath(final BwPrincipal pr) {
String s;
final String account = pr.getAccount();
if (account.startsWith("agrp_")) {
return Util.buildPath(true, "/public/" +
Defs.adminGroupsDirName +
"/", account);
}
if (account.length() == 0) {
s = "_";
} else {
s = account.toLowerCase().substring(0, 1);
final char c = s.charAt(0);
if (!Character.isLetterOrDigit(c)) {
s = "_";
}
}
return Util.buildPath(true, "/", s, pr.getPrincipalRef());
}
public Path createFile(final String path) throws CalFacadeException {
try {
final Path pathToFile = Paths.get(path);
Files.createDirectories(pathToFile.getParent());
return Files.createFile(pathToFile);
} catch (final Throwable t) {
throw new CalFacadeException(t);
}
}
public boolean empty(final String path) {
return delete(new File(path), false);
}
public static boolean makeDir(final String path) throws CalFacadeException {
final File f = new File(path);
if (!f.exists()) {
return f.mkdir();
}
if (!f.isDirectory()) {
throw new CalFacadeException(f.getAbsolutePath() +
" must be a directory");
}
return false;
}
public static File directory(final String path) throws CalFacadeException {
final File f = new File(path);
if (!f.exists()) {
return null;
}
if (!f.isDirectory()) {
throw new CalFacadeException(f.getAbsolutePath() +
" must be a directory");
}
return f;
}
public File subDirectory(final String path,
final String name) throws Throwable {
final Path p = Paths.get(path, name);
final File f = p.toFile();
if (!f.exists() || !f.isDirectory()) {
throw new Exception(name + " in " +
path +
" must exist and be a directory");
}
return f;
}
public File subDirectory(final File f,
final String name,
final boolean mustExist) throws Throwable {
final File dir = new File(f.getAbsolutePath(), name);
if (dir.exists() && !dir.isDirectory()) {
throw new Exception(name + " in " +
f.getAbsolutePath() +
" must be a directory");
}
if (!dir.exists() && mustExist) {
throw new Exception(name + " in " +
f.getAbsolutePath() +
" must exist and be a directory");
}
return dir;
}
public static File file(final File dir,
final String name,
final boolean mustExist) throws CalFacadeException {
final File f = new File(dir.getAbsolutePath(), name);
if (f.exists() && !f.isFile()) {
throw new CalFacadeException(name + " in " +
f.getAbsolutePath() +
" must be a file");
}
if (!f.exists() && mustExist) {
throw new CalFacadeException(name + " in " +
f.getAbsolutePath() +
" must exist and be a file");
}
return f;
}
public File fileOrDir(final File dir,
final String name) throws Throwable {
final File f = new File(dir.getAbsolutePath(), name);
if (!f.exists()) {
throw new Exception(name + " in " +
f.getAbsolutePath() +
" must exist");
}
return dir;
}
public static File file(final String path) throws CalFacadeException {
final File f = new File(path);
if (!f.exists() || !f.isFile()) {
throw new CalFacadeException(path + " must exist and be a file");
}
return f;
}
/** Parse a reader and return the DOM representation.
*
* @param rdr Reader
* @param nameSpaced true if this document has namespaces
* @return Document Parsed body or null for no body
* @exception CalFacadeException Some error occurred.
*/
public static Document parseXml(final Reader rdr,
final boolean nameSpaced) throws CalFacadeException {
if (rdr == null) {
// No content?
return null;
}
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(nameSpaced);
try {
final DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(new InputSource(rdr));
} catch (final Throwable t) {
throw new CalFacadeException(t);
}
}
/** If it's a file - delete it.
* If it's a directory delete the contents and if deleteThis is true
* delete the directory as well.
*
* @param file file/dir
* @param deleteThis true to delete directory
* @return true if something deleted
*/
public boolean delete(final File file,
final boolean deleteThis) {
final File[] flist;
if(file == null){
return false;
}
if (file.isFile()) {
return file.delete();
}
if (!file.isDirectory()) {
return false;
}
flist = file.listFiles();
if (flist != null && flist.length > 0) {
for (final File f : flist) {
if (!delete(f, true)) {
return false;
}
}
}
if (!deleteThis) {
return true;
}
return file.delete();
}
private final static CopyOption[] copyOptionAttributes =
new CopyOption[] { REPLACE_EXISTING, COPY_ATTRIBUTES };
/**
* A {@code FileVisitor} that copies a file-tree ("cp -r")
*/
class DirRestore<T extends BwDbentity> implements FileVisitor<Path> {
private final Path in;
private final Function<InputStream, T> restore;
DirRestore(final Path in,
final Function<InputStream, T> restore) {
this.in = in;
this.restore = restore;
}
@Override
public FileVisitResult preVisitDirectory(final Path dir,
final BasicFileAttributes attrs) {
// before visiting entries in a directory we copy the directory
return CONTINUE;
}
@Override
public FileVisitResult visitFile(final Path file,
final BasicFileAttributes attrs) {
//Utils.debug("**** Copy file " + file);
final File f = file.toFile();
final InputStream is;
try {
is = new FileInputStream(f);
} catch (final FileNotFoundException fnfe) {
warn("File not found: " + file);
return CONTINUE;
}
restore.apply(is);
return CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(final Path dir,
final IOException exc) {
return CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(final Path file,
final IOException exc) {
if (exc instanceof FileSystemLoopException) {
error("cycle detected: " + file);
} else {
error("Unable to copy: " + file + "; " + exc);
}
return CONTINUE;
}
}
public class DeletingFileVisitor extends SimpleFileVisitor<Path> {
@Override
public FileVisitResult visitFile(final Path file,
final BasicFileAttributes attributes)
throws IOException {
if(attributes.isRegularFile()) {
Files.delete(file);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(final Path directory,
final IOException ioe)
throws IOException {
Files.delete(directory);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(final Path file,
final IOException ioe)
throws IOException {
error("Unable to delete: " + file +
": " + ioe);
return FileVisitResult.CONTINUE;
}
}
public void deleteAll(final Path dir) throws Throwable {
final DeletingFileVisitor delFileVisitor = new DeletingFileVisitor();
Files.walkFileTree(dir, delFileVisitor);
}
/** Return a Properties object containing all those properties that
* match the given prefix. The property name will have the prefix
* replaced by the new prefix.
*
* @param props source properties
* @param prefix to match
* @param newPrefix replacement
* @return never null
*/
public static Properties filter(final Properties props,
final String prefix,
final String newPrefix) {
final Properties res = new Properties();
for (final String pname: props.stringPropertyNames()) {
if (pname.startsWith(prefix)) {
res.setProperty(newPrefix + pname.substring(prefix.length()),
props.getProperty(pname));
}
}
return res;
}
void print(final String fmt,
final Object... params) {
final Formatter f = new Formatter();
info(f.format(fmt, params).toString());
}
void assertion(final boolean test,
final String fmt,
final Object... params) {
if (test) {
return;
}
final Formatter f = new Formatter();
throw new RuntimeException(f.format(fmt, params).toString());
}
int getInt(final String val) {
try {
return Integer.valueOf(val);
} catch (final Throwable ignored) {
throw new RuntimeException("Failed to parse as Integer " + val);
}
}
}