aboutsummaryrefslogtreecommitdiffstats
path: root/utils/src/main/java/org/onap/policy/common/utils/resources
diff options
context:
space:
mode:
Diffstat (limited to 'utils/src/main/java/org/onap/policy/common/utils/resources')
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/resources/DirectoryUtils.java63
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/resources/MessageConstants.java43
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/resources/PrometheusUtils.java107
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/resources/ResourceUtils.java105
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/resources/TextFileUtils.java68
5 files changed, 309 insertions, 77 deletions
diff --git a/utils/src/main/java/org/onap/policy/common/utils/resources/DirectoryUtils.java b/utils/src/main/java/org/onap/policy/common/utils/resources/DirectoryUtils.java
new file mode 100644
index 00000000..35a13138
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/resources/DirectoryUtils.java
@@ -0,0 +1,63 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.resources;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utilities for manipulating directories.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class DirectoryUtils {
+ private static final Logger logger = LoggerFactory.getLogger(DirectoryUtils.class);
+
+ /**
+ * Creates a directory file, only accessible by the owner.
+ *
+ * @param prefix file name prefix
+ * @return a new, temporary directory
+ * @throws IOException if an error occurs
+ */
+ public static Path createTempDirectory(String prefix) throws IOException {
+ /*
+ * Disabling sonar, as the code below sets the permissions, just as sonar
+ * suggests it be fixed.
+ */
+ var path = Files.createTempDirectory(prefix); // NOSONAR
+ logger.info("created temporary directory, {}", path);
+
+ var file = path.toFile();
+
+ TextFileUtils.setDefaultPermissions(file);
+
+ // ensure nothing has been written to it yet
+ FileUtils.cleanDirectory(file);
+
+ return path;
+ }
+}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/resources/MessageConstants.java b/utils/src/main/java/org/onap/policy/common/utils/resources/MessageConstants.java
new file mode 100644
index 00000000..ef46fdf5
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/resources/MessageConstants.java
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.resources;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * Common messages to be used by all components.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class MessageConstants {
+
+ public static final String POLICY_API = "policy-api";
+ public static final String POLICY_PAP = "policy-pap";
+ public static final String POLICY_APEX_PDP = "policy-apex-pdp";
+ public static final String POLICY_DROOLS_PDP = "policy-drools-pdp";
+ public static final String POLICY_XACML_PDP = "policy-xacml-pdp";
+ public static final String POLICY_DISTRIBUTION = "policy-distribution";
+ public static final String POLICY_CLAMP = "policy-clamp";
+
+ public static final String START_SUCCESS_MSG = "Started %s service successfully.";
+ public static final String START_FAILURE_MSG = "Start of %s service failed.";
+}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/resources/PrometheusUtils.java b/utils/src/main/java/org/onap/policy/common/utils/resources/PrometheusUtils.java
new file mode 100644
index 00000000..bac65d4e
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/resources/PrometheusUtils.java
@@ -0,0 +1,107 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2022 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2022 Bell Canada. 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.resources;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * Prometheus constants and utilities.
+ */
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class PrometheusUtils {
+
+ /**
+ * Policy Deployments Metric Name.
+ */
+ public static final String POLICY_DEPLOYMENTS_METRIC = "policy_deployments";
+
+ /**
+ * Policy Deployments Metric Help Message.
+ */
+ public static final String POLICY_DEPLOYMENT_HELP = "The total number of policy deployments.";
+
+ /**
+ * Policy Execution Metric Name.
+ */
+ public static final String POLICY_EXECUTION_METRIC = "policy_executions";
+
+ /**
+ * Policy Execution Metric Help Message.
+ */
+ public static final String POLICY_EXECUTION_HELP = "The total number of TOSCA policy executions.";
+
+ /**
+ * Policy Execution Latency in Seconds Metric Name.
+ * This metric name is not to be used as a counter.
+ */
+ public static final String POLICY_EXECUTIONS_LATENCY_SECONDS_METRIC = "policy_executions_latency_seconds";
+
+ /**
+ * Policy Execution Latency in Seconds Metric Help message.
+ */
+ public static final String POLICY_EXECUTIONS_LATENCY_SECONDS_HELP =
+ "The latency in seconds of TOSCA policy executions.";
+
+ /**
+ * Metric label for arbitrary operations (eg. deploy, undeploy, execute).
+ */
+ public static final String OPERATION_METRIC_LABEL = "operation";
+
+ /**
+ * Deploy operation value.
+ */
+ public static final String DEPLOY_OPERATION = "deploy";
+
+ /**
+ * Undeploy operation value.
+ */
+ public static final String UNDEPLOY_OPERATION = "undeploy";
+
+ /**
+ * Metric label for states (ie. PASSIVE, ACTIVE).
+ */
+ public static final String STATE_METRIC_LABEL = "state";
+
+ /**
+ * Metric label for status of an operation (ie. SUCCESS or FAILURE).
+ */
+ public static final String STATUS_METRIC_LABEL = "status";
+
+ /**
+ * Prometheus namespace values mapping to the supported PDP types.
+ */
+ public enum PdpType {
+ PDPD("pdpd"),
+ PDPA("pdpa"),
+ PDPX("pdpx");
+
+ @Getter
+ private final String namespace;
+
+ PdpType(String namespace) {
+ this.namespace = namespace;
+ }
+ }
+} \ No newline at end of file
diff --git a/utils/src/main/java/org/onap/policy/common/utils/resources/ResourceUtils.java b/utils/src/main/java/org/onap/policy/common/utils/resources/ResourceUtils.java
index 365efabe..3ee062f1 100644
--- a/utils/src/main/java/org/onap/policy/common/utils/resources/ResourceUtils.java
+++ b/utils/src/main/java/org/onap/policy/common/utils/resources/ResourceUtils.java
@@ -1,7 +1,8 @@
/*-
* ============LICENSE_START=======================================================
* Copyright (C) 2018 Ericsson. All rights reserved.
- * Modifications Copyright (C) 2020 Nordix Foundation.
+ * Modifications Copyright (C) 2020, 2023 Nordix Foundation.
+ * Modifications Copyright (C) 2020-2021 AT&T Intellectual Property. 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.
@@ -21,18 +22,22 @@
package org.onap.policy.common.utils.resources;
-import java.io.ByteArrayOutputStream;
+import com.google.re2j.Pattern;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Enumeration;
+import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
-
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,25 +45,18 @@ import org.slf4j.LoggerFactory;
* This is common utility class with static methods for handling Java resources on the class path. It is an abstract
* class to prevent any direct instantiation and private constructor to prevent extending this class.
*/
-public abstract class ResourceUtils {
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class ResourceUtils {
// Get a reference to the logger
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceUtils.class);
- // The length of byte buffers used to read resources into strings
- private static final int BYTE_BUFFER_LENGH = 1024;
+ private static final Pattern SLASH_PAT = Pattern.compile("/");
// Resource types
private static final String FILE_PROTOCOL = "file";
private static final String JAR_PROTOCOL = "jar";
/**
- * Private constructor used to prevent sub class instantiation.
- */
- private ResourceUtils() {
- // Prevent construction of this class
- }
-
- /**
* Method to resolve a resource; the local file system is checked first and then the class path is checked.
*
* @param resourceName The resource name
@@ -66,7 +64,7 @@ public abstract class ResourceUtils {
*/
public static URL getUrl4Resource(final String resourceName) {
// Check the local fine system first
- final URL urlToResource = getLocalFile(resourceName);
+ final var urlToResource = getLocalFile(resourceName);
// Check if this is a local file
if (urlToResource != null) {
@@ -86,25 +84,16 @@ public abstract class ResourceUtils {
*/
public static String getResourceAsString(final String resourceName) {
// Get the resource as a stream, we'll convert it to a string then
- final InputStream resourceStream = getResourceAsStream(resourceName);
- if (resourceStream == null) {
- return null;
- }
-
- // Read the stream contents in to an output stream
- final ByteArrayOutputStream resourceOutputStreamBuffer = new ByteArrayOutputStream();
- final byte[] resourceBuffer = new byte[BYTE_BUFFER_LENGH];
- int length;
- try {
- while ((length = resourceStream.read(resourceBuffer)) != -1) {
- resourceOutputStreamBuffer.write(resourceBuffer, 0, length);
+ // Read the stream contents, closing when done
+ try (var resourceStream = getResourceAsStream(resourceName)) {
+ if (resourceStream == null) {
+ return null;
}
+ return IOUtils.toString(resourceStream, StandardCharsets.UTF_8);
} catch (final IOException e) {
- LOGGER.debug("error reading resource stream \"{}\" : " + e.getMessage(), resourceName, e);
+ LOGGER.debug("error reading resource stream {}", resourceName, e);
return null;
}
-
- return resourceOutputStreamBuffer.toString();
}
/**
@@ -116,12 +105,12 @@ public abstract class ResourceUtils {
*/
public static InputStream getResourceAsStream(final String resourceName) {
// Find a URL to the resource first
- final URL urlToResource = getUrl4Resource(resourceName);
+ final var urlToResource = getUrl4Resource(resourceName);
// Check if the resource exists
if (urlToResource == null) {
// No resource found
- LOGGER.debug("cound not find resource \"{}\" : ", resourceName);
+ LOGGER.debug("could not find resource \"{}\" : ", resourceName);
return null;
}
@@ -130,7 +119,7 @@ public abstract class ResourceUtils {
return urlToResource.openStream();
} catch (final IOException e) {
// Any of many IO exceptions such as the resource is a directory
- LOGGER.debug("error attaching resource \"{}\" to stream : " + e.getMessage(), resourceName, e);
+ LOGGER.debug("error attaching resource {}", resourceName, e);
return null;
}
}
@@ -143,11 +132,11 @@ public abstract class ResourceUtils {
*/
public static URL getUrlResource(final String resourceName) {
try {
- final ClassLoader classLoader = ResourceUtils.class.getClassLoader();
+ final var classLoader = ResourceUtils.class.getClassLoader();
- final String[] fileParts = resourceName.split("/");
+ final String[] fileParts = SLASH_PAT.split(resourceName);
// Read the resource
- URL url = classLoader.getResource(resourceName);
+ var url = classLoader.getResource(resourceName);
// Check if the resource is defined
if (url != null) {
@@ -164,7 +153,7 @@ public abstract class ResourceUtils {
return url;
}
} catch (final Exception e) {
- LOGGER.debug("error getting URL resource \"{}\" : " + e.getMessage(), e);
+ LOGGER.debug("error getting URL resource {}", resourceName, e);
return null;
}
}
@@ -178,8 +167,8 @@ public abstract class ResourceUtils {
public static URL getLocalFile(final String resourceName) {
try {
// Input might already be in URL format
- final URL ret = new URL(resourceName);
- final File f = new File(ret.toURI());
+ final var ret = new URL(resourceName);
+ final var f = new File(ret.toURI());
if (f.exists()) {
return ret;
}
@@ -188,10 +177,10 @@ public abstract class ResourceUtils {
}
try {
- final File f = new File(resourceName);
+ final var f = new File(resourceName);
// Check if the file exists
if (f.exists()) {
- final URL urlret = f.toURI().toURL();
+ final var urlret = f.toURI().toURL();
LOGGER.debug("resource \"{}\" was found on the local file system", f.toURI().toURL());
return urlret;
} else {
@@ -199,7 +188,7 @@ public abstract class ResourceUtils {
return null;
}
} catch (final Exception e) {
- LOGGER.debug("error finding resource \"{}\" : " + e.getMessage(), e);
+ LOGGER.debug("error finding resource {}", resourceName, e);
return null;
}
}
@@ -215,7 +204,7 @@ public abstract class ResourceUtils {
return null;
}
- URL modelFileUrl = getUrl4Resource(resource);
+ var modelFileUrl = getUrl4Resource(resource);
if (modelFileUrl != null) {
return modelFileUrl.getPath();
} else {
@@ -227,11 +216,11 @@ public abstract class ResourceUtils {
* Read the list of entries in a resource directory.
*
* @param resourceDirectoryName the name of the resource directory
- * @return the list of entries
+ * @return a set of entries
*/
public static Set<String> getDirectoryContents(final String resourceDirectoryName) {
// Find the location of the resource, is it in a Jar or on the local file system?
- URL directoryUrl = ResourceUtils.getUrl4Resource(resourceDirectoryName);
+ var directoryUrl = ResourceUtils.getUrl4Resource(resourceDirectoryName);
if (directoryUrl == null) {
LOGGER.debug("resource \"{}\" was not found", resourceDirectoryName);
@@ -255,11 +244,11 @@ public abstract class ResourceUtils {
*
* @param localResourceDirectoryUrl the local resource file URL
* @param resourceDirectoryName the name of the resource directory
- * @return a list of the directory contents
+ * @return a set of the directory contents
*/
public static Set<String> getDirectoryContentsLocal(final URL localResourceDirectoryUrl,
final String resourceDirectoryName) {
- File localDirectory = new File(localResourceDirectoryUrl.getFile());
+ var localDirectory = new File(localResourceDirectoryUrl.getFile());
if (!localDirectory.isDirectory()) {
LOGGER.debug("resource \"{}\" is not a directory", resourceDirectoryName);
@@ -267,7 +256,7 @@ public abstract class ResourceUtils {
}
Set<String> localDirectorySet = new TreeSet<>();
- for (File localDirectoryEntry : localDirectory.listFiles()) {
+ for (File localDirectoryEntry : Objects.requireNonNull(localDirectory.listFiles())) {
if (localDirectoryEntry.isDirectory()) {
localDirectorySet
.add(resourceDirectoryName + File.separator + localDirectoryEntry.getName() + File.separator);
@@ -284,23 +273,29 @@ public abstract class ResourceUtils {
*
* @param jarResourceDirectoryUrl the name of the resource directory in the jar
* @param resourceDirectoryName the name of the resource directory
- * @return a list of the directory contents
+ * @return a set of the directory contents
*/
public static Set<String> getDirectoryContentsJar(final URL jarResourceDirectoryUrl,
final String resourceDirectoryName) {
- File jarResourceDirectory = new File(jarResourceDirectoryUrl.getPath());
+ String dirNameWithSlash = resourceDirectoryName + "/";
+ int minLength = dirNameWithSlash.length() + 1;
+ var jarResourceDirectory = new File(jarResourceDirectoryUrl.getPath());
String jarFileName = jarResourceDirectory.getParent().replaceFirst("^file:", "").replaceFirst("!.*$", "");
Set<String> localDirectorySet = new TreeSet<>();
- try (JarFile jarFile = new JarFile(jarFileName)) {
- Enumeration<JarEntry> entries = jarFile.entries();
+ try (var jarFile = new JarFile(jarFileName)) {
+ Enumeration<JarEntry> entries = jarFile.entries(); // NOSONAR
while (entries.hasMoreElements()) {
- JarEntry je = entries.nextElement();
-
- if (je.getName().matches("^" + resourceDirectoryName + "\\/.+")) {
- localDirectorySet.add(je.getName());
+ /*
+ * Ignore sonar issue, as the entries are not being expanded here.
+ */
+ JarEntry je = entries.nextElement(); // NOSONAR
+ String jeName = je.getName();
+
+ if (jeName.length() >= minLength && jeName.startsWith(dirNameWithSlash)) {
+ localDirectorySet.add(jeName);
}
}
} catch (IOException ioe) {
diff --git a/utils/src/main/java/org/onap/policy/common/utils/resources/TextFileUtils.java b/utils/src/main/java/org/onap/policy/common/utils/resources/TextFileUtils.java
index 01af7fd8..7701eae9 100644
--- a/utils/src/main/java/org/onap/policy/common/utils/resources/TextFileUtils.java
+++ b/utils/src/main/java/org/onap/policy/common/utils/resources/TextFileUtils.java
@@ -1,6 +1,7 @@
/*-
* ============LICENSE_START=======================================================
* Copyright (C) 2019 Nordix Foundation.
+ * Modifications Copyright (C) 2020-2021 AT&T Intellectual Property. 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.
@@ -25,7 +26,13 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* The Class TextFileUtils is class that provides useful functions for handling text files. Functions to read and write
@@ -33,13 +40,9 @@ import java.nio.file.Files;
*
* @author Liam Fallon (liam.fallon@est.tech)
*/
-public abstract class TextFileUtils {
- private static final String UTF_8 = "UTF-8";
- private static final int READER_CHAR_BUFFER_SIZE_4096 = 4096;
-
- private TextFileUtils() {
- // This class cannot be initialized
- }
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class TextFileUtils {
+ private static final Logger logger = LoggerFactory.getLogger(TextFileUtils.class);
/**
* Method to return the contents of a text file as a string.
@@ -49,8 +52,8 @@ public abstract class TextFileUtils {
* @throws IOException on errors reading text from the file
*/
public static String getTextFileAsString(final String textFilePath) throws IOException {
- final File textFile = new File(textFilePath);
- return new String(Files.readAllBytes(textFile.toPath()), UTF_8);
+ final var textFile = new File(textFilePath);
+ return Files.readString(textFile.toPath());
}
/**
@@ -61,7 +64,7 @@ public abstract class TextFileUtils {
* @throws IOException on errors reading text from the file
*/
public static void putStringAsTextFile(final String outString, final String textFilePath) throws IOException {
- final File textFile = new File(textFilePath);
+ final var textFile = new File(textFilePath);
if (!textFile.getParentFile().exists()) {
textFile.getParentFile().mkdirs();
}
@@ -77,7 +80,7 @@ public abstract class TextFileUtils {
* @throws IOException on errors reading text from the file
*/
public static void putStringAsFile(final String outString, final File textFile) throws IOException {
- Files.write(textFile.toPath(), outString.getBytes(UTF_8));
+ Files.writeString(textFile.toPath(), outString);
}
/**
@@ -88,7 +91,7 @@ public abstract class TextFileUtils {
* @throws IOException on errors reading text from the file
*/
public static String getStreamAsString(final InputStream textStream) throws IOException {
- return getReaderAsString(new InputStreamReader(textStream, UTF_8));
+ return getReaderAsString(new InputStreamReader(textStream, StandardCharsets.UTF_8));
}
/**
@@ -99,16 +102,37 @@ public abstract class TextFileUtils {
* @throws IOException on errors reading text from the file
*/
public static String getReaderAsString(final Reader textReader) throws IOException {
- final StringBuilder builder = new StringBuilder();
- int charsRead = -1;
- final char[] chars = new char[READER_CHAR_BUFFER_SIZE_4096];
- do {
- charsRead = textReader.read(chars);
- if (charsRead > 0) {
- builder.append(chars, 0, charsRead);
- }
+ return IOUtils.toString(textReader);
+ }
+
+ /**
+ * Creates a temporary file, only accessible by the owner.
+ *
+ * @param prefix file name prefix
+ * @param suffix file name suffix
+ * @return a new, temporary file
+ * @throws IOException if an error occurs
+ */
+ public static File createTempFile(String prefix, String suffix) throws IOException {
+ /*
+ * Disabling sonar, because setDefaultPermissions() will set the permissions of
+ * the file.
+ */
+ var file = File.createTempFile(prefix, suffix); // NOSONAR
+
+ setDefaultPermissions(file);
+
+ return file;
+ }
+
+ /**
+ * Sets permissions on a file or directory so that only the owner can access it.
+ *
+ * @param file file or directory on which permissions are to be set
+ */
+ public static void setDefaultPermissions(File file) {
+ if (!file.setReadable(true, true) || !file.setWritable(true, true) || !file.setExecutable(true, true)) {
+ logger.warn("cannot set permissions for {}", file);
}
- while (charsRead > 0);
- return builder.toString();
}
}