You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by kd...@apache.org on 2018/09/22 02:11:17 UTC
[18/51] [partial] nifi-registry git commit: NIFIREG-201 Refactoring
project structure to better isolate extensions
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FileUtils.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FileUtils.java b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FileUtils.java
new file mode 100644
index 0000000..5abaf7e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FileUtils.java
@@ -0,0 +1,426 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.nifi.registry.util;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.slf4j.Logger;
+
+/**
+ * A utility class containing a few useful static methods to do typical IO
+ * operations.
+ *
+ */
+public class FileUtils {
+
+ public static final long TRANSFER_CHUNK_SIZE_BYTES = 1024 * 1024 * 8; //8 MB chunks
+ public static final long MILLIS_BETWEEN_ATTEMPTS = 50L;
+
+ /**
+ * Closes the given closeable quietly - no logging, no exceptions...
+ *
+ * @param closeable the thing to close
+ */
+ public static void closeQuietly(final Closeable closeable) {
+ if (null != closeable) {
+ try {
+ closeable.close();
+ } catch (final IOException io) {/*IGNORE*/
+
+ }
+ }
+ }
+
+ /**
+ * Releases the given lock quietly no logging, no exception
+ *
+ * @param lock the lock to release
+ */
+ public static void releaseQuietly(final FileLock lock) {
+ if (null != lock) {
+ try {
+ lock.release();
+ } catch (final IOException io) {
+ /*IGNORE*/
+ }
+ }
+ }
+
+ /* Superseded by renamed class bellow */
+ @Deprecated
+ public static void ensureDirectoryExistAndCanAccess(final File dir) throws IOException {
+ ensureDirectoryExistAndCanReadAndWrite(dir);
+ }
+
+ public static void ensureDirectoryExistAndCanReadAndWrite(final File dir) throws IOException {
+ if (dir.exists() && !dir.isDirectory()) {
+ throw new IOException(dir.getAbsolutePath() + " is not a directory");
+ } else if (!dir.exists()) {
+ final boolean made = dir.mkdirs();
+ if (!made) {
+ throw new IOException(dir.getAbsolutePath() + " could not be created");
+ }
+ }
+ if (!(dir.canRead() && dir.canWrite())) {
+ throw new IOException(dir.getAbsolutePath() + " directory does not have read/write privilege");
+ }
+ }
+
+ public static void ensureDirectoryExistAndCanRead(final File dir) throws IOException {
+ if (dir.exists() && !dir.isDirectory()) {
+ throw new IOException(dir.getAbsolutePath() + " is not a directory");
+ } else if (!dir.exists()) {
+ final boolean made = dir.mkdirs();
+ if (!made) {
+ throw new IOException(dir.getAbsolutePath() + " could not be created");
+ }
+ }
+ if (!dir.canRead()) {
+ throw new IOException(dir.getAbsolutePath() + " directory does not have read privilege");
+ }
+ }
+
+ /**
+ * Copies the given source file to the given destination file. The given destination will be overwritten if it already exists.
+ *
+ * @param source the file to copy
+ * @param destination the file to copy to
+ * @param lockInputFile if true will lock input file during copy; if false will not
+ * @param lockOutputFile if true will lock output file during copy; if false will not
+ * @param move if true will perform what is effectively a move operation rather than a pure copy. This allows for potentially highly efficient movement of the file but if not possible this will
+ * revert to a copy then delete behavior. If false, then the file is copied and the source file is retained. If a true rename/move occurs then no lock is held during that time.
+ * @param logger if failures occur, they will be logged to this logger if possible. If this logger is null, an IOException will instead be thrown, indicating the problem.
+ * @return long number of bytes copied
+ * @throws FileNotFoundException if the source file could not be found
+ * @throws IOException if unable to read or write the underlying streams
+ * @throws SecurityException if a security manager denies the needed file operations
+ */
+ public static long copyFile(final File source, final File destination, final boolean lockInputFile, final boolean lockOutputFile, final boolean move, final Logger logger)
+ throws FileNotFoundException, IOException {
+
+ FileInputStream fis = null;
+ FileOutputStream fos = null;
+ FileLock inLock = null;
+ FileLock outLock = null;
+ long fileSize = 0L;
+ if (!source.canRead()) {
+ throw new IOException("Must at least have read permission");
+
+ }
+ if (move && source.renameTo(destination)) {
+ fileSize = destination.length();
+ } else {
+ try {
+ fis = new FileInputStream(source);
+ fos = new FileOutputStream(destination);
+ final FileChannel in = fis.getChannel();
+ final FileChannel out = fos.getChannel();
+ if (lockInputFile) {
+ inLock = in.tryLock(0, Long.MAX_VALUE, true);
+ if (null == inLock) {
+ throw new IOException("Unable to obtain shared file lock for: " + source.getAbsolutePath());
+ }
+ }
+ if (lockOutputFile) {
+ outLock = out.tryLock(0, Long.MAX_VALUE, false);
+ if (null == outLock) {
+ throw new IOException("Unable to obtain exclusive file lock for: " + destination.getAbsolutePath());
+ }
+ }
+ long bytesWritten = 0;
+ do {
+ bytesWritten += out.transferFrom(in, bytesWritten, TRANSFER_CHUNK_SIZE_BYTES);
+ fileSize = in.size();
+ } while (bytesWritten < fileSize);
+ out.force(false);
+ FileUtils.closeQuietly(fos);
+ FileUtils.closeQuietly(fis);
+ fos = null;
+ fis = null;
+ if (move && !FileUtils.deleteFile(source, null, 5)) {
+ if (logger == null) {
+ FileUtils.deleteFile(destination, null, 5);
+ throw new IOException("Could not remove file " + source.getAbsolutePath());
+ } else {
+ logger.warn("Configured to delete source file when renaming/move not successful. However, unable to delete file at: " + source.getAbsolutePath());
+ }
+ }
+ } finally {
+ FileUtils.releaseQuietly(inLock);
+ FileUtils.releaseQuietly(outLock);
+ FileUtils.closeQuietly(fos);
+ FileUtils.closeQuietly(fis);
+ }
+ }
+ return fileSize;
+ }
+
+ /**
+ * Copies the given source file to the given destination file. The given destination will be overwritten if it already exists.
+ *
+ * @param source the file to copy from
+ * @param destination the file to copy to
+ * @param lockInputFile if true will lock input file during copy; if false will not
+ * @param lockOutputFile if true will lock output file during copy; if false will not
+ * @param logger the logger to use
+ * @return long number of bytes copied
+ * @throws FileNotFoundException if the source file could not be found
+ * @throws IOException if unable to read or write to file
+ * @throws SecurityException if a security manager denies the needed file operations
+ */
+ public static long copyFile(final File source, final File destination, final boolean lockInputFile, final boolean lockOutputFile, final Logger logger) throws FileNotFoundException, IOException {
+ return FileUtils.copyFile(source, destination, lockInputFile, lockOutputFile, false, logger);
+ }
+
+ public static long copyFile(final File source, final OutputStream stream, final boolean closeOutputStream, final boolean lockInputFile) throws FileNotFoundException, IOException {
+ FileInputStream fis = null;
+ FileLock inLock = null;
+ long fileSize = 0L;
+ try {
+ fis = new FileInputStream(source);
+ final FileChannel in = fis.getChannel();
+ if (lockInputFile) {
+ inLock = in.tryLock(0, Long.MAX_VALUE, true);
+ if (inLock == null) {
+ throw new IOException("Unable to obtain exclusive file lock for: " + source.getAbsolutePath());
+ }
+
+ }
+
+ byte[] buffer = new byte[1 << 18]; //256 KB
+ int bytesRead = -1;
+ while ((bytesRead = fis.read(buffer)) != -1) {
+ stream.write(buffer, 0, bytesRead);
+ }
+ in.force(false);
+ stream.flush();
+ fileSize = in.size();
+ } finally {
+ FileUtils.releaseQuietly(inLock);
+ FileUtils.closeQuietly(fis);
+ if (closeOutputStream) {
+ FileUtils.closeQuietly(stream);
+ }
+ }
+ return fileSize;
+ }
+
+ public static long copyFile(final InputStream stream, final File destination, final boolean closeInputStream, final boolean lockOutputFile) throws FileNotFoundException, IOException {
+ final Path destPath = destination.toPath();
+ final long size = Files.copy(stream, destPath);
+ if (closeInputStream) {
+ stream.close();
+ }
+ return size;
+ }
+
+ /**
+ * Deletes the given file. If the given file exists but could not be deleted
+ * this will be printed as a warning to the given logger
+ *
+ * @param file to delete
+ * @param logger to notify
+ * @return true if deleted
+ */
+ public static boolean deleteFile(final File file, final Logger logger) {
+ return FileUtils.deleteFile(file, logger, 1);
+ }
+
+ /**
+ * Deletes the given file. If the given file exists but could not be deleted
+ * this will be printed as a warning to the given logger
+ *
+ * @param file to delete
+ * @param logger to notify
+ * @param attempts indicates how many times an attempt to delete should be
+ * made
+ * @return true if given file no longer exists
+ */
+ public static boolean deleteFile(final File file, final Logger logger, final int attempts) {
+ if (file == null) {
+ return false;
+ }
+ boolean isGone = false;
+ try {
+ if (file.exists()) {
+ final int effectiveAttempts = Math.max(1, attempts);
+ for (int i = 0; i < effectiveAttempts && !isGone; i++) {
+ isGone = file.delete() || !file.exists();
+ if (!isGone && (effectiveAttempts - i) > 1) {
+ FileUtils.sleepQuietly(MILLIS_BETWEEN_ATTEMPTS);
+ }
+ }
+ if (!isGone && logger != null) {
+ logger.warn("File appears to exist but unable to delete file: " + file.getAbsolutePath());
+ }
+ }
+ } catch (final Throwable t) {
+ if (logger != null) {
+ logger.warn("Unable to delete file: '" + file.getAbsolutePath() + "' due to " + t);
+ }
+ }
+ return isGone;
+ }
+
+ /**
+ * Deletes all files (not directories..) in the given directory (non
+ * recursive) that match the given filename filter. If any file cannot be
+ * deleted then this is printed at warn to the given logger.
+ *
+ * @param directory to delete contents of
+ * @param filter if null then no filter is used
+ * @param logger to notify
+ * @throws IOException if abstract pathname does not denote a directory, or
+ * if an I/O error occurs
+ */
+ public static void deleteFilesInDirectory(final File directory, final FilenameFilter filter, final Logger logger) throws IOException {
+ FileUtils.deleteFilesInDirectory(directory, filter, logger, false);
+ }
+
+ /**
+ * Deletes all files (not directories) in the given directory (recursive)
+ * that match the given filename filter. If any file cannot be deleted then
+ * this is printed at warn to the given logger.
+ *
+ * @param directory to delete contents of
+ * @param filter if null then no filter is used
+ * @param logger to notify
+ * @param recurse true if should recurse
+ * @throws IOException if abstract pathname does not denote a directory, or
+ * if an I/O error occurs
+ */
+ public static void deleteFilesInDirectory(final File directory, final FilenameFilter filter, final Logger logger, final boolean recurse) throws IOException {
+ FileUtils.deleteFilesInDirectory(directory, filter, logger, recurse, false);
+ }
+
+ /**
+ * Deletes all files (not directories) in the given directory (recursive)
+ * that match the given filename filter. If any file cannot be deleted then
+ * this is printed at warn to the given logger.
+ *
+ * @param directory to delete contents of
+ * @param filter if null then no filter is used
+ * @param logger to notify
+ * @param recurse will look for contents of sub directories.
+ * @param deleteEmptyDirectories default is false; if true will delete
+ * directories found that are empty
+ * @throws IOException if abstract pathname does not denote a directory, or
+ * if an I/O error occurs
+ */
+ public static void deleteFilesInDirectory(final File directory, final FilenameFilter filter, final Logger logger, final boolean recurse, final boolean deleteEmptyDirectories) throws IOException {
+ // ensure the specified directory is actually a directory and that it exists
+ if (null != directory && directory.isDirectory()) {
+ final File ingestFiles[] = directory.listFiles();
+ if (ingestFiles == null) {
+ // null if abstract pathname does not denote a directory, or if an I/O error occurs
+ throw new IOException("Unable to list directory content in: " + directory.getAbsolutePath());
+ }
+ for (File ingestFile : ingestFiles) {
+ boolean process = (filter == null) ? true : filter.accept(directory, ingestFile.getName());
+ if (ingestFile.isFile() && process) {
+ FileUtils.deleteFile(ingestFile, logger, 3);
+ }
+ if (ingestFile.isDirectory() && recurse) {
+ FileUtils.deleteFilesInDirectory(ingestFile, filter, logger, recurse, deleteEmptyDirectories);
+ if (deleteEmptyDirectories && ingestFile.list().length == 0) {
+ FileUtils.deleteFile(ingestFile, logger, 3);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Deletes given files.
+ *
+ * @param files to delete
+ * @param recurse will recurse
+ * @throws IOException if issues deleting files
+ */
+ public static void deleteFiles(final Collection<File> files, final boolean recurse) throws IOException {
+ for (final File file : files) {
+ FileUtils.deleteFile(file, recurse);
+ }
+ }
+
+ public static void deleteFile(final File file, final boolean recurse) throws IOException {
+ final File[] list = file.listFiles();
+ if (file.isDirectory() && recurse && list != null) {
+ FileUtils.deleteFiles(Arrays.asList(list), recurse);
+ }
+ //now delete the file itself regardless of whether it is plain file or a directory
+ if (!FileUtils.deleteFile(file, null, 5)) {
+ throw new IOException("Unable to delete " + file.getAbsolutePath());
+ }
+ }
+
+ public static void sleepQuietly(final long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (final InterruptedException ex) {
+ /* do nothing */
+ }
+ }
+
+
+ // The invalid character list is derived from this Stackoverflow page.
+ // https://stackoverflow.com/questions/1155107/is-there-a-cross-platform-java-method-to-remove-filename-special-chars
+ private final static int[] INVALID_CHARS = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47, 32};
+
+ static {
+ Arrays.sort(INVALID_CHARS);
+ }
+
+ /**
+ * Replaces invalid characters for a file system name within a given filename string to underscore '_'.
+ * Be careful not to pass a file path as this method replaces path delimiter characters (i.e forward/back slashes).
+ * @param filename The filename to clean
+ * @return sanitized filename
+ */
+ public static String sanitizeFilename(String filename) {
+ if (filename == null || filename.isEmpty()) {
+ return filename;
+ }
+ int codePointCount = filename.codePointCount(0, filename.length());
+
+ final StringBuilder cleanName = new StringBuilder();
+ for (int i = 0; i < codePointCount; i++) {
+ int c = filename.codePointAt(i);
+ if (Arrays.binarySearch(INVALID_CHARS, c) < 0) {
+ cleanName.appendCodePoint(c);
+ } else {
+ cleanName.append('_');
+ }
+ }
+ return cleanName.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FormatUtils.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FormatUtils.java b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FormatUtils.java
new file mode 100644
index 0000000..c1e353d
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/FormatUtils.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.nifi.registry.util;
+
+import java.text.NumberFormat;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class FormatUtils {
+
+ private static final String UNION = "|";
+
+ // for Data Sizes
+ private static final double BYTES_IN_KILOBYTE = 1024;
+ private static final double BYTES_IN_MEGABYTE = BYTES_IN_KILOBYTE * 1024;
+ private static final double BYTES_IN_GIGABYTE = BYTES_IN_MEGABYTE * 1024;
+ private static final double BYTES_IN_TERABYTE = BYTES_IN_GIGABYTE * 1024;
+
+ // for Time Durations
+ private static final String NANOS = join(UNION, "ns", "nano", "nanos", "nanosecond", "nanoseconds");
+ private static final String MILLIS = join(UNION, "ms", "milli", "millis", "millisecond", "milliseconds");
+ private static final String SECS = join(UNION, "s", "sec", "secs", "second", "seconds");
+ private static final String MINS = join(UNION, "m", "min", "mins", "minute", "minutes");
+ private static final String HOURS = join(UNION, "h", "hr", "hrs", "hour", "hours");
+ private static final String DAYS = join(UNION, "d", "day", "days");
+ private static final String WEEKS = join(UNION, "w", "wk", "wks", "week", "weeks");
+
+ private static final String VALID_TIME_UNITS = join(UNION, NANOS, MILLIS, SECS, MINS, HOURS, DAYS, WEEKS);
+ public static final String TIME_DURATION_REGEX = "(\\d+)\\s*(" + VALID_TIME_UNITS + ")";
+ public static final Pattern TIME_DURATION_PATTERN = Pattern.compile(TIME_DURATION_REGEX);
+
+ /**
+ * Formats the specified count by adding commas.
+ *
+ * @param count the value to add commas to
+ * @return the string representation of the given value with commas included
+ */
+ public static String formatCount(final long count) {
+ return NumberFormat.getIntegerInstance().format(count);
+ }
+
+ /**
+ * Formats the specified duration in 'mm:ss.SSS' format.
+ *
+ * @param sourceDuration the duration to format
+ * @param sourceUnit the unit to interpret the duration
+ * @return representation of the given time data in minutes/seconds
+ */
+ public static String formatMinutesSeconds(final long sourceDuration, final TimeUnit sourceUnit) {
+ final long millis = TimeUnit.MILLISECONDS.convert(sourceDuration, sourceUnit);
+
+ final long millisInMinute = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
+ final int minutes = (int) (millis / millisInMinute);
+ final long secondsMillisLeft = millis - minutes * millisInMinute;
+
+ final long millisInSecond = TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS);
+ final int seconds = (int) (secondsMillisLeft / millisInSecond);
+ final long millisLeft = secondsMillisLeft - seconds * millisInSecond;
+
+ return pad2Places(minutes) + ":" + pad2Places(seconds) + "." + pad3Places(millisLeft);
+ }
+
+ /**
+ * Formats the specified duration in 'HH:mm:ss.SSS' format.
+ *
+ * @param sourceDuration the duration to format
+ * @param sourceUnit the unit to interpret the duration
+ * @return representation of the given time data in hours/minutes/seconds
+ */
+ public static String formatHoursMinutesSeconds(final long sourceDuration, final TimeUnit sourceUnit) {
+ final long millis = TimeUnit.MILLISECONDS.convert(sourceDuration, sourceUnit);
+
+ final long millisInHour = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);
+ final int hours = (int) (millis / millisInHour);
+ final long minutesSecondsMillisLeft = millis - hours * millisInHour;
+
+ return pad2Places(hours) + ":" + formatMinutesSeconds(minutesSecondsMillisLeft, TimeUnit.MILLISECONDS);
+ }
+
+ private static String pad2Places(final long val) {
+ return (val < 10) ? "0" + val : String.valueOf(val);
+ }
+
+ private static String pad3Places(final long val) {
+ return (val < 100) ? "0" + pad2Places(val) : String.valueOf(val);
+ }
+
+ /**
+ * Formats the specified data size in human readable format.
+ *
+ * @param dataSize Data size in bytes
+ * @return Human readable format
+ */
+ public static String formatDataSize(final double dataSize) {
+ // initialize the formatter
+ final NumberFormat format = NumberFormat.getNumberInstance();
+ format.setMaximumFractionDigits(2);
+
+ // check terabytes
+ double dataSizeToFormat = dataSize / BYTES_IN_TERABYTE;
+ if (dataSizeToFormat > 1) {
+ return format.format(dataSizeToFormat) + " TB";
+ }
+
+ // check gigabytes
+ dataSizeToFormat = dataSize / BYTES_IN_GIGABYTE;
+ if (dataSizeToFormat > 1) {
+ return format.format(dataSizeToFormat) + " GB";
+ }
+
+ // check megabytes
+ dataSizeToFormat = dataSize / BYTES_IN_MEGABYTE;
+ if (dataSizeToFormat > 1) {
+ return format.format(dataSizeToFormat) + " MB";
+ }
+
+ // check kilobytes
+ dataSizeToFormat = dataSize / BYTES_IN_KILOBYTE;
+ if (dataSizeToFormat > 1) {
+ return format.format(dataSizeToFormat) + " KB";
+ }
+
+ // default to bytes
+ return format.format(dataSize) + " bytes";
+ }
+
+ public static long getTimeDuration(final String value, final TimeUnit desiredUnit) {
+ final Matcher matcher = TIME_DURATION_PATTERN.matcher(value.toLowerCase());
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Value '" + value + "' is not a valid Time Duration");
+ }
+
+ final String duration = matcher.group(1);
+ final String units = matcher.group(2);
+ TimeUnit specifiedTimeUnit = null;
+ switch (units.toLowerCase()) {
+ case "ns":
+ case "nano":
+ case "nanos":
+ case "nanoseconds":
+ specifiedTimeUnit = TimeUnit.NANOSECONDS;
+ break;
+ case "ms":
+ case "milli":
+ case "millis":
+ case "milliseconds":
+ specifiedTimeUnit = TimeUnit.MILLISECONDS;
+ break;
+ case "s":
+ case "sec":
+ case "secs":
+ case "second":
+ case "seconds":
+ specifiedTimeUnit = TimeUnit.SECONDS;
+ break;
+ case "m":
+ case "min":
+ case "mins":
+ case "minute":
+ case "minutes":
+ specifiedTimeUnit = TimeUnit.MINUTES;
+ break;
+ case "h":
+ case "hr":
+ case "hrs":
+ case "hour":
+ case "hours":
+ specifiedTimeUnit = TimeUnit.HOURS;
+ break;
+ case "d":
+ case "day":
+ case "days":
+ specifiedTimeUnit = TimeUnit.DAYS;
+ break;
+ case "w":
+ case "wk":
+ case "wks":
+ case "week":
+ case "weeks":
+ final long durationVal = Long.parseLong(duration);
+ return desiredUnit.convert(durationVal, TimeUnit.DAYS)*7;
+ }
+
+ final long durationVal = Long.parseLong(duration);
+ return desiredUnit.convert(durationVal, specifiedTimeUnit);
+ }
+
+ public static String formatUtilization(final double utilization) {
+ return utilization + "%";
+ }
+
+ private static String join(final String delimiter, final String... values) {
+ if (values.length == 0) {
+ return "";
+ } else if (values.length == 1) {
+ return values[0];
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append(values[0]);
+ for (int i = 1; i < values.length; i++) {
+ sb.append(delimiter).append(values[i]);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Formats nanoseconds in the format:
+ * 3 seconds, 8 millis, 3 nanos - if includeTotalNanos = false,
+ * 3 seconds, 8 millis, 3 nanos (3008000003 nanos) - if includeTotalNanos = true
+ *
+ * @param nanos the number of nanoseconds to format
+ * @param includeTotalNanos whether or not to include the total number of nanoseconds in parentheses in the returned value
+ * @return a human-readable String that is a formatted representation of the given number of nanoseconds.
+ */
+ public static String formatNanos(final long nanos, final boolean includeTotalNanos) {
+ final StringBuilder sb = new StringBuilder();
+
+ final long seconds = nanos > 1000000000L ? nanos / 1000000000L : 0L;
+ long millis = nanos > 1000000L ? nanos / 1000000L : 0L;
+ final long nanosLeft = nanos % 1000000L;
+
+ if (seconds > 0) {
+ sb.append(seconds).append(" seconds");
+ }
+ if (millis > 0) {
+ if (seconds > 0) {
+ sb.append(", ");
+ millis -= seconds * 1000L;
+ }
+
+ sb.append(millis).append(" millis");
+ }
+ if (seconds > 0 || millis > 0) {
+ sb.append(", ");
+ }
+ sb.append(nanosLeft).append(" nanos");
+
+ if (includeTotalNanos) {
+ sb.append(" (").append(nanos).append(" nanos)");
+ }
+
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/PropertyValue.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/PropertyValue.java b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/PropertyValue.java
new file mode 100644
index 0000000..4950772
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/PropertyValue.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.nifi.registry.util;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * <p>
+ * A PropertyValue provides a mechanism whereby the currently configured value
+ * can be obtained in different forms.
+ * </p>
+ */
+public interface PropertyValue {
+
+ /**
+ * @return the raw property value as a string
+ */
+ String getValue();
+
+ /**
+ * @return an integer representation of the property value, or
+ * <code>null</code> if not set
+ * @throws NumberFormatException if not able to parse
+ */
+ Integer asInteger();
+
+ /**
+ * @return a Long representation of the property value, or <code>null</code>
+ * if not set
+ * @throws NumberFormatException if not able to parse
+ */
+ Long asLong();
+
+ /**
+ * @return a Boolean representation of the property value, or
+ * <code>null</code> if not set
+ */
+ Boolean asBoolean();
+
+ /**
+ * @return a Float representation of the property value, or
+ * <code>null</code> if not set
+ * @throws NumberFormatException if not able to parse
+ */
+ Float asFloat();
+
+ /**
+ * @return a Double representation of the property value, of
+ * <code>null</code> if not set
+ * @throws NumberFormatException if not able to parse
+ */
+ Double asDouble();
+
+ /**
+ * @param timeUnit specifies the TimeUnit to convert the time duration into
+ * @return a Long value representing the value of the configured time period
+ * in terms of the specified TimeUnit; if the property is not set, returns
+ * <code>null</code>
+ */
+ Long asTimePeriod(TimeUnit timeUnit);
+
+ /**
+ *
+ * @param dataUnit specifies the DataUnit to convert the data size into
+ * @return a Long value representing the value of the configured data size
+ * in terms of the specified DataUnit; if hte property is not set, returns
+ * <code>null</code>
+ */
+ Double asDataSize(DataUnit dataUnit);
+
+ /**
+ * @return <code>true</code> if the user has configured a value, or if the
+ * PropertyDescriptor for the associated property has a default
+ * value, <code>false</code> otherwise
+ */
+ boolean isSet();
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/StandardPropertyValue.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/StandardPropertyValue.java b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/StandardPropertyValue.java
new file mode 100644
index 0000000..b185fad
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-utils/src/main/java/org/apache/nifi/registry/util/StandardPropertyValue.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.nifi.registry.util;
+
+import java.util.concurrent.TimeUnit;
+
+public class StandardPropertyValue implements PropertyValue {
+
+ private final String rawValue;
+
+ public StandardPropertyValue(final String rawValue) {
+ this.rawValue = rawValue;
+ }
+
+ @Override
+ public String getValue() {
+ return rawValue;
+ }
+
+ @Override
+ public Integer asInteger() {
+ return (rawValue == null) ? null : Integer.parseInt(rawValue.trim());
+ }
+
+ @Override
+ public Long asLong() {
+ return (rawValue == null) ? null : Long.parseLong(rawValue.trim());
+ }
+
+ @Override
+ public Boolean asBoolean() {
+ return (rawValue == null) ? null : Boolean.parseBoolean(rawValue.trim());
+ }
+
+ @Override
+ public Float asFloat() {
+ return (rawValue == null) ? null : Float.parseFloat(rawValue.trim());
+ }
+
+ @Override
+ public Double asDouble() {
+ return (rawValue == null) ? null : Double.parseDouble(rawValue.trim());
+ }
+
+ @Override
+ public Long asTimePeriod(final TimeUnit timeUnit) {
+ return (rawValue == null) ? null : FormatUtils.getTimeDuration(rawValue.trim(), timeUnit);
+ }
+
+ @Override
+ public Double asDataSize(final DataUnit dataUnit) {
+ return rawValue == null ? null : DataUnit.parseDataSize(rawValue.trim(), dataUnit);
+ }
+
+ @Override
+ public boolean isSet() {
+ return rawValue != null;
+ }
+
+ @Override
+ public String toString() {
+ return rawValue;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-utils/src/test/java/org/apache/nifi/registry/util/TestFileUtils.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-utils/src/test/java/org/apache/nifi/registry/util/TestFileUtils.java b/nifi-registry-core/nifi-registry-utils/src/test/java/org/apache/nifi/registry/util/TestFileUtils.java
new file mode 100644
index 0000000..d4bc963
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-utils/src/test/java/org/apache/nifi/registry/util/TestFileUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.nifi.registry.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestFileUtils {
+ @Test
+ public void testSanitizeFilename() {
+ String filename = "This / is / a test";
+ final String sanitizedFilename = FileUtils.sanitizeFilename(filename);
+ assertEquals("This___is___a_test", sanitizedFilename);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/pom.xml b/nifi-registry-core/nifi-registry-web-api/pom.xml
new file mode 100644
index 0000000..65ec265
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/pom.xml
@@ -0,0 +1,359 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF 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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-core</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-registry-web-api</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ <packaging>war</packaging>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring.boot.version}</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-war-plugin</artifactId>
+ <configuration>
+ <failOnMissingWebXml>false</failOnMissingWebXml>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.github.kongchen</groupId>
+ <artifactId>swagger-maven-plugin</artifactId>
+ <version>3.1.6</version>
+ <executions>
+ <execution>
+ <phase>compile</phase>
+ <goals>
+ <goal>generate</goal>
+ </goals>
+ <configuration>
+ <apiSources>
+ <apiSource>
+ <locations>
+ <location>org.apache.nifi.registry.web.api</location>
+ </locations>
+ <schemes>
+ <scheme>http</scheme>
+ <scheme>https</scheme>
+ </schemes>
+ <basePath>/nifi-registry-api</basePath>
+ <info>
+ <title>NiFi Registry REST API</title>
+ <version>${project.version}</version>
+ <description>
+ The REST API provides an interface to a registry with operations for saving, versioning, reading NiFi flows and components.
+ </description>
+ <contact>
+ <name>Apache NiFi Registry</name>
+ <email>dev@nifi.apache.org</email>
+ <url>https://nifi.apache.org</url>
+ </contact>
+ <license>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
+ <name>Apache 2.0 License</name>
+ </license>
+ </info>
+ <securityDefinitions>
+ <securityDefinition>
+ <jsonPath>${project.basedir}/src/main/resources/swagger/security-definitions.json</jsonPath>
+ </securityDefinition>
+ </securityDefinitions>
+ <templatePath>classpath:/templates/index.html.hbs</templatePath>
+ <outputPath>
+ ${project.build.directory}/${project.artifactId}-${project.version}/docs/rest-api/index.html
+ </outputPath>
+ <swaggerDirectory>${project.build.directory}/swagger</swaggerDirectory>
+ </apiSource>
+ </apiSources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-resources</id>
+ <phase>validate</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/${project.artifactId}-${project.version}/docs/rest-api/images</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/main/resources/images</directory>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>com.googlecode.maven-download-plugin</groupId>
+ <artifactId>download-maven-plugin</artifactId>
+ <version>1.2.1</version>
+ <executions>
+ <execution>
+ <id>download-swagger-ui</id>
+ <!-- This plugin downloads swagger UI static assets during the build, to be
+ served by the web app to render the dynamically generated Swagger spec.
+ For offline development, or to build without the swagger UI, activate
+ the "no-swagger-ui" maven profile during the build with the "-P" flag -->
+ <goals>
+ <goal>wget</goal>
+ </goals>
+ <configuration>
+ <url>
+ https://github.com/swagger-api/swagger-ui/archive/v${swagger.ui.version}.tar.gz
+ </url>
+ <unpack>true</unpack>
+ <outputDirectory>${project.build.directory}
+ </outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>1.8</version>
+ <executions>
+ <execution>
+ <id>bundle-swagger-ui</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <target>
+ <sequential>
+ <echo>Copy static Swagger UI files to target</echo>
+ <copy todir="${project.build.directory}/classes/static/swagger">
+ <fileset dir="${project.build.directory}/swagger-ui-${swagger.ui.version}/dist">
+ <include name="**" />
+ </fileset>
+ </copy>
+ <echo>Disable schema validation by removing validatorUrl</echo>
+ <replace token="https://online.swagger.io/validator" value="" dir="${project.build.directory}/classes/static/swagger">
+ <include name="swagger-ui-bundle.js" />
+ <include name="swagger-ui-standalone-preset.js" />
+ </replace>
+ <echo>Rename 'index.html' to 'ui.html'</echo>
+ <move file="${project.build.directory}/classes/static/swagger/index.html" tofile="${project.build.directory}/classes/static/swagger/ui.html" />
+ <echo>Replace default swagger.json location</echo>
+ <replace token="http://petstore.swagger.io/v2/swagger.json" value="/nifi-registry-api/swagger/swagger.json" dir="${project.build.directory}/classes/static/swagger">
+ <include name="ui.html" />
+ </replace>
+ <echo>Copy swagger.json into static assets folder</echo>
+ <copy todir="${project.build.directory}/classes/static/swagger">
+ <fileset dir="${project.build.directory}/swagger">
+ <include name="*.json" />
+ </fileset>
+ </copy>
+ </sequential>
+ </target>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>no-swagger-ui</id>
+ <!-- Activate this profile with "-P no-swagger-ui" to disable the Swagger UI
+ static assets from being downloaded and bundled with the web api WAR. -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.googlecode.maven-download-plugin</groupId>
+ <artifactId>download-maven-plugin</artifactId>
+ <version>1.2.1</version>
+ <executions>
+ <execution>
+ <id>download-swagger-ui</id>
+ <phase>none</phase>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>1.8</version>
+ <executions>
+ <execution>
+ <id>bundle-swagger-ui</id>
+ <phase>none</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <version>${spring.boot.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-jersey</artifactId>
+ <version>${spring.boot.version}</version>
+ </dependency>
+ <!-- Exclude micrometer-core because it creates a class cast issue with logback, revisit later -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ <version>${spring.boot.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-core</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.security.kerberos</groupId>
+ <artifactId>spring-security-kerberos-core</artifactId>
+ <version>1.0.1.RELEASE</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-core</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <!-- Must be marked provided in order to produce a correct WAR -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ <version>${spring.boot.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-framework</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-properties</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ <scope>provided</scope> <!-- This will be in the lib directory -->
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-security-api</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ <scope>provided</scope> <!-- This will be in lib directory -->
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-provider-api</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ <scope>provided</scope> <!-- This will be in lib directory -->
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-security-utils</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.swagger</groupId>
+ <artifactId>swagger-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt</artifactId>
+ <version>0.7.0</version>
+ </dependency>
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-client</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <version>${spring.boot.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-jetty</artifactId>
+ <version>${spring.boot.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.unboundid</groupId>
+ <artifactId>unboundid-ldapsdk</artifactId>
+ <version>3.2.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.spockframework</groupId>
+ <artifactId>spock-core</artifactId>
+ <version>1.0-groovy-2.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy-all</artifactId>
+ <version>2.4.12</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>cglib</groupId>
+ <artifactId>cglib-nodep</artifactId>
+ <version>2.2.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryApiApplication.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryApiApplication.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryApiApplication.java
new file mode 100644
index 0000000..d06555d
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryApiApplication.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.nifi.registry;
+
+import org.apache.nifi.registry.event.EventService;
+import org.apache.nifi.registry.event.StandardEvent;
+import org.apache.nifi.registry.hook.Event;
+import org.apache.nifi.registry.hook.EventType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+import java.util.Properties;
+
+/**
+ * Main class for starting the NiFi Registry Web API as a Spring Boot application.
+ *
+ * This class is purposely in the org.apache.nifi.registry package since that is the common base
+ * package across other modules. This is done because spring-boot will use the package of this
+ * class to automatically scan for beans/config/entities/etc. and would otherwise require
+ * configuring custom packages to scan in several different places.
+ *
+ * WebMvcAutoConfiguration is excluded because our web app is using Jersey in place of SpringMVC
+ */
+@SpringBootApplication
+public class NiFiRegistryApiApplication extends SpringBootServletInitializer {
+
+ public static final String NIFI_REGISTRY_PROPERTIES_ATTRIBUTE = "nifi-registry.properties";
+ public static final String NIFI_REGISTRY_MASTER_KEY_ATTRIBUTE = "nifi-registry.key";
+
+ @Autowired
+ private EventService eventService;
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+ final Properties defaultProperties = new Properties();
+
+ // Enable Actuator Endpoints
+ defaultProperties.setProperty("management.endpoints.web.expose", "*");
+
+ // Run Jersey as a filter instead of a servlet so that requests can be forwarded to other handlers (e.g., actuator)
+ defaultProperties.setProperty("spring.jersey.type", "filter");
+
+ return application
+ .sources(NiFiRegistryApiApplication.class)
+ .properties(defaultProperties);
+ }
+
+ @Component
+ private class OnApplicationReadyEventing
+ implements ApplicationListener<ApplicationReadyEvent> {
+
+ @Override
+ public void onApplicationEvent(final ApplicationReadyEvent event) {
+ Event registryStartEvent = new StandardEvent.Builder()
+ .eventType(EventType.REGISTRY_START)
+ .build();
+ eventService.publish(registryStartEvent);
+ }
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(NiFiRegistryApiApplication.class, args);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryPropertiesFactory.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryPropertiesFactory.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryPropertiesFactory.java
new file mode 100644
index 0000000..b7865bb
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/NiFiRegistryPropertiesFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.nifi.registry;
+
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.context.ServletContextAware;
+
+import javax.servlet.ServletContext;
+
+/**
+ * The JettyServer puts an instance of NiFiRegistryProperties into the ServletContext, this class
+ * obtains that instance and makes it available to inject to all other places.
+ *
+ */
+@Configuration
+public class NiFiRegistryPropertiesFactory implements ServletContextAware {
+
+ private NiFiRegistryProperties properties;
+
+ @Override
+ public void setServletContext(ServletContext servletContext) {
+ properties = (NiFiRegistryProperties) servletContext.getAttribute(
+ NiFiRegistryApiApplication.NIFI_REGISTRY_PROPERTIES_ATTRIBUTE);
+ }
+
+ @Bean
+ public NiFiRegistryProperties getNiFiRegistryProperties() {
+ return properties;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java
new file mode 100644
index 0000000..a5ab5ef
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.nifi.registry.web;
+
+import org.apache.nifi.registry.web.api.AccessPolicyResource;
+import org.apache.nifi.registry.web.api.AccessResource;
+import org.apache.nifi.registry.web.api.BucketFlowResource;
+import org.apache.nifi.registry.web.api.BucketResource;
+import org.apache.nifi.registry.web.api.ConfigResource;
+import org.apache.nifi.registry.web.api.FlowResource;
+import org.apache.nifi.registry.web.api.ItemResource;
+import org.apache.nifi.registry.web.api.TenantResource;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.filter.HttpMethodOverrideFilter;
+import org.glassfish.jersey.servlet.ServletProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletContext;
+import javax.ws.rs.core.Context;
+
+/**
+ * This is the main Jersey configuration for the application.
+ *
+ * NOTE: Don't set @ApplicationPath here because it has already been set to 'nifi-registry-api' in JettyServer
+ */
+@Configuration
+public class NiFiRegistryResourceConfig extends ResourceConfig {
+
+ private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryResourceConfig.class);
+
+ public NiFiRegistryResourceConfig(@Context ServletContext servletContext) {
+ // register filters
+ register(HttpMethodOverrideFilter.class);
+
+ // register the exception mappers & jackson object mapper resolver
+ packages("org.apache.nifi.registry.web.mapper");
+
+ // register endpoints
+ register(AccessPolicyResource.class);
+ register(AccessResource.class);
+ register(BucketResource.class);
+ register(BucketFlowResource.class);
+ register(FlowResource.class);
+ register(ItemResource.class);
+ register(TenantResource.class);
+ register(ConfigResource.class);
+
+ // include bean validation errors in response
+ property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
+
+ // this is necessary for the /access/token/kerberos endpoint to work correctly
+ // when sending 401 Unauthorized with a WWW-Authenticate: Negotiate header.
+ // if this value needs to be changed, kerberos authentication needs to move to filter chain
+ // so it can directly set the HttpServletResponse instead of indirectly through a JAX-RS Response
+ property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);
+
+ // configure jersey to ignore resource paths for actuator and swagger-ui
+ property(ServletProperties.FILTER_STATIC_CONTENT_REGEX, "/(actuator|swagger/).*");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java
new file mode 100644
index 0000000..713b369
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessPolicyResource.java
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.nifi.registry.web.api;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import io.swagger.annotations.Authorization;
+import io.swagger.annotations.Extension;
+import io.swagger.annotations.ExtensionProperty;
+import org.apache.nifi.registry.authorization.AccessPolicy;
+import org.apache.nifi.registry.authorization.AccessPolicySummary;
+import org.apache.nifi.registry.authorization.Resource;
+import org.apache.nifi.registry.event.EventService;
+import org.apache.nifi.registry.exception.ResourceNotFoundException;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.AuthorizerCapabilityDetection;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.resource.Authorizable;
+import org.apache.nifi.registry.service.AuthorizationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+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 java.net.URI;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * RESTful endpoint for managing access policies.
+ */
+@Component
+@Path("/policies")
+@Api(
+ value = "policies",
+ description = "Endpoint for managing access policies.",
+ authorizations = { @Authorization("Authorization") }
+)
+public class AccessPolicyResource extends AuthorizableApplicationResource {
+
+ private static final Logger logger = LoggerFactory.getLogger(AccessPolicyResource.class);
+
+ private Authorizer authorizer;
+
+ @Autowired
+ public AccessPolicyResource(
+ Authorizer authorizer,
+ AuthorizationService authorizationService,
+ EventService eventService) {
+ super(authorizationService, eventService);
+ this.authorizer = authorizer;
+ }
+
+ /**
+ * Create a new access policy.
+ *
+ * @param httpServletRequest request
+ * @param requestAccessPolicy the access policy to create.
+ * @return The created access policy.
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Creates an access policy",
+ response = AccessPolicy.class,
+ extensions = {
+ @Extension(name = "access-policy", properties = {
+ @ExtensionProperty(name = "action", value = "write"),
+ @ExtensionProperty(name = "resource", value = "/policies") })
+ }
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409 + " The NiFi Registry might not be configured to use a ConfigurableAccessPolicyProvider.") })
+ public Response createAccessPolicy(
+ @Context final HttpServletRequest httpServletRequest,
+ @ApiParam(value = "The access policy configuration details.", required = true)
+ final AccessPolicy requestAccessPolicy) {
+
+ verifyAuthorizerSupportsConfigurablePolicies();
+ authorizeAccess(RequestAction.WRITE);
+
+ if (requestAccessPolicy == null) {
+ throw new IllegalArgumentException("Access policy details must be specified when creating a new policy.");
+ }
+ if (requestAccessPolicy.getIdentifier() != null) {
+ throw new IllegalArgumentException("Access policy ID cannot be specified when creating a new policy.");
+ }
+ if (requestAccessPolicy.getResource() == null) {
+ throw new IllegalArgumentException("Resource must be specified when creating a new access policy.");
+ }
+ RequestAction.valueOfValue(requestAccessPolicy.getAction());
+
+ AccessPolicy createdPolicy = authorizationService.createAccessPolicy(requestAccessPolicy);
+
+ String locationUri = generateAccessPolicyUri(createdPolicy);
+ return generateCreatedResponse(URI.create(locationUri), createdPolicy).build();
+ }
+
+ /**
+ * Retrieves all access policies
+ *
+ * @return A list of access policies
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Gets all access policies",
+ response = AccessPolicy.class,
+ responseContainer = "List",
+ extensions = {
+ @Extension(name = "access-policy", properties = {
+ @ExtensionProperty(name = "action", value = "read"),
+ @ExtensionProperty(name = "resource", value = "/policies") })
+ }
+ )
+ @ApiResponses({
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response getAccessPolicies() {
+
+ verifyAuthorizerIsManaged();
+ authorizeAccess(RequestAction.READ);
+
+ List<AccessPolicy> accessPolicies = authorizationService.getAccessPolicies();
+ if (accessPolicies == null) {
+ accessPolicies = Collections.emptyList();
+ }
+
+ return generateOkResponse(accessPolicies).build();
+ }
+
+ /**
+ * Retrieves the specified access policy.
+ *
+ * @param identifier The id of the access policy to retrieve
+ * @return An accessPolicyEntity.
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}")
+ @ApiOperation(
+ value = "Gets an access policy",
+ response = AccessPolicy.class,
+ extensions = {
+ @Extension(name = "access-policy", properties = {
+ @ExtensionProperty(name = "action", value = "read"),
+ @ExtensionProperty(name = "resource", value = "/policies") })
+ }
+ )
+ @ApiResponses({
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response getAccessPolicy(
+ @ApiParam(value = "The access policy id.", required = true)
+ @PathParam("id") final String identifier) {
+
+ verifyAuthorizerIsManaged();
+ authorizeAccess(RequestAction.READ);
+
+ final AccessPolicy accessPolicy = authorizationService.getAccessPolicy(identifier);
+ if (accessPolicy == null) {
+ logger.warn("The specified access policy id [{}] does not exist.", identifier);
+
+ throw new ResourceNotFoundException("The specified policy does not exist in this registry.");
+ }
+
+ return generateOkResponse(accessPolicy).build();
+ }
+
+
+ /**
+ * Retrieve a specified access policy for a given (action, resource) pair.
+ *
+ * @param action the action, i.e. "read", "write"
+ * @param rawResource the name of the resource as a raw string
+ * @return An access policy.
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{action}/{resource: .+}")
+ @ApiOperation(
+ value = "Gets an access policy for the specified action and resource",
+ response = AccessPolicy.class,
+ extensions = {
+ @Extension(name = "access-policy", properties = {
+ @ExtensionProperty(name = "action", value = "read"),
+ @ExtensionProperty(name = "resource", value = "/policies") })
+ }
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) })
+ public Response getAccessPolicyForResource(
+ @ApiParam(value = "The request action.", allowableValues = "read, write, delete", required = true)
+ @PathParam("action")
+ final String action,
+ @ApiParam(value = "The resource of the policy.", required = true)
+ @PathParam("resource")
+ final String rawResource) {
+
+ verifyAuthorizerIsManaged();
+ authorizeAccess(RequestAction.READ);
+
+ // parse the action and resource type
+ final RequestAction requestAction = RequestAction.valueOfValue(action);
+ final String resource = "/" + rawResource;
+
+ AccessPolicy accessPolicy = authorizationService.getAccessPolicy(resource, requestAction);
+ if (accessPolicy == null) {
+ throw new ResourceNotFoundException("No policy found for action='" + action + "', resource='" + resource + "'");
+ }
+ return generateOkResponse(accessPolicy).build();
+ }
+
+
+ /**
+ * Update an access policy.
+ *
+ * @param httpServletRequest request
+ * @param identifier The id of the access policy to update.
+ * @param requestAccessPolicy An access policy.
+ * @return the updated access policy.
+ */
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}")
+ @ApiOperation(
+ value = "Updates a access policy",
+ response = AccessPolicy.class,
+ extensions = {
+ @Extension(name = "access-policy", properties = {
+ @ExtensionProperty(name = "action", value = "write"),
+ @ExtensionProperty(name = "resource", value = "/policies") })
+ }
+ )
+ @ApiResponses({
+ @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409 + " The NiFi Registry might not be configured to use a ConfigurableAccessPolicyProvider.") })
+ public Response updateAccessPolicy(
+ @Context
+ final HttpServletRequest httpServletRequest,
+ @ApiParam(value = "The access policy id.", required = true)
+ @PathParam("id")
+ final String identifier,
+ @ApiParam(value = "The access policy configuration details.", required = true)
+ final AccessPolicy requestAccessPolicy) {
+
+ verifyAuthorizerSupportsConfigurablePolicies();
+ authorizeAccess(RequestAction.WRITE);
+
+ if (requestAccessPolicy == null) {
+ throw new IllegalArgumentException("Access policy details must be specified when updating a policy.");
+ }
+ if (!identifier.equals(requestAccessPolicy.getIdentifier())) {
+ throw new IllegalArgumentException(String.format("The policy id in the request body (%s) does not equal the "
+ + "policy id of the requested resource (%s).", requestAccessPolicy.getIdentifier(), identifier));
+ }
+
+ AccessPolicy createdPolicy = authorizationService.updateAccessPolicy(requestAccessPolicy);
+
+ String locationUri = generateAccessPolicyUri(createdPolicy);
+ return generateOkResponse(createdPolicy).build();
+ }
+
+
+ /**
+ * Remove a specified access policy.
+ *
+ * @param httpServletRequest request
+ * @param identifier The id of the access policy to remove.
+ * @return The deleted access policy
+ */
+ @DELETE
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}")
+ @ApiOperation(
+ value = "Deletes an access policy",
+ response = AccessPolicy.class,
+ extensions = {
+ @Extension(name = "access-policy", properties = {
+ @ExtensionProperty(name = "action", value = "delete"),
+ @ExtensionProperty(name = "resource", value = "/policies") })
+ }
+ )
+ @ApiResponses({
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+ @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+ @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409 + " The NiFi Registry might not be configured to use a ConfigurableAccessPolicyProvider.") })
+ public Response removeAccessPolicy(
+ @Context final HttpServletRequest httpServletRequest,
+ @ApiParam(value = "The access policy id.", required = true)
+ @PathParam("id")
+ final String identifier) {
+
+ verifyAuthorizerSupportsConfigurablePolicies();
+ authorizeAccess(RequestAction.DELETE);
+ AccessPolicy deletedPolicy = authorizationService.deleteAccessPolicy(identifier);
+ if (deletedPolicy == null) {
+ logger.warn("The specified access policy id [{}] does not exist.", identifier);
+
+ throw new ResourceNotFoundException("The specified policy does not exist in this registry.");
+ }
+ return generateOkResponse(deletedPolicy).build();
+ }
+
+ /**
+ * Gets the available resources that support access/authorization policies.
+ *
+ * @return A resourcesEntity.
+ */
+ @GET
+ @Path("/resources")
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Gets the available resources that support access/authorization policies",
+ response = Resource.class,
+ responseContainer = "List",
+ extensions = {
+ @Extension(name = "access-policy", properties = {
+ @ExtensionProperty(name = "action", value = "read"),
+ @ExtensionProperty(name = "resource", value = "/policies") })
+ }
+ )
+ @ApiResponses({
+ @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+ @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403) })
+ public Response getResources() {
+ authorizeAccess(RequestAction.READ);
+
+ final List<Resource> resources = authorizationService.getResources();
+
+ return generateOkResponse(resources).build();
+ }
+
+
+ private void verifyAuthorizerIsManaged() {
+ if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+ throw new IllegalStateException(AuthorizationService.MSG_NON_MANAGED_AUTHORIZER);
+ }
+ }
+
+ private void verifyAuthorizerSupportsConfigurablePolicies() {
+ if (!AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)) {
+ verifyAuthorizerIsManaged();
+ throw new IllegalStateException(AuthorizationService.MSG_NON_CONFIGURABLE_POLICIES);
+ }
+ }
+
+ private void authorizeAccess(RequestAction actionType) {
+ final Authorizable policiesAuthorizable = authorizableLookup.getPoliciesAuthorizable();
+ authorizationService.authorize(policiesAuthorizable, actionType);
+ }
+
+ private String generateAccessPolicyUri(final AccessPolicySummary accessPolicy) {
+ return generateResourceUri("policies", accessPolicy.getIdentifier());
+ }
+
+}