mirror of
https://github.com/apache/nifi.git
synced 2025-02-28 14:39:10 +00:00
Merge remote-tracking branch 'upstream/develop' into nifi-solr-bundle
Conflicts: nifi/nifi-assembly/pom.xml
This commit is contained in:
commit
4107a0e934
@ -187,9 +187,8 @@ public abstract class AbstractConfigurableComponent implements ConfigurableCompo
|
||||
}
|
||||
|
||||
public final List<PropertyDescriptor> getPropertyDescriptors() {
|
||||
final List<PropertyDescriptor> descriptors = new ArrayList<>();
|
||||
descriptors.addAll(getSupportedPropertyDescriptors());
|
||||
return descriptors;
|
||||
final List<PropertyDescriptor> supported = getSupportedPropertyDescriptors();
|
||||
return supported == null ? Collections.<PropertyDescriptor>emptyList() :new ArrayList<>(supported);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -79,4 +79,23 @@ public interface ValidationContext {
|
||||
* @return
|
||||
*/
|
||||
String getAnnotationData();
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the given value contains a NiFi Expression Language expression,
|
||||
* <code>false</code> if it does not
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
boolean isExpressionLanguagePresent(String value);
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the property with the given name supports the NiFi Expression Language,
|
||||
* <code>false</code> if the property does not support the Expression Language or is not a valid property
|
||||
* name
|
||||
*
|
||||
* @param propertyName
|
||||
* @return
|
||||
*/
|
||||
boolean isExpressionLanguageSupported(String propertyName);
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Interface for obtaining content from the NiFi content repository.
|
||||
*/
|
||||
public interface ViewableContent {
|
||||
|
||||
public static final String CONTENT_REQUEST_ATTRIBUTE = "org.apache.nifi.web.content";
|
||||
|
||||
public enum DisplayMode {
|
||||
Original,
|
||||
Formatted,
|
||||
Hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* The stream to the viewable content. The data stream can only be read once so
|
||||
* an extension can call this method or getContent.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
InputStream getContentStream();
|
||||
|
||||
/**
|
||||
* Gets the content as a string. The data stream can only be read once so
|
||||
* an extension can call this method or getContentStream.
|
||||
*
|
||||
* @return
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
String getContent() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the desired play mode. If the mode is Hex the
|
||||
* framework will handle generating the mark up. The only
|
||||
* values that an extension will see is Original or Formatted.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
DisplayMode getDisplayMode();
|
||||
|
||||
/**
|
||||
* The contents file name.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getFileName();
|
||||
|
||||
/**
|
||||
* The mime type of the content.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getContentType();
|
||||
}
|
@ -219,8 +219,7 @@
|
||||
<nifi.content.repository.archive.enabled>false</nifi.content.repository.archive.enabled>
|
||||
<nifi.content.repository.always.sync>false</nifi.content.repository.always.sync>
|
||||
<nifi.content.viewer.url />
|
||||
|
||||
|
||||
|
||||
<nifi.restore.directory />
|
||||
<nifi.ui.banner.text />
|
||||
<nifi.ui.autorefresh.interval>30 sec</nifi.ui.autorefresh.interval>
|
||||
|
@ -46,7 +46,10 @@ public class StandardValidators {
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
final ValidationResult.Builder builder = new ValidationResult.Builder();
|
||||
builder.subject(subject).input(input);
|
||||
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return builder.valid(true).explanation("Contains Expression Language").build();
|
||||
}
|
||||
|
||||
try {
|
||||
FlowFile.KeyValidator.validateKey(input);
|
||||
builder.valid(true);
|
||||
@ -63,7 +66,10 @@ public class StandardValidators {
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
final ValidationResult.Builder builder = new ValidationResult.Builder();
|
||||
builder.subject("Property Name").input(subject);
|
||||
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return builder.valid(true).explanation("Contains Expression Language").build();
|
||||
}
|
||||
|
||||
try {
|
||||
FlowFile.KeyValidator.validateKey(subject);
|
||||
builder.valid(true);
|
||||
@ -78,6 +84,10 @@ public class StandardValidators {
|
||||
public static final Validator POSITIVE_INTEGER_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String value, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build();
|
||||
}
|
||||
|
||||
String reason = null;
|
||||
try {
|
||||
final int intVal = Integer.parseInt(value);
|
||||
@ -96,6 +106,10 @@ public class StandardValidators {
|
||||
public static final Validator POSITIVE_LONG_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String value, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build();
|
||||
}
|
||||
|
||||
String reason = null;
|
||||
try {
|
||||
final long longVal = Long.parseLong(value);
|
||||
@ -123,6 +137,10 @@ public class StandardValidators {
|
||||
public static final Validator BOOLEAN_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String value, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build();
|
||||
}
|
||||
|
||||
final boolean valid = "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value);
|
||||
final String explanation = valid ? null : "Value must be 'true' or 'false'";
|
||||
return new ValidationResult.Builder().subject(subject).input(value).valid(valid).explanation(explanation).build();
|
||||
@ -132,6 +150,10 @@ public class StandardValidators {
|
||||
public static final Validator INTEGER_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String value, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build();
|
||||
}
|
||||
|
||||
String reason = null;
|
||||
try {
|
||||
Integer.parseInt(value);
|
||||
@ -146,6 +168,10 @@ public class StandardValidators {
|
||||
public static final Validator LONG_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String value, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build();
|
||||
}
|
||||
|
||||
String reason = null;
|
||||
try {
|
||||
Long.parseLong(value);
|
||||
@ -160,6 +186,10 @@ public class StandardValidators {
|
||||
public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String value, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build();
|
||||
}
|
||||
|
||||
String reason = null;
|
||||
try {
|
||||
final int intVal = Integer.parseInt(value);
|
||||
@ -178,6 +208,10 @@ public class StandardValidators {
|
||||
public static final Validator CHARACTER_SET_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String value, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build();
|
||||
}
|
||||
|
||||
String reason = null;
|
||||
try {
|
||||
if (!Charset.isSupported(value)) {
|
||||
@ -201,6 +235,10 @@ public class StandardValidators {
|
||||
public static final Validator URI_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").build();
|
||||
}
|
||||
|
||||
try {
|
||||
new URI(input);
|
||||
return new ValidationResult.Builder().subject(subject).input(input).explanation("Valid URI").valid(true).build();
|
||||
@ -215,6 +253,10 @@ public class StandardValidators {
|
||||
public static final Validator ATTRIBUTE_EXPRESSION_LANGUAGE_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").build();
|
||||
}
|
||||
|
||||
try {
|
||||
context.newExpressionLanguageCompiler().compile(input);
|
||||
return new ValidationResult.Builder().subject(subject).input(input).valid(true).build();
|
||||
@ -228,6 +270,10 @@ public class StandardValidators {
|
||||
public static final Validator TIME_PERIOD_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").build();
|
||||
}
|
||||
|
||||
if (input == null) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).valid(false).explanation("Time Period cannot be null").build();
|
||||
}
|
||||
@ -242,6 +288,10 @@ public class StandardValidators {
|
||||
public static final Validator DATA_SIZE_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").build();
|
||||
}
|
||||
|
||||
if (input == null) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).valid(false).explanation("Data Size cannot be null").build();
|
||||
}
|
||||
@ -268,6 +318,10 @@ public class StandardValidators {
|
||||
return new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").build();
|
||||
}
|
||||
|
||||
try {
|
||||
final String evaluatedInput = context.newPropertyValue(input).evaluateAttributeExpressions().getValue();
|
||||
new URL(evaluatedInput);
|
||||
@ -292,6 +346,10 @@ public class StandardValidators {
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").build();
|
||||
}
|
||||
|
||||
final ValidationResult vr = DATA_SIZE_VALIDATOR.validate(subject, input, context);
|
||||
if(!vr.isValid()){
|
||||
return vr;
|
||||
@ -313,6 +371,10 @@ public class StandardValidators {
|
||||
return new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").build();
|
||||
}
|
||||
|
||||
final boolean matches = pattern.matcher(input).matches();
|
||||
return new ValidationResult.Builder()
|
||||
.input(input)
|
||||
@ -394,6 +456,10 @@ public class StandardValidators {
|
||||
return new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").build();
|
||||
}
|
||||
|
||||
String reason = null;
|
||||
try {
|
||||
final long longVal = Long.parseLong(input);
|
||||
@ -436,6 +502,10 @@ public class StandardValidators {
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").build();
|
||||
}
|
||||
|
||||
if (input == null) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).valid(false).explanation("Time Period cannot be null").build();
|
||||
}
|
||||
@ -469,6 +539,10 @@ public class StandardValidators {
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String value, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build();
|
||||
}
|
||||
|
||||
final String substituted;
|
||||
if (allowEL) {
|
||||
try {
|
||||
@ -500,6 +574,10 @@ public class StandardValidators {
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String value, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(value).explanation("Expression Language Present").valid(true).build();
|
||||
}
|
||||
|
||||
final String substituted;
|
||||
if (allowEL) {
|
||||
try {
|
||||
@ -541,6 +619,10 @@ public class StandardValidators {
|
||||
return new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
|
||||
if ( context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input) ) {
|
||||
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").build();
|
||||
}
|
||||
|
||||
final ControllerService svc = context.getControllerServiceLookup().getControllerService(input);
|
||||
|
||||
if (svc == null) {
|
||||
|
@ -21,10 +21,11 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.nifi.components.ValidationContext;
|
||||
import org.apache.nifi.components.ValidationResult;
|
||||
import org.apache.nifi.components.Validator;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class TestStandardValidators {
|
||||
|
||||
@ -33,22 +34,24 @@ public class TestStandardValidators {
|
||||
Validator val = StandardValidators.createTimePeriodValidator(1L, TimeUnit.SECONDS, Long.MAX_VALUE, TimeUnit.NANOSECONDS);
|
||||
ValidationResult vr;
|
||||
|
||||
vr = val.validate("TimePeriodTest", "0 sense made", null);
|
||||
final ValidationContext validationContext = Mockito.mock(ValidationContext.class);
|
||||
|
||||
vr = val.validate("TimePeriodTest", "0 sense made", validationContext);
|
||||
assertFalse(vr.isValid());
|
||||
|
||||
vr = val.validate("TimePeriodTest", null, null);
|
||||
vr = val.validate("TimePeriodTest", null, validationContext);
|
||||
assertFalse(vr.isValid());
|
||||
|
||||
vr = val.validate("TimePeriodTest", "0 secs", null);
|
||||
vr = val.validate("TimePeriodTest", "0 secs", validationContext);
|
||||
assertFalse(vr.isValid());
|
||||
|
||||
vr = val.validate("TimePeriodTest", "999 millis", null);
|
||||
vr = val.validate("TimePeriodTest", "999 millis", validationContext);
|
||||
assertFalse(vr.isValid());
|
||||
|
||||
vr = val.validate("TimePeriodTest", "999999999 nanos", null);
|
||||
vr = val.validate("TimePeriodTest", "999999999 nanos", validationContext);
|
||||
assertFalse(vr.isValid());
|
||||
|
||||
vr = val.validate("TimePeriodTest", "1 sec", null);
|
||||
vr = val.validate("TimePeriodTest", "1 sec", validationContext);
|
||||
assertTrue(vr.isValid());
|
||||
}
|
||||
|
||||
@ -57,28 +60,29 @@ public class TestStandardValidators {
|
||||
Validator val = StandardValidators.createDataSizeBoundsValidator(100, 1000);
|
||||
ValidationResult vr;
|
||||
|
||||
vr = val.validate("DataSizeBounds", "5 GB", null);
|
||||
final ValidationContext validationContext = Mockito.mock(ValidationContext.class);
|
||||
vr = val.validate("DataSizeBounds", "5 GB", validationContext);
|
||||
assertFalse(vr.isValid());
|
||||
|
||||
vr = val.validate("DataSizeBounds", "0 B", null);
|
||||
vr = val.validate("DataSizeBounds", "0 B", validationContext);
|
||||
assertFalse(vr.isValid());
|
||||
|
||||
vr = val.validate("DataSizeBounds", "99 B", null);
|
||||
vr = val.validate("DataSizeBounds", "99 B", validationContext);
|
||||
assertFalse(vr.isValid());
|
||||
|
||||
vr = val.validate("DataSizeBounds", "100 B", null);
|
||||
vr = val.validate("DataSizeBounds", "100 B", validationContext);
|
||||
assertTrue(vr.isValid());
|
||||
|
||||
vr = val.validate("DataSizeBounds", "999 B", null);
|
||||
vr = val.validate("DataSizeBounds", "999 B", validationContext);
|
||||
assertTrue(vr.isValid());
|
||||
|
||||
vr = val.validate("DataSizeBounds", "1000 B", null);
|
||||
vr = val.validate("DataSizeBounds", "1000 B", validationContext);
|
||||
assertTrue(vr.isValid());
|
||||
|
||||
vr = val.validate("DataSizeBounds", "1001 B", null);
|
||||
vr = val.validate("DataSizeBounds", "1001 B", validationContext);
|
||||
assertFalse(vr.isValid());
|
||||
|
||||
vr = val.validate("DataSizeBounds", "water", null);
|
||||
vr = val.validate("DataSizeBounds", "water", validationContext);
|
||||
assertFalse(vr.isValid());
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
<requiredProperties>
|
||||
<requiredProperty key="artifactBaseName" />
|
||||
<requiredProperty key="nifiVersion" >
|
||||
<defaultValue>0.0.1-incubating-SNAPSHOT</defaultValue>
|
||||
<defaultValue>0.1.0-incubating-SNAPSHOT</defaultValue>
|
||||
</requiredProperty>
|
||||
<requiredProperty key="package">
|
||||
<defaultValue>${groupId}.processors.${artifactBaseName}</defaultValue>
|
||||
|
@ -20,8 +20,13 @@ import org.apache.nifi.components.PropertyDescriptor;
|
||||
import org.apache.nifi.components.PropertyValue;
|
||||
import org.apache.nifi.flowfile.FlowFile;
|
||||
import org.apache.nifi.processor.*;
|
||||
import org.apache.nifi.annotation.behavior.ReadsAttribute;
|
||||
import org.apache.nifi.annotation.behavior.ReadsAttributes;
|
||||
import org.apache.nifi.annotation.behavior.WritesAttribute;
|
||||
import org.apache.nifi.annotation.behavior.WritesAttributes;
|
||||
import org.apache.nifi.annotation.lifecycle.OnScheduled;
|
||||
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
||||
import org.apache.nifi.annotation.documentation.SeeAlso;
|
||||
import org.apache.nifi.annotation.documentation.Tags;
|
||||
import org.apache.nifi.processor.exception.ProcessException;
|
||||
import org.apache.nifi.processor.util.StandardValidators;
|
||||
@ -30,6 +35,9 @@ import java.util.*;
|
||||
|
||||
@Tags({"example"})
|
||||
@CapabilityDescription("Provide a description")
|
||||
@SeeAlso({})
|
||||
@ReadsAttributes({@ReadsAttribute(attribute="", description="")})
|
||||
@WritesAttributes({@WritesAttribute(attribute="", description="")})
|
||||
public class MyProcessor extends AbstractProcessor {
|
||||
|
||||
public static final PropertyDescriptor MY_PROPERTY = new PropertyDescriptor
|
||||
|
@ -1,96 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>GetFile</title>
|
||||
<link rel="stylesheet" href="../../css/component-usage.css" type="text/css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Processor Documentation ================================================== -->
|
||||
<h2>Description:</h2>
|
||||
|
||||
<p>Replace with a description.</p>
|
||||
|
||||
<p>
|
||||
<strong>Uses Attributes:</strong>
|
||||
</p>
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
<strong>Modifies Attributes:</strong>
|
||||
</p>
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
<strong>Properties:</strong>
|
||||
</p>
|
||||
|
||||
<p>In the list below, the names of required properties appear
|
||||
in bold. Any other properties (not in bold) are considered optional.
|
||||
If a property has a default value, it is indicated. If a property
|
||||
supports the use of the NiFi Expression Language (or simply,
|
||||
"expression language"), that is also indicated.</p>
|
||||
<ul>
|
||||
<li><strong>My Property</strong>
|
||||
<ul>
|
||||
<li>An example property.</li>
|
||||
<li>Default value: no default</li>
|
||||
<li>Supports expression language: false</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<strong>Relationships:</strong>
|
||||
</p>
|
||||
<ul>
|
||||
<li>my_relationship
|
||||
<ul>
|
||||
<li>An example relationship.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -16,9 +16,13 @@
|
||||
*/
|
||||
package org.apache.nifi.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.nifi.attribute.expression.language.Query;
|
||||
import org.apache.nifi.attribute.expression.language.Query.Range;
|
||||
import org.apache.nifi.attribute.expression.language.StandardExpressionLanguageCompiler;
|
||||
import org.apache.nifi.components.PropertyDescriptor;
|
||||
import org.apache.nifi.components.PropertyValue;
|
||||
@ -30,9 +34,16 @@ import org.apache.nifi.expression.ExpressionLanguageCompiler;
|
||||
public class MockValidationContext implements ValidationContext, ControllerServiceLookup {
|
||||
|
||||
private final MockProcessContext context;
|
||||
private final Map<String, Boolean> expressionLanguageSupported;
|
||||
|
||||
public MockValidationContext(final MockProcessContext processContext) {
|
||||
this.context = processContext;
|
||||
|
||||
final Map<PropertyDescriptor, String> properties = processContext.getProperties();
|
||||
expressionLanguageSupported = new HashMap<>(properties.size());
|
||||
for ( final PropertyDescriptor descriptor : properties.keySet() ) {
|
||||
expressionLanguageSupported.put(descriptor.getName(), descriptor.isExpressionLanguageSupported());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -90,4 +101,20 @@ public class MockValidationContext implements ValidationContext, ControllerServi
|
||||
public boolean isControllerServiceEnabled(final ControllerService service) {
|
||||
return context.isControllerServiceEnabled(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpressionLanguagePresent(final String value) {
|
||||
if ( value == null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<Range> elRanges = Query.extractExpressionRanges(value);
|
||||
return (elRanges != null && !elRanges.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpressionLanguageSupported(final String propertyName) {
|
||||
final Boolean supported = expressionLanguageSupported.get(propertyName);
|
||||
return Boolean.TRUE.equals(supported);
|
||||
}
|
||||
}
|
||||
|
@ -137,14 +137,14 @@ public class StandardProcessorTestRunner implements TestRunner {
|
||||
private static void detectDeprecatedAnnotations(final Processor processor) {
|
||||
for ( final Class<? extends Annotation> annotationClass : deprecatedTypeAnnotations ) {
|
||||
if ( processor.getClass().isAnnotationPresent(annotationClass) ) {
|
||||
logger.warn("Processor is using deprecated Annotation " + annotationClass.getCanonicalName());
|
||||
Assert.fail("Processor is using deprecated Annotation " + annotationClass.getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
for ( final Class<? extends Annotation> annotationClass : deprecatedMethodAnnotations ) {
|
||||
for ( final Method method : processor.getClass().getMethods() ) {
|
||||
if ( method.isAnnotationPresent(annotationClass) ) {
|
||||
logger.warn("Processor is using deprecated Annotation " + annotationClass.getCanonicalName() + " for method " + method);
|
||||
Assert.fail("Processor is using deprecated Annotation " + annotationClass.getCanonicalName() + " for method " + method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -302,12 +302,13 @@ public class HtmlDocumentationWriter implements DocumentationWriter {
|
||||
List<PropertyDescriptor> properties = configurableComponent.getPropertyDescriptors();
|
||||
if (properties.size() > 0) {
|
||||
xmlStreamWriter.writeStartElement("table");
|
||||
xmlStreamWriter.writeAttribute("id", "properties");
|
||||
|
||||
// write the header row
|
||||
xmlStreamWriter.writeStartElement("tr");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Name");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Default Value");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Valid Values");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Allowable Values");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Description");
|
||||
xmlStreamWriter.writeEndElement();
|
||||
|
||||
@ -315,6 +316,7 @@ public class HtmlDocumentationWriter implements DocumentationWriter {
|
||||
for (PropertyDescriptor property : properties) {
|
||||
xmlStreamWriter.writeStartElement("tr");
|
||||
xmlStreamWriter.writeStartElement("td");
|
||||
xmlStreamWriter.writeAttribute("id", "name");
|
||||
if (property.isRequired()) {
|
||||
writeSimpleElement(xmlStreamWriter, "strong", property.getDisplayName());
|
||||
} else {
|
||||
@ -322,11 +324,13 @@ public class HtmlDocumentationWriter implements DocumentationWriter {
|
||||
}
|
||||
|
||||
xmlStreamWriter.writeEndElement();
|
||||
writeSimpleElement(xmlStreamWriter, "td", property.getDefaultValue());
|
||||
writeSimpleElement(xmlStreamWriter, "td", property.getDefaultValue(), false, "default-value");
|
||||
xmlStreamWriter.writeStartElement("td");
|
||||
xmlStreamWriter.writeAttribute("id", "allowable-values");
|
||||
writeValidValues(xmlStreamWriter, property);
|
||||
xmlStreamWriter.writeEndElement();
|
||||
xmlStreamWriter.writeStartElement("td");
|
||||
xmlStreamWriter.writeAttribute("id", "description");
|
||||
if (property.getDescription() != null && property.getDescription().trim().length() > 0) {
|
||||
xmlStreamWriter.writeCharacters(property.getDescription());
|
||||
} else {
|
||||
@ -372,6 +376,7 @@ public class HtmlDocumentationWriter implements DocumentationWriter {
|
||||
xmlStreamWriter
|
||||
.writeCharacters("Dynamic Properties allow the user to specify both the name and value of a property.");
|
||||
xmlStreamWriter.writeStartElement("table");
|
||||
xmlStreamWriter.writeAttribute("id", "dynamic-properties");
|
||||
xmlStreamWriter.writeStartElement("tr");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Name");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Value");
|
||||
@ -379,8 +384,8 @@ public class HtmlDocumentationWriter implements DocumentationWriter {
|
||||
xmlStreamWriter.writeEndElement();
|
||||
for (final DynamicProperty dynamicProperty : dynamicProperties) {
|
||||
xmlStreamWriter.writeStartElement("tr");
|
||||
writeSimpleElement(xmlStreamWriter, "td", dynamicProperty.name());
|
||||
writeSimpleElement(xmlStreamWriter, "td", dynamicProperty.value());
|
||||
writeSimpleElement(xmlStreamWriter, "td", dynamicProperty.name(), false, "name");
|
||||
writeSimpleElement(xmlStreamWriter, "td", dynamicProperty.value(), false, "value");
|
||||
xmlStreamWriter.writeStartElement("td");
|
||||
xmlStreamWriter.writeCharacters(dynamicProperty.description());
|
||||
if (dynamicProperty.supportsExpressionLanguage()) {
|
||||
@ -489,7 +494,26 @@ public class HtmlDocumentationWriter implements DocumentationWriter {
|
||||
*/
|
||||
protected final static void writeSimpleElement(final XMLStreamWriter writer, final String elementName,
|
||||
final String characters, boolean strong) throws XMLStreamException {
|
||||
writer.writeStartElement(elementName);
|
||||
writeSimpleElement(writer, elementName, characters, strong, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a begin element, an id attribute(if specified), then text, then end element for
|
||||
* element of the users choosing. Example: <p id="p-id">text</p>
|
||||
*
|
||||
* @param writer the stream writer to use
|
||||
* @param elementName the name of the element
|
||||
* @param characters the text of the element
|
||||
* @param strong whether to bold the text of the element or not
|
||||
* @param id the id of the element. specifying null will cause no element to be written.
|
||||
* @throws XMLStreamException
|
||||
*/
|
||||
protected final static void writeSimpleElement(final XMLStreamWriter writer, final String elementName,
|
||||
final String characters, boolean strong, String id) throws XMLStreamException {
|
||||
writer.writeStartElement(elementName);
|
||||
if (id != null) {
|
||||
writer.writeAttribute("id", id);
|
||||
}
|
||||
if (strong) {
|
||||
writer.writeStartElement("strong");
|
||||
}
|
||||
|
@ -83,6 +83,7 @@ public class HtmlProcessorDocumentationWriter extends HtmlDocumentationWriter {
|
||||
writeSimpleElement(xmlStreamWriter, "h3", "Reads Attributes: ");
|
||||
if (attributesRead.size() > 0) {
|
||||
xmlStreamWriter.writeStartElement("table");
|
||||
xmlStreamWriter.writeAttribute("id", "reads-attributes");
|
||||
xmlStreamWriter.writeStartElement("tr");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Name");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Description");
|
||||
@ -120,6 +121,7 @@ public class HtmlProcessorDocumentationWriter extends HtmlDocumentationWriter {
|
||||
writeSimpleElement(xmlStreamWriter, "h3", "Writes Attributes: ");
|
||||
if (attributesRead.size() > 0) {
|
||||
xmlStreamWriter.writeStartElement("table");
|
||||
xmlStreamWriter.writeAttribute("id", "writes-attributes");
|
||||
xmlStreamWriter.writeStartElement("tr");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Name");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Description");
|
||||
@ -203,6 +205,7 @@ public class HtmlProcessorDocumentationWriter extends HtmlDocumentationWriter {
|
||||
|
||||
if (processor.getRelationships().size() > 0) {
|
||||
xmlStreamWriter.writeStartElement("table");
|
||||
xmlStreamWriter.writeAttribute("id", "relationships");
|
||||
xmlStreamWriter.writeStartElement("tr");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Name");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Description");
|
||||
@ -236,6 +239,7 @@ public class HtmlProcessorDocumentationWriter extends HtmlDocumentationWriter {
|
||||
xmlStreamWriter.writeStartElement("p");
|
||||
xmlStreamWriter.writeCharacters("A Dynamic Relationship may be created based on how the user configures the Processor.");
|
||||
xmlStreamWriter.writeStartElement("table");
|
||||
xmlStreamWriter.writeAttribute("id", "dynamic-relationships");
|
||||
xmlStreamWriter.writeStartElement("tr");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Name");
|
||||
writeSimpleElement(xmlStreamWriter, "th", "Description");
|
||||
|
@ -791,34 +791,38 @@ public class FileSystemRepository implements ContentRepository {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Get all of the Destructable Claims and bin them based on their Container. We do this
|
||||
// because the Container generally maps to a physical partition on the disk, so we want a few
|
||||
// different threads hitting the different partitions but don't want multiple threads hitting
|
||||
// the same partition.
|
||||
final List<ContentClaim> toDestroy = new ArrayList<>();
|
||||
while (true) {
|
||||
toDestroy.clear();
|
||||
contentClaimManager.drainDestructableClaims(toDestroy, 10000);
|
||||
if (toDestroy.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final ContentClaim claim : toDestroy) {
|
||||
final String container = claim.getContainer();
|
||||
final BlockingQueue<ContentClaim> claimQueue = reclaimable.get(container);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
if (claimQueue.offer(claim, 10, TimeUnit.MINUTES)) {
|
||||
break;
|
||||
} else {
|
||||
LOG.warn("Failed to clean up {} because old claims aren't being cleaned up fast enough. This Content Claim will remain in the Content Repository until NiFi is restarted, at which point it will be cleaned up", claim);
|
||||
try {
|
||||
// Get all of the Destructable Claims and bin them based on their Container. We do this
|
||||
// because the Container generally maps to a physical partition on the disk, so we want a few
|
||||
// different threads hitting the different partitions but don't want multiple threads hitting
|
||||
// the same partition.
|
||||
final List<ContentClaim> toDestroy = new ArrayList<>();
|
||||
while (true) {
|
||||
toDestroy.clear();
|
||||
contentClaimManager.drainDestructableClaims(toDestroy, 10000);
|
||||
if (toDestroy.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final ContentClaim claim : toDestroy) {
|
||||
final String container = claim.getContainer();
|
||||
final BlockingQueue<ContentClaim> claimQueue = reclaimable.get(container);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
if (claimQueue.offer(claim, 10, TimeUnit.MINUTES)) {
|
||||
break;
|
||||
} else {
|
||||
LOG.warn("Failed to clean up {} because old claims aren't being cleaned up fast enough. This Content Claim will remain in the Content Repository until NiFi is restarted, at which point it will be cleaned up", claim);
|
||||
}
|
||||
}
|
||||
} catch (final InterruptedException ie) {
|
||||
LOG.warn("Failed to clean up {} because thread was interrupted", claim);
|
||||
}
|
||||
} catch (final InterruptedException ie) {
|
||||
LOG.warn("Failed to clean up {} because thread was interrupted", claim);
|
||||
}
|
||||
}
|
||||
} catch (final Throwable t) {
|
||||
LOG.error("Failed to cleanup content claims due to {}", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1198,23 +1202,23 @@ public class FileSystemRepository implements ContentRepository {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (oldestArchiveDate.get() > (System.currentTimeMillis() - maxArchiveMillis)) {
|
||||
final Long minRequiredSpace = minUsableContainerBytesForArchive.get(containerName);
|
||||
if (minRequiredSpace == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final long usableSpace = getContainerUsableSpace(containerName);
|
||||
if (usableSpace > minRequiredSpace) {
|
||||
try {
|
||||
if (oldestArchiveDate.get() > (System.currentTimeMillis() - maxArchiveMillis)) {
|
||||
final Long minRequiredSpace = minUsableContainerBytesForArchive.get(containerName);
|
||||
if (minRequiredSpace == null) {
|
||||
return;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to determine space available in container {}; will attempt to cleanup archive", containerName);
|
||||
|
||||
try {
|
||||
final long usableSpace = getContainerUsableSpace(containerName);
|
||||
if (usableSpace > minRequiredSpace) {
|
||||
return;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to determine space available in container {}; will attempt to cleanup archive", containerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.currentThread().setName("Cleanup Archive for " + containerName);
|
||||
final long oldestContainerArchive;
|
||||
|
||||
|
@ -18,10 +18,12 @@ package org.apache.nifi.processor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.nifi.attribute.expression.language.PreparedQuery;
|
||||
import org.apache.nifi.attribute.expression.language.Query;
|
||||
import org.apache.nifi.attribute.expression.language.Query.Range;
|
||||
import org.apache.nifi.attribute.expression.language.StandardExpressionLanguageCompiler;
|
||||
import org.apache.nifi.components.PropertyDescriptor;
|
||||
import org.apache.nifi.components.PropertyValue;
|
||||
@ -37,6 +39,7 @@ public class StandardValidationContext implements ValidationContext {
|
||||
private final ControllerServiceProvider controllerServiceProvider;
|
||||
private final Map<PropertyDescriptor, String> properties;
|
||||
private final Map<PropertyDescriptor, PreparedQuery> preparedQueries;
|
||||
private final Map<String, Boolean> expressionLanguageSupported;
|
||||
private final String annotationData;
|
||||
|
||||
public StandardValidationContext(final ControllerServiceProvider controllerServiceProvider, final Map<PropertyDescriptor, String> properties, final String annotationData) {
|
||||
@ -44,7 +47,7 @@ public class StandardValidationContext implements ValidationContext {
|
||||
this.properties = new HashMap<>(properties);
|
||||
this.annotationData = annotationData;
|
||||
|
||||
preparedQueries = new HashMap<>();
|
||||
preparedQueries = new HashMap<>(properties.size());
|
||||
for (final Map.Entry<PropertyDescriptor, String> entry : properties.entrySet()) {
|
||||
final PropertyDescriptor desc = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
@ -56,6 +59,10 @@ public class StandardValidationContext implements ValidationContext {
|
||||
preparedQueries.put(desc, pq);
|
||||
}
|
||||
|
||||
expressionLanguageSupported = new HashMap<>(properties.size());
|
||||
for ( final PropertyDescriptor descriptor : properties.keySet() ) {
|
||||
expressionLanguageSupported.put(descriptor.getName(), descriptor.isExpressionLanguageSupported());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -94,4 +101,20 @@ public class StandardValidationContext implements ValidationContext {
|
||||
public ControllerServiceLookup getControllerServiceLookup() {
|
||||
return controllerServiceProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpressionLanguagePresent(final String value) {
|
||||
if ( value == null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<Range> elRanges = Query.extractExpressionRanges(value);
|
||||
return (elRanges != null && !elRanges.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpressionLanguageSupported(final String propertyName) {
|
||||
final Boolean supported = expressionLanguageSupported.get(propertyName);
|
||||
return Boolean.TRUE.equals(supported);
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
@ -63,7 +64,6 @@ import org.apache.nifi.reporting.BulletinRepository;
|
||||
import org.apache.nifi.reporting.Severity;
|
||||
import org.apache.nifi.scheduling.SchedulingStrategy;
|
||||
import org.apache.nifi.user.NiFiUser;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -126,9 +126,14 @@ public class StandardRootGroupPort extends AbstractPort implements RootGroupPort
|
||||
|
||||
@Override
|
||||
public void onTrigger(final ProcessContext context, final ProcessSessionFactory sessionFactory) {
|
||||
final FlowFileRequest flowFileRequest = requestQueue.poll();
|
||||
final FlowFileRequest flowFileRequest;
|
||||
try {
|
||||
flowFileRequest = requestQueue.poll(100, TimeUnit.MILLISECONDS);
|
||||
} catch (final InterruptedException ie) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( flowFileRequest == null ) {
|
||||
context.yield();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -113,6 +113,11 @@
|
||||
<artifactId>nifi-client-dto</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-content-access</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-custom-ui-utilities</artifactId>
|
||||
@ -152,6 +157,11 @@
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-docs</artifactId>
|
||||
<type>war</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-content-viewer</artifactId>
|
||||
<type>war</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
|
@ -52,6 +52,7 @@ import org.apache.nifi.util.NiFiProperties;
|
||||
import org.apache.nifi.web.NiFiWebContext;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.web.ContentAccess;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
@ -99,7 +100,9 @@ public class JettyServer implements NiFiServer {
|
||||
private ExtensionMapping extensionMapping;
|
||||
private WebAppContext webApiContext;
|
||||
private WebAppContext webDocsContext;
|
||||
private WebAppContext webContentViewerContext;
|
||||
private Collection<WebAppContext> customUiWebContexts;
|
||||
private Collection<WebAppContext> contentViewerWebContexts;
|
||||
private final NiFiProperties props;
|
||||
|
||||
/**
|
||||
@ -164,6 +167,7 @@ public class JettyServer implements NiFiServer {
|
||||
File webApiWar = null;
|
||||
File webErrorWar = null;
|
||||
File webDocsWar = null;
|
||||
File webContentViewerWar = null;
|
||||
List<File> otherWars = new ArrayList<>();
|
||||
for (File war : warToNarWorkingDirectoryLookup.keySet()) {
|
||||
if (war.getName().toLowerCase().startsWith("nifi-web-api")) {
|
||||
@ -172,6 +176,8 @@ public class JettyServer implements NiFiServer {
|
||||
webErrorWar = war;
|
||||
} else if (war.getName().toLowerCase().startsWith("nifi-web-docs")) {
|
||||
webDocsWar = war;
|
||||
} else if (war.getName().toLowerCase().startsWith("nifi-web-content-viewer")) {
|
||||
webContentViewerWar = war;
|
||||
} else if (war.getName().toLowerCase().startsWith("nifi-web")) {
|
||||
webUiWar = war;
|
||||
} else {
|
||||
@ -188,23 +194,29 @@ public class JettyServer implements NiFiServer {
|
||||
throw new RuntimeException("Unable to load nifi-web-docs WAR");
|
||||
} else if (webErrorWar == null) {
|
||||
throw new RuntimeException("Unable to load nifi-web-error WAR");
|
||||
} else if (webContentViewerWar == null) {
|
||||
throw new RuntimeException("Unable to load nifi-web-content-viewer WAR");
|
||||
}
|
||||
|
||||
// handlers for each war and init params for the web api
|
||||
final HandlerCollection handlers = new HandlerCollection();
|
||||
final Map<String, String> initParams = new HashMap<>();
|
||||
final Map<String, String> customUiMappings = new HashMap<>();
|
||||
final Map<String, String> mimeTypeMappings = new HashMap<>();
|
||||
final ClassLoader frameworkClassLoader = getClass().getClassLoader();
|
||||
final ClassLoader jettyClassLoader = frameworkClassLoader.getParent();
|
||||
|
||||
// deploy the other wars
|
||||
if (CollectionUtils.isNotEmpty(otherWars)) {
|
||||
customUiWebContexts = new ArrayList<>();
|
||||
contentViewerWebContexts = new ArrayList<>();
|
||||
|
||||
for (File war : otherWars) {
|
||||
// see if this war is a custom processor ui
|
||||
List<String> customUiProcessorTypes = getCustomUiProcessorTypes(war);
|
||||
List<String> customUiProcessorTypes = getWarExtensions(war, "META-INF/nifi-processor");
|
||||
List<String> contentViewerMimeTypes = getWarExtensions(war, "META-INF/nifi-content-viewer");
|
||||
|
||||
// only include wars that are for custom processor ui's
|
||||
if (CollectionUtils.isNotEmpty(customUiProcessorTypes)) {
|
||||
// only include wars that are for extensions
|
||||
if (!customUiProcessorTypes.isEmpty() || !contentViewerMimeTypes.isEmpty()) {
|
||||
String warName = StringUtils.substringBeforeLast(war.getName(), ".");
|
||||
String warContextPath = String.format("/%s", warName);
|
||||
|
||||
@ -216,19 +228,27 @@ public class JettyServer implements NiFiServer {
|
||||
narClassLoaderForWar = jettyClassLoader;
|
||||
}
|
||||
|
||||
// create the custom ui web app context
|
||||
WebAppContext customUiContext = loadWar(war, warContextPath, narClassLoaderForWar);
|
||||
// create the extension web app context
|
||||
WebAppContext extensionUiContext = loadWar(war, warContextPath, narClassLoaderForWar);
|
||||
|
||||
// hold on to a reference to all custom ui web app contexts
|
||||
customUiWebContexts.add(customUiContext);
|
||||
// also store it by type so we can populate the appropriate initialization parameters
|
||||
if (!customUiProcessorTypes.isEmpty()) {
|
||||
customUiWebContexts.add(extensionUiContext);
|
||||
} else {
|
||||
// record the mime type to web app mapping (need to handle type collision)
|
||||
contentViewerWebContexts.add(extensionUiContext);
|
||||
}
|
||||
|
||||
// include custom ui web context in the handlers
|
||||
handlers.addHandler(customUiContext);
|
||||
handlers.addHandler(extensionUiContext);
|
||||
|
||||
// add the initialization paramters
|
||||
for (String customUiProcessorType : customUiProcessorTypes) {
|
||||
// map the processor type to the custom ui path
|
||||
initParams.put(customUiProcessorType, warContextPath);
|
||||
customUiMappings.put(customUiProcessorType, warContextPath);
|
||||
}
|
||||
for (final String contentViewerMimeType : contentViewerMimeTypes) {
|
||||
mimeTypeMappings.put(contentViewerMimeType, warContextPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -239,10 +259,14 @@ public class JettyServer implements NiFiServer {
|
||||
|
||||
// load the web api app
|
||||
webApiContext = loadWar(webApiWar, "/nifi-api", frameworkClassLoader);
|
||||
Map<String, String> webApiInitParams = webApiContext.getInitParams();
|
||||
webApiInitParams.putAll(initParams);
|
||||
webApiContext.getInitParams().putAll(customUiMappings);
|
||||
handlers.addHandler(webApiContext);
|
||||
|
||||
// load the content viewer app
|
||||
webContentViewerContext = loadWar(webContentViewerWar, "/nifi-content-viewer", frameworkClassLoader);
|
||||
webContentViewerContext.getInitParams().putAll(mimeTypeMappings);
|
||||
handlers.addHandler(webContentViewerContext);
|
||||
|
||||
// create a web app for the docs
|
||||
final String docsContextPath = "/nifi-docs";
|
||||
|
||||
@ -292,18 +316,18 @@ public class JettyServer implements NiFiServer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the processor types that the specified war file is a custom UI for.
|
||||
* Returns the extension in the specified WAR using the specified path.
|
||||
*
|
||||
* @param warFile
|
||||
* @param war
|
||||
* @return
|
||||
*/
|
||||
private List<String> getCustomUiProcessorTypes(final File warFile) {
|
||||
private List<String> getWarExtensions(final File war, final String path) {
|
||||
List<String> processorTypes = new ArrayList<>();
|
||||
JarFile jarFile = null;
|
||||
try {
|
||||
// load the jar file and attempt to find the nifi-processor entry
|
||||
jarFile = new JarFile(warFile);
|
||||
JarEntry jarEntry = jarFile.getJarEntry("META-INF/nifi-processor");
|
||||
jarFile = new JarFile(war);
|
||||
JarEntry jarEntry = jarFile.getJarEntry(path);
|
||||
|
||||
// ensure the nifi-processor entry was found
|
||||
if (jarEntry != null) {
|
||||
@ -320,7 +344,7 @@ public class JettyServer implements NiFiServer {
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
logger.warn(String.format("Unable to inspect %s for a custom processor UI.", warFile));
|
||||
logger.warn(String.format("Unable to inspect %s for a custom processor UI.", war));
|
||||
} finally {
|
||||
try {
|
||||
// close the jar file - which closes all input streams obtained via getInputStream above
|
||||
@ -537,20 +561,48 @@ public class JettyServer implements NiFiServer {
|
||||
|
||||
// ensure the appropriate wars deployed successfully before injecting the NiFi context and security filters -
|
||||
// this must be done after starting the server (and ensuring there were no start up failures)
|
||||
if (webApiContext != null && CollectionUtils.isNotEmpty(customUiWebContexts)) {
|
||||
if (webApiContext != null) {
|
||||
final ServletContext webApiServletContext = webApiContext.getServletHandler().getServletContext();
|
||||
final WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(webApiServletContext);
|
||||
final NiFiWebContext NiFiWebContext = webApplicationContext.getBean("nifiWebContext", NiFiWebContext.class);
|
||||
|
||||
for (final WebAppContext customUiContext : customUiWebContexts) {
|
||||
// set the NiFi context in each custom ui servlet context
|
||||
final ServletContext customUiServletContext = customUiContext.getServletHandler().getServletContext();
|
||||
customUiServletContext.setAttribute("nifi-web-context", NiFiWebContext);
|
||||
if (CollectionUtils.isNotEmpty(customUiWebContexts)) {
|
||||
final NiFiWebContext niFiWebContext = webApplicationContext.getBean("nifiWebContext", NiFiWebContext.class);
|
||||
|
||||
for (final WebAppContext customUiContext : customUiWebContexts) {
|
||||
// set the NiFi context in each custom ui servlet context
|
||||
final ServletContext customUiServletContext = customUiContext.getServletHandler().getServletContext();
|
||||
customUiServletContext.setAttribute("nifi-web-context", niFiWebContext);
|
||||
|
||||
// add the security filter to any custom ui wars
|
||||
// add the security filter to any custom ui wars
|
||||
final FilterHolder securityFilter = webApiContext.getServletHandler().getFilter("springSecurityFilterChain");
|
||||
if (securityFilter != null) {
|
||||
customUiContext.addFilter(securityFilter, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(contentViewerWebContexts)) {
|
||||
for (final WebAppContext contentViewerContext : contentViewerWebContexts) {
|
||||
// add the security filter to any content viewer wars
|
||||
final FilterHolder securityFilter = webApiContext.getServletHandler().getFilter("springSecurityFilterChain");
|
||||
if (securityFilter != null) {
|
||||
contentViewerContext.addFilter(securityFilter, "/*", EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ensure the web content viewer war was loaded
|
||||
if (webContentViewerContext != null) {
|
||||
final ContentAccess contentAccess = webApplicationContext.getBean("contentAccess", ContentAccess.class);
|
||||
|
||||
// add the content access
|
||||
final ServletContext webContentViewerServletContext = webContentViewerContext.getServletHandler().getServletContext();
|
||||
webContentViewerServletContext.setAttribute("nifi-content-access", contentAccess);
|
||||
|
||||
// add the security filter to the content viewer controller
|
||||
final FilterHolder securityFilter = webApiContext.getServletHandler().getFilter("springSecurityFilterChain");
|
||||
if (securityFilter != null) {
|
||||
customUiContext.addFilter(securityFilter, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
webContentViewerContext.addFilter(securityFilter, "/*", EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -560,7 +612,7 @@ public class JettyServer implements NiFiServer {
|
||||
final ServletContext webDocsServletContext = webDocsContext.getServletHandler().getServletContext();
|
||||
webDocsServletContext.setAttribute("nifi-extension-mapping", extensionMapping);
|
||||
}
|
||||
|
||||
|
||||
// if this nifi is a node in a cluster, start the flow service and load the flow - the
|
||||
// flow service is loaded here for clustered nodes because the loading of the flow will
|
||||
// initialize the connection between the node and the NCM. if the node connects (starts
|
||||
|
@ -102,6 +102,11 @@
|
||||
<artifactId>nifi-web-utils</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-content-access</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-client-dto</artifactId>
|
||||
|
@ -65,7 +65,6 @@ import org.apache.nifi.web.api.dto.status.ControllerStatusDTO;
|
||||
import org.apache.nifi.web.api.dto.status.NodeStatusDTO;
|
||||
import org.apache.nifi.web.api.dto.status.ProcessGroupStatusDTO;
|
||||
import org.apache.nifi.web.api.dto.status.StatusHistoryDTO;
|
||||
import org.apache.nifi.web.util.DownloadableContent;
|
||||
|
||||
/**
|
||||
* Defines the NiFiServiceFacade interface.
|
||||
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.web;
|
||||
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import com.sun.jersey.core.util.MultivaluedMapImpl;
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.ws.rs.HttpMethod;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.cluster.manager.NodeResponse;
|
||||
import org.apache.nifi.cluster.manager.exception.UnknownNodeException;
|
||||
import org.apache.nifi.cluster.manager.impl.WebClusterManager;
|
||||
import org.apache.nifi.cluster.node.Node;
|
||||
import org.apache.nifi.cluster.protocol.NodeIdentifier;
|
||||
import org.apache.nifi.controller.repository.claim.ContentDirection;
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
import org.apache.nifi.web.security.user.NiFiUserDetails;
|
||||
import org.apache.nifi.web.util.WebUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class StandardNiFiContentAccess implements ContentAccess {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(StandardNiFiContentAccess.class);
|
||||
public static final String CLIENT_ID_PARAM = "clientId";
|
||||
|
||||
private NiFiProperties properties;
|
||||
private NiFiServiceFacade serviceFacade;
|
||||
private WebClusterManager clusterManager;
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasRole('ROLE_PROVENANCE')")
|
||||
public DownloadableContent getContent(final ContentRequestContext request) {
|
||||
// if clustered, send request to cluster manager
|
||||
if (properties.isClusterManager()) {
|
||||
// get the URI
|
||||
URI dataUri;
|
||||
try {
|
||||
dataUri = new URI(request.getDataUri());
|
||||
} catch (final URISyntaxException use) {
|
||||
throw new ClusterRequestException(use);
|
||||
}
|
||||
|
||||
// set the request parameters
|
||||
final MultivaluedMap<String, String> parameters = new MultivaluedMapImpl();
|
||||
parameters.add(CLIENT_ID_PARAM, request.getClientId());
|
||||
|
||||
// set the headers
|
||||
final Map<String, String> headers = new HashMap<>();
|
||||
if (StringUtils.isNotBlank(request.getProxiedEntitiesChain())) {
|
||||
headers.put("X-ProxiedEntitiesChain", request.getProxiedEntitiesChain());
|
||||
}
|
||||
|
||||
// add the user's authorities (if any) to the headers
|
||||
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null) {
|
||||
final Object userDetailsObj = authentication.getPrincipal();
|
||||
if (userDetailsObj instanceof NiFiUserDetails) {
|
||||
// serialize user details object
|
||||
final String hexEncodedUserDetails = WebUtils.serializeObjectToHex((Serializable) userDetailsObj);
|
||||
|
||||
// put serialized user details in header
|
||||
headers.put("X-ProxiedEntityUserDetails", hexEncodedUserDetails);
|
||||
}
|
||||
}
|
||||
|
||||
// get the target node and ensure it exists
|
||||
final Node targetNode = clusterManager.getNode(request.getClusterNodeId());
|
||||
if (targetNode == null) {
|
||||
throw new UnknownNodeException("The specified cluster node does not exist.");
|
||||
}
|
||||
|
||||
final Set<NodeIdentifier> targetNodes = new HashSet<>();
|
||||
targetNodes.add(targetNode.getNodeId());
|
||||
|
||||
// replicate the request to the specific node
|
||||
final NodeResponse nodeResponse = clusterManager.applyRequest(HttpMethod.GET, dataUri, parameters, headers, targetNodes);
|
||||
final ClientResponse clientResponse = nodeResponse.getClientResponse();
|
||||
final MultivaluedMap<String, String> responseHeaders = clientResponse.getHeaders();
|
||||
|
||||
// get the file name
|
||||
final String contentDisposition = responseHeaders.getFirst("Content-Disposition");
|
||||
final String filename = StringUtils.substringAfterLast(contentDisposition, "filename=");
|
||||
|
||||
// get the content type
|
||||
final String contentType = responseHeaders.getFirst("Content-Type");
|
||||
|
||||
// create the downloadable content
|
||||
return new DownloadableContent(filename, contentType, clientResponse.getEntityInputStream());
|
||||
} else {
|
||||
// example URI: http://localhost:8080/nifi-api/controller/provenance/events/1/content/input
|
||||
final String eventDetails = StringUtils.substringAfterLast(request.getDataUri(), "events/");
|
||||
final String rawEventId = StringUtils.substringBefore(eventDetails, "/content/");
|
||||
final String rawDirection = StringUtils.substringAfterLast(eventDetails, "/content/");
|
||||
|
||||
// get the content type
|
||||
final Long eventId = Long.parseLong(rawEventId);
|
||||
final ContentDirection direction = ContentDirection.valueOf(rawDirection.toUpperCase());
|
||||
return serviceFacade.getContent(eventId, request.getDataUri(), direction);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProperties(NiFiProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public void setServiceFacade(NiFiServiceFacade serviceFacade) {
|
||||
this.serviceFacade = serviceFacade;
|
||||
}
|
||||
|
||||
public void setClusterManager(WebClusterManager clusterManager) {
|
||||
this.clusterManager = clusterManager;
|
||||
}
|
||||
}
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.apache.nifi.web;
|
||||
|
||||
import org.apache.nifi.web.OptimisticLockingManager;
|
||||
import org.apache.nifi.web.ConfigurationSnapshot;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -150,7 +148,6 @@ import org.apache.nifi.web.dao.ProcessorDAO;
|
||||
import org.apache.nifi.web.dao.RemoteProcessGroupDAO;
|
||||
import org.apache.nifi.web.dao.SnippetDAO;
|
||||
import org.apache.nifi.web.dao.TemplateDAO;
|
||||
import org.apache.nifi.web.util.DownloadableContent;
|
||||
import org.apache.nifi.web.util.SnippetUtils;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
@ -89,7 +89,7 @@ public class StandardNiFiWebContext implements NiFiWebContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAnyRole('ROLE_DFM')")
|
||||
@PreAuthorize("hasRole('ROLE_DFM')")
|
||||
public void saveActions(final Collection<ProcessorConfigurationAction> processorActions) {
|
||||
Objects.requireNonNull(processorActions, "Actions cannot be null.");
|
||||
|
||||
|
@ -74,7 +74,7 @@ import org.apache.nifi.web.api.request.ClientIdParameter;
|
||||
import org.apache.nifi.web.api.request.DateTimeParameter;
|
||||
import org.apache.nifi.web.api.request.IntegerParameter;
|
||||
import org.apache.nifi.web.api.request.LongParameter;
|
||||
import org.apache.nifi.web.util.DownloadableContent;
|
||||
import org.apache.nifi.web.DownloadableContent;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.codehaus.enunciate.jaxrs.TypeHint;
|
||||
|
@ -108,7 +108,7 @@ import org.apache.nifi.web.api.dto.search.SearchResultsDTO;
|
||||
import org.apache.nifi.web.api.dto.status.ControllerStatusDTO;
|
||||
import org.apache.nifi.web.api.dto.status.ProcessGroupStatusDTO;
|
||||
import org.apache.nifi.web.api.dto.status.StatusHistoryDTO;
|
||||
import org.apache.nifi.web.util.DownloadableContent;
|
||||
import org.apache.nifi.web.DownloadableContent;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.admin.service.UserService;
|
||||
|
@ -30,6 +30,13 @@
|
||||
<bean class="org.apache.nifi.web.StandardOptimisticLockingManager" />
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<!-- content access -->
|
||||
<bean id="contentAccess" class="org.apache.nifi.web.StandardNiFiContentAccess">
|
||||
<property name="serviceFacade" ref="serviceFacade"/>
|
||||
<property name="properties" ref="nifiProperties"/>
|
||||
<property name="clusterManager" ref="clusterManager"/>
|
||||
</bean>
|
||||
|
||||
<!-- dto factory -->
|
||||
<bean id="dtoFactory" class="org.apache.nifi.web.api.dto.DtoFactory">
|
||||
|
@ -0,0 +1,25 @@
|
||||
<?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</groupId>
|
||||
<artifactId>nifi-web</artifactId>
|
||||
<version>0.1.0-incubating-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-content-access</artifactId>
|
||||
</project>
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.web;
|
||||
|
||||
/**
|
||||
* Provides access to content within NiFi.
|
||||
*
|
||||
* @author unattributed
|
||||
*/
|
||||
public interface ContentAccess {
|
||||
|
||||
/**
|
||||
* Gets the content for the specified claim.
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
DownloadableContent getContent(ContentRequestContext request);
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.web;
|
||||
|
||||
/**
|
||||
* A request for content.
|
||||
*/
|
||||
public interface ContentRequestContext {
|
||||
|
||||
/**
|
||||
* The URI to the data.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getDataUri();
|
||||
|
||||
/**
|
||||
* If clustered, this is the id of the node the data resides on.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getClusterNodeId();
|
||||
|
||||
/**
|
||||
* The client id for the user making the request.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getClientId();
|
||||
|
||||
/**
|
||||
* The proxy chain for the current request, if applicable.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getProxiedEntitiesChain();
|
||||
}
|
@ -14,12 +14,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.web.util;
|
||||
package org.apache.nifi.web;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
*
|
||||
* Represents content that can be downloaded.
|
||||
*/
|
||||
public final class DownloadableContent {
|
||||
|
||||
@ -33,14 +33,29 @@ public final class DownloadableContent {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* The filename of the content.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* The content type of the content.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The content stream.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public InputStream getContent() {
|
||||
return content;
|
||||
}
|
1
nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/.gitignore
vendored
Executable file
1
nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/.gitignore
vendored
Executable file
@ -0,0 +1 @@
|
||||
/target
|
@ -0,0 +1,91 @@
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web</artifactId>
|
||||
<version>0.1.0-incubating-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-content-viewer</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-content-access</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-utils</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>54.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId>
|
||||
<version>1.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet.jsp</groupId>
|
||||
<artifactId>javax.servlet.jsp-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.el</groupId>
|
||||
<artifactId>javax.el-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet.jsp.jstl</groupId>
|
||||
<artifactId>javax.servlet.jsp.jstl-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,284 @@
|
||||
/*
|
||||
* 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.web;
|
||||
|
||||
import com.ibm.icu.text.CharsetDetector;
|
||||
import com.ibm.icu.text.CharsetMatch;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.stream.io.StreamUtils;
|
||||
import org.apache.nifi.web.ViewableContent.DisplayMode;
|
||||
import org.apache.tika.detect.DefaultDetector;
|
||||
import org.apache.tika.io.TikaInputStream;
|
||||
import org.apache.tika.metadata.Metadata;
|
||||
import org.apache.tika.mime.MediaType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
||||
/**
|
||||
* Controller servlet for viewing content. This is responsible for generating
|
||||
* the markup for the header and footer of the page. Included in that is the
|
||||
* combo that allows the user to choose how they wait to view the data
|
||||
* (original, formatted, hex). If a data viewer is registered for the detected
|
||||
* content type, it will include the markup it generates in the response.
|
||||
*/
|
||||
public class ContentViewerController extends HttpServlet {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContentViewerController.class);
|
||||
|
||||
// 1.5kb - multiple of 12 (3 bytes = 4 base 64 encoded chars)
|
||||
private final static int BUFFER_LENGTH = 1536;
|
||||
|
||||
/**
|
||||
* Gets the content and defers to registered viewers to generate the markup.
|
||||
*
|
||||
* @param request servlet request
|
||||
* @param response servlet response
|
||||
* @throws ServletException if a servlet-specific error occurs
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
|
||||
// get the content
|
||||
final ServletContext servletContext = request.getServletContext();
|
||||
final ContentAccess contentAccess = (ContentAccess) servletContext.getAttribute("nifi-content-access");
|
||||
|
||||
// get the content
|
||||
final DownloadableContent downloadableContent;
|
||||
try {
|
||||
downloadableContent = contentAccess.getContent(getContentRequest(request));
|
||||
} catch (final ResourceNotFoundException rnfe) {
|
||||
request.setAttribute("title", "Error");
|
||||
request.setAttribute("messages", "Unable to find the specified content");
|
||||
|
||||
// forward to the error page
|
||||
final ServletContext viewerContext = servletContext.getContext("/nifi");
|
||||
viewerContext.getRequestDispatcher("/message").forward(request, response);
|
||||
return;
|
||||
} catch (final AccessDeniedException ade) {
|
||||
request.setAttribute("title", "Acess Denied");
|
||||
request.setAttribute("messages", "Unable to approve access to the specified content: " + ade.getMessage());
|
||||
|
||||
// forward to the error page
|
||||
final ServletContext viewerContext = servletContext.getContext("/nifi");
|
||||
viewerContext.getRequestDispatcher("/message").forward(request, response);
|
||||
return;
|
||||
} catch (final Exception e) {
|
||||
request.setAttribute("title", "Error");
|
||||
request.setAttribute("messages", "An unexcepted error has occurred: " + e.getMessage());
|
||||
|
||||
// forward to the error page
|
||||
final ServletContext viewerContext = servletContext.getContext("/nifi");
|
||||
viewerContext.getRequestDispatcher("/message").forward(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// determine how we want to view the data
|
||||
String mode = request.getParameter("mode");
|
||||
|
||||
// if the name isn't set, use original
|
||||
if (mode == null) {
|
||||
mode = DisplayMode.Original.name();
|
||||
}
|
||||
|
||||
// determine the display mode
|
||||
final DisplayMode displayMode;
|
||||
try {
|
||||
displayMode = DisplayMode.valueOf(mode);
|
||||
} catch (final IllegalArgumentException iae) {
|
||||
request.setAttribute("title", "Error");
|
||||
request.setAttribute("messages", "Invalid display mode: " + mode);
|
||||
|
||||
// forward to the error page
|
||||
final ServletContext viewerContext = servletContext.getContext("/nifi");
|
||||
viewerContext.getRequestDispatcher("/message").forward(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// buffer the content to support reseting in case we need to detect the content type or char encoding
|
||||
final BufferedInputStream bis = new BufferedInputStream(downloadableContent.getContent());
|
||||
|
||||
// detect the content type
|
||||
final DefaultDetector detector = new DefaultDetector();
|
||||
|
||||
// create the stream for tika to process, buffered to support reseting
|
||||
final TikaInputStream tikaStream = TikaInputStream.get(bis);
|
||||
|
||||
// provide a hint based on the filename
|
||||
final Metadata metadata = new Metadata();
|
||||
metadata.set(Metadata.RESOURCE_NAME_KEY, downloadableContent.getFilename());
|
||||
|
||||
// Get mime type
|
||||
final MediaType mediatype = detector.detect(tikaStream, metadata);
|
||||
final String mimeType = mediatype.toString();
|
||||
|
||||
// add attributes needed for the header
|
||||
final StringBuffer requestUrl = request.getRequestURL();
|
||||
request.setAttribute("requestUrl", requestUrl.toString());
|
||||
request.setAttribute("dataRef", request.getParameter("ref"));
|
||||
request.setAttribute("filename", downloadableContent.getFilename());
|
||||
request.setAttribute("contentType", mimeType);
|
||||
|
||||
// generate the header
|
||||
request.getRequestDispatcher("/WEB-INF/jsp/header.jsp").include(request, response);
|
||||
|
||||
// remove the attributes needed for the header
|
||||
request.removeAttribute("requestUrl");
|
||||
request.removeAttribute("dataRef");
|
||||
request.removeAttribute("filename");
|
||||
request.removeAttribute("contentType");
|
||||
|
||||
// generate the markup for the content based on the display mode
|
||||
if (DisplayMode.Hex.equals(displayMode)) {
|
||||
final byte[] buffer = new byte[BUFFER_LENGTH];
|
||||
final int read = StreamUtils.fillBuffer(bis, buffer, false);
|
||||
|
||||
// trim the byte array if necessary
|
||||
byte[] bytes = buffer;
|
||||
if (read != buffer.length) {
|
||||
bytes = new byte[read];
|
||||
System.arraycopy(buffer, 0, bytes, 0, read);
|
||||
}
|
||||
|
||||
// convert bytes into the base 64 bytes
|
||||
final String base64 = Base64.encodeBase64String(bytes);
|
||||
|
||||
// defer to the jsp
|
||||
request.setAttribute("content", base64);
|
||||
request.getRequestDispatcher("/WEB-INF/jsp/hexview.jsp").include(request, response);
|
||||
} else {
|
||||
// lookup a viewer for the content
|
||||
final String contentViewerUri = servletContext.getInitParameter(mimeType);
|
||||
|
||||
// handle no viewer for content type
|
||||
if (contentViewerUri == null) {
|
||||
request.getRequestDispatcher("/WEB-INF/jsp/no-viewer.jsp").include(request, response);
|
||||
} else {
|
||||
// create a request attribute for accessing the content
|
||||
request.setAttribute(ViewableContent.CONTENT_REQUEST_ATTRIBUTE, new ViewableContent() {
|
||||
@Override
|
||||
public InputStream getContentStream() {
|
||||
return bis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContent() throws IOException {
|
||||
// detect the charset
|
||||
final CharsetDetector detector = new CharsetDetector();
|
||||
detector.setText(bis);
|
||||
detector.enableInputFilter(true);
|
||||
final CharsetMatch match = detector.detect();
|
||||
|
||||
// ensure we were able to detect the charset
|
||||
if (match == null) {
|
||||
throw new IOException("Unable to detect character encoding.");
|
||||
}
|
||||
|
||||
// convert the stream using the detected charset
|
||||
return IOUtils.toString(bis, match.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewableContent.DisplayMode getDisplayMode() {
|
||||
return displayMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return downloadableContent.getFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return mimeType;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// generate the content
|
||||
final ServletContext viewerContext = servletContext.getContext(contentViewerUri);
|
||||
viewerContext.getRequestDispatcher("/view-content").include(request, response);
|
||||
} catch (final Exception e) {
|
||||
String message = e.getMessage() != null ? e.getMessage() : e.toString();
|
||||
message = "Unable to generate view of data: " + message;
|
||||
|
||||
// log the error
|
||||
logger.error(message);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.error(StringUtils.EMPTY, e);
|
||||
}
|
||||
|
||||
// populate the request attributes
|
||||
request.setAttribute("title", "Error");
|
||||
request.setAttribute("messages", message);
|
||||
|
||||
// forward to the error page
|
||||
final ServletContext viewerContext = servletContext.getContext("/nifi");
|
||||
viewerContext.getRequestDispatcher("/message").forward(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the request attribute
|
||||
request.removeAttribute(ViewableContent.CONTENT_REQUEST_ATTRIBUTE);
|
||||
}
|
||||
}
|
||||
|
||||
// generate footer
|
||||
request.getRequestDispatcher("/WEB-INF/jsp/footer.jsp").include(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content request context based on the specified request.
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private ContentRequestContext getContentRequest(final HttpServletRequest request) {
|
||||
return new ContentRequestContext() {
|
||||
@Override
|
||||
public String getDataUri() {
|
||||
return request.getParameter("ref");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClusterNodeId() {
|
||||
final String ref = request.getParameter("ref");
|
||||
return StringUtils.substringAfterLast(ref, "clusterNodeId=");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return request.getParameter("clientId");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProxiedEntitiesChain() {
|
||||
return request.getHeader("X-ProxiedEntitiesChain");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
nifi-web-docs
|
||||
Copyright 2014-2015 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
===========================================
|
||||
Apache Software License v2
|
||||
===========================================
|
||||
|
||||
The following binary components are provided under the Apache Software License v2
|
||||
|
||||
(ASLv2) Apache Commons Lang
|
||||
The following NOTICE information applies:
|
||||
Apache Commons Lang
|
||||
Copyright 2001-2014 The Apache Software Foundation
|
||||
|
||||
This product includes software from the Spring Framework,
|
||||
under the Apache License 2.0 (see: StringUtils.containsWhitespace())
|
@ -0,0 +1,20 @@
|
||||
<%--
|
||||
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.
|
||||
--%>
|
||||
<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,92 @@
|
||||
<%--
|
||||
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.
|
||||
--%>
|
||||
<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="shortcut icon" href="../nifi/images/nifi16.ico"/>
|
||||
<title>NiFi</title>
|
||||
<link href="css/main.css" rel="stylesheet" type="text/css" />
|
||||
<link href="../nifi/css/message-pane.css" rel="stylesheet" type="text/css" />
|
||||
<link href="../nifi/css/message-page.css" rel="stylesheet" type="text/css" />
|
||||
<link rel="stylesheet" href="../nifi/js/jquery/combo/jquery.combo.css" type="text/css" />
|
||||
<link rel="stylesheet" href="../nifi/css/reset.css" type="text/css" />
|
||||
<script type="text/javascript" src="../nifi/js/jquery/jquery-2.1.1.min.js"></script>
|
||||
<script type="text/javascript" src="../nifi/js/jquery/combo/jquery.combo.js"></script>
|
||||
<script type="text/javascript">
|
||||
var $$ = $.noConflict(true);
|
||||
$$(document).ready(function () {
|
||||
var url = '${requestUrl}';
|
||||
var ref = '${param.ref}';
|
||||
|
||||
// create the parameters
|
||||
var params = {
|
||||
ref: ref
|
||||
};
|
||||
|
||||
// include the cluster node if appropriate
|
||||
var clusterNodeId = '${param.clusterNodeId}';
|
||||
if (clusterNodeId !== null && clusterNodeId !== '') {
|
||||
params['clusterNodeId'] = clusterNodeId;
|
||||
}
|
||||
|
||||
// determine the appropriate mode to select initially
|
||||
var initialMode = '${param.mode}';
|
||||
if (initialMode === null && initialMode === '') {
|
||||
initialMode = 'Original';
|
||||
}
|
||||
|
||||
var currentLocation = null;
|
||||
$$('#view-as').combo({
|
||||
options: [{
|
||||
text: 'original',
|
||||
value: 'Original'
|
||||
}, {
|
||||
text: 'formatted',
|
||||
value: 'Formatted'
|
||||
}, {
|
||||
text: 'hex',
|
||||
value: 'Hex'
|
||||
}],
|
||||
selectedOption: {
|
||||
value: initialMode
|
||||
},
|
||||
select: function (option) {
|
||||
// just record the selection during creation
|
||||
if (currentLocation === null) {
|
||||
currentLocation = option.value;
|
||||
return;
|
||||
}
|
||||
|
||||
// if the selection has changesd, reload the page
|
||||
if (currentLocation !== option.value) {
|
||||
window.location.href = url + '?' + $$.param($$.extend({
|
||||
mode: option.value
|
||||
}, params));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body class="message-pane">
|
||||
<div id="view-as-label">View as</div>
|
||||
<div id="view-as" class="pointer button-normal"></div>
|
||||
<div id="content-filename"><span class="content-label">filename</span>${filename}</div>
|
||||
<div id="content-type"><span class="content-label">content type</span>${contentType}</div>
|
||||
<div class="message-pane-message-box">
|
@ -0,0 +1,32 @@
|
||||
<%--
|
||||
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.
|
||||
--%>
|
||||
<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
|
||||
<link rel="stylesheet" href="js/hexview/hexview.default.css" type="text/css" />
|
||||
<script type="text/javascript" src="../nifi/js/jquery/jquery-2.1.1.min.js"></script>
|
||||
<script type="text/javascript" src="js/hexview/hexview.js"></script>
|
||||
|
||||
<div id="hexview-content" class="hexviewwindow" title="">
|
||||
${content}
|
||||
<form id="hexviewwindow_params">
|
||||
<input type="hidden" name="highlights" value="" />
|
||||
<input type="hidden" name="row_width" value="16" />
|
||||
<input type="hidden" name="word_size" value="1" />
|
||||
<input type="hidden" name="hide_0x" value="1" />
|
||||
<input type="hidden" name="caption" value="" />
|
||||
</form>
|
||||
</div>
|
||||
<div id="trancation-message">Showing up to 1.5kb</div>
|
@ -0,0 +1,20 @@
|
||||
<%--
|
||||
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.
|
||||
--%>
|
||||
<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
|
||||
<div id="no-viewer">
|
||||
No viewer is registered for this content type.
|
||||
</div>
|
@ -0,0 +1,26 @@
|
||||
<?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.
|
||||
-->
|
||||
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
|
||||
<display-name>nifi-content-viewer</display-name>
|
||||
<servlet>
|
||||
<servlet-name>ContentViewerController</servlet-name>
|
||||
<servlet-class>org.apache.nifi.web.ContentViewerController</servlet-class>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>ContentViewerController</servlet-name>
|
||||
<url-pattern></url-pattern>
|
||||
</servlet-mapping>
|
||||
</web-app>
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#view-as-label {
|
||||
position: absolute;
|
||||
top: 72px;
|
||||
left: 144px;
|
||||
line-height: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#view-as {
|
||||
position: absolute;
|
||||
top: 72px;
|
||||
left: 200px;
|
||||
width: 80px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
#content-filename {
|
||||
position: absolute;
|
||||
top: 58px;
|
||||
right: 50px;
|
||||
line-height: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#content-type {
|
||||
position: absolute;
|
||||
top: 75px;
|
||||
right: 50px;
|
||||
line-height: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.content-label {
|
||||
font-weight: bold;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* hex viewer */
|
||||
|
||||
#hexview-content {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
bottom: 50px;
|
||||
left: 100px;
|
||||
top: 100px;
|
||||
border: 1px solid #aaa;
|
||||
overflow: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#hexview-content table.hexviewerwindow_table {
|
||||
border: none;
|
||||
margin-left: 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#hexview-content td {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#trancation-message {
|
||||
position: absolute;
|
||||
left: 100px;
|
||||
bottom: 35px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* no viewer */
|
||||
|
||||
#no-viewer {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
bottom: 50px;
|
||||
left: 100px;
|
||||
top: 100px;
|
||||
border: 1px solid #aaa;
|
||||
overflow: auto;
|
||||
background-color: #fff;
|
||||
font-style: italic;
|
||||
padding: 5px;
|
||||
font-size: 13px;
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
|
||||
HexViewJS License
|
||||
-----------------
|
||||
|
||||
HexViewJS is written by Nick McVeity <nmcveity@gmail.com> and is
|
||||
licensed under the terms of the MIT license reproduced below.
|
||||
|
||||
========================================================================
|
||||
|
||||
Copyright (c) 2010 Nick McVeity <nmcveity@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
|
@ -0,0 +1,10 @@
|
||||
.hexviewerwindow { font-family: monospace; background-color: #F2F2F2;}
|
||||
div.hexviewerwindow { padding: 20px; }
|
||||
.hexviewerwindow_table { border-collapse:collapse; border: 5px solid grey; margin-left: 16px; caption-side:bottom; }
|
||||
.hexviewerwindow_offset {background: #A9D0F5; padding-right: 8px; }
|
||||
.hexviewerwindow_visual {background: #A9F5F2; padding-left: 8px; }
|
||||
.hexviewerwindow_code {}
|
||||
.hexviewerwindow_code_hi {background: #F4FA58; }
|
||||
.hexviewerwindow_border_start {border-left: solid #E0E0E0 1px; }
|
||||
.hexviewerwindow_border_middle {border-bottom: solid #E0E0E0 1px; border-top: solid #E0E0E0 1px;}
|
||||
.hexviewerwindow_border_end {border-right: solid #E0E0E0 1px; border-top: solid #E0E0E0 1px; }
|
@ -0,0 +1,199 @@
|
||||
$(document).ready(function () {
|
||||
var HEX = '0123456789ABCDEF';
|
||||
|
||||
function dec2_to_hex(dec)
|
||||
{
|
||||
if (dec < 0)
|
||||
dec = 0;
|
||||
|
||||
if (dec > 255)
|
||||
dec = 255;
|
||||
|
||||
return HEX.charAt(Math.floor(dec / 16)) + HEX.charAt(dec % 16);
|
||||
}
|
||||
|
||||
function dec_to_hex8(dec)
|
||||
{
|
||||
var str = "";
|
||||
|
||||
for (var i = 3; i >= 0; i--)
|
||||
{
|
||||
str += dec2_to_hex((dec >> (i*8)) & 255);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function remove_whitespace(str)
|
||||
{
|
||||
return str.replace(/\n/g, "")
|
||||
.replace(/\t/g, "")
|
||||
.replace(/ /g, "")
|
||||
.replace(/\r/g, "");
|
||||
}
|
||||
|
||||
var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
|
||||
function base64_decode(encoded)
|
||||
{
|
||||
var decoded = "";
|
||||
|
||||
for (var i = 0; i < encoded.length; i += 4)
|
||||
{
|
||||
var ch0 = encoded.charAt(i+0);
|
||||
var ch1 = encoded.charAt(i+1);
|
||||
var ch2 = encoded.charAt(i+2);
|
||||
var ch3 = encoded.charAt(i+3);
|
||||
|
||||
var index0 = BASE64_CHARS.indexOf(ch0);
|
||||
var index1 = BASE64_CHARS.indexOf(ch1);
|
||||
var index2 = BASE64_CHARS.indexOf(ch2);
|
||||
var index3 = BASE64_CHARS.indexOf(ch3);
|
||||
|
||||
decoded += String.fromCharCode((index0 << 2) | (index1 >> 4));
|
||||
decoded += String.fromCharCode(((index1 & 15) << 4) | (index2 >> 2));
|
||||
|
||||
// skip the base64 padding as those weren't present in the actual bytes
|
||||
var token = String.fromCharCode(((index2 & 3) << 6) | index3);
|
||||
if (index3 !== 64 || token !== '@') {
|
||||
decoded += token;
|
||||
}
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
function markup_hexviewwindow(div, index)
|
||||
{
|
||||
var entityMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'/': '/'
|
||||
};
|
||||
|
||||
function escapeHtml(string) {
|
||||
if (string === null || typeof string === 'undefined') {
|
||||
return '';
|
||||
} else {
|
||||
return String(string).replace(/[&<>"'\/]/g, function (s) {
|
||||
return entityMap[s];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var bin_data = base64_decode(remove_whitespace(div.text()));
|
||||
var line_data;
|
||||
var title = div.attr("title");
|
||||
|
||||
var highlights_str = $("form#hexviewwindow_params input[name='highlights']", div).attr("value").split(',');
|
||||
var highlights = [];
|
||||
|
||||
for (var i = 0; i < highlights_str.length; i++)
|
||||
{
|
||||
highlights.push(highlights_str[i].split(":"));
|
||||
}
|
||||
|
||||
var params = title.split(':');
|
||||
var step = parseInt($("form#hexviewwindow_params input[name='row_width']", div).attr("value"));
|
||||
var word_size = parseInt($("form#hexviewwindow_params input[name='word_size']", div).attr("value"));
|
||||
var hide_0x = parseInt($("form#hexviewwindow_params input[name='hide_0x']", div).attr("value"));
|
||||
var decimal_offset = parseInt($("form#hexviewwindow_params input[name='decimal_offset']", div).attr("value"));
|
||||
var start_byte_1 = parseInt($("form#hexviewwindow_params input[name='start_byte_1']", div).attr("value"));
|
||||
var caption = $("form#hexviewwindow_params input[name='caption']", div).attr("value");
|
||||
|
||||
div.text("");
|
||||
div.append("<table></table>");
|
||||
|
||||
var offset = (start_byte_1 ? 1 : 0);
|
||||
|
||||
function apply_highlights(index)
|
||||
{
|
||||
for (var j = 0; j < highlights.length; j++)
|
||||
{
|
||||
if ((index >= highlights[j][0]) && (index <= highlights[j][1]))
|
||||
{
|
||||
if (index === highlights[j][0])
|
||||
{
|
||||
$("table tr td:last", div).addClass("hexviewerwindow_border_start");
|
||||
}
|
||||
|
||||
if (index === highlights[j][1])
|
||||
{
|
||||
$("table tr td:last", div).addClass("hexviewerwindow_border_end");
|
||||
}
|
||||
|
||||
$("table tr td:last", div).addClass("hexviewerwindow_code_hi hexviewerwindow_border_middle");
|
||||
$("table tr td:last", div).attr("style", "background-color: " + highlights[j][2] + ";");
|
||||
$("table tr td:last", div).attr("title", highlights[j][3]);
|
||||
|
||||
runlen += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$("table tr td:last", div).addClass("hexviewerwindow_code");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (caption)
|
||||
$("table", div).append("<caption>" + escapeHtml(caption) + "</caption>");
|
||||
|
||||
while (bin_data.length > 0)
|
||||
{
|
||||
line_data = bin_data.slice(0, step);
|
||||
bin_data = bin_data.slice(step);
|
||||
|
||||
$("table", div).addClass("hexviewerwindow_table");
|
||||
$("table", div).append("<tr></tr>").addClass("hexviewerwindow");
|
||||
$("table tr:last", div).append("<td>" + (decimal_offset ? ("00000000"+offset).slice(-8) : "0x" + dec_to_hex8(offset)) + "</td>");
|
||||
$("table tr td:last", div).addClass("hexviewerwindow_offset");
|
||||
|
||||
var runlen = 0;
|
||||
|
||||
for (var i = 0; i < line_data.length; i += word_size)
|
||||
{
|
||||
var num = "";
|
||||
|
||||
for (var j = 0; j < word_size; j++)
|
||||
{
|
||||
num += dec2_to_hex(line_data.charCodeAt(i+j));
|
||||
}
|
||||
|
||||
$("table tr:last", div).append("<td>" + (hide_0x ? "" : "0x") + num + "</td>");
|
||||
|
||||
apply_highlights(offset+i);
|
||||
}
|
||||
|
||||
var text = "";
|
||||
|
||||
for (var i = 0; i < line_data.length; i++)
|
||||
{
|
||||
var cc = line_data.charCodeAt(i);
|
||||
|
||||
if ((cc >= 32) && (cc <= 126))
|
||||
{
|
||||
text = text + line_data.charAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
text = text + ".";
|
||||
}
|
||||
}
|
||||
|
||||
if (line_data.length < step)
|
||||
$("table tr td:last", div).attr("colspan", Math.floor((step - line_data.length) / word_size) + 1);
|
||||
|
||||
offset += step;
|
||||
|
||||
$("table tr:last", div).append("<td>" + escapeHtml(text) + "</td>");
|
||||
$("table tr td:last", div).addClass("hexviewerwindow_visual");
|
||||
}
|
||||
}
|
||||
|
||||
$("div.hexviewwindow").each(function (index) {
|
||||
markup_hexviewwindow($(this), index);
|
||||
});
|
||||
});
|
@ -113,6 +113,10 @@ table tr:last-child td:last-child {
|
||||
border-bottom-right-radius:3px;
|
||||
}
|
||||
|
||||
td#allowable-values, td#default-value, td#name, td#value {
|
||||
white-space:nowrap;
|
||||
}
|
||||
|
||||
/* links */
|
||||
|
||||
a, a:link, a:visited {
|
||||
|
@ -115,6 +115,17 @@
|
||||
<url-pattern>/bulletin-board</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- servlet to support message page -->
|
||||
|
||||
<servlet>
|
||||
<servlet-name>MessagePage</servlet-name>
|
||||
<jsp-file>/WEB-INF/pages/message-page.jsp</jsp-file>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>MessagePage</servlet-name>
|
||||
<url-pattern>/message</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- servlet to support image downloading -->
|
||||
|
||||
<servlet>
|
||||
|
@ -0,0 +1,20 @@
|
||||
.CodeMirror-foldmarker {
|
||||
color: blue;
|
||||
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
|
||||
font-family: arial;
|
||||
line-height: .3;
|
||||
cursor: pointer;
|
||||
}
|
||||
.CodeMirror-foldgutter {
|
||||
width: .7em;
|
||||
}
|
||||
.CodeMirror-foldgutter-open,
|
||||
.CodeMirror-foldgutter-folded {
|
||||
cursor: pointer;
|
||||
}
|
||||
.CodeMirror-foldgutter-open:after {
|
||||
content: "\25BE";
|
||||
}
|
||||
.CodeMirror-foldgutter-folded:after {
|
||||
content: "\25B8";
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -86,3 +86,11 @@ div.combo-nifi-tooltip {
|
||||
background-color: #FFFFA3;
|
||||
color: #454545;
|
||||
}
|
||||
|
||||
div.button-normal {
|
||||
background: transparent url(../../../images/bgButton.png) repeat-x center center;
|
||||
}
|
||||
|
||||
div.button-over {
|
||||
background: transparent url(../../../images/bgButtonOver.png) repeat-x center center;
|
||||
}
|
||||
|
@ -29,8 +29,10 @@
|
||||
<module>nifi-web-api</module>
|
||||
<module>nifi-web-error</module>
|
||||
<module>nifi-web-docs</module>
|
||||
<module>nifi-web-content-viewer</module>
|
||||
<module>nifi-web-ui</module>
|
||||
<module>nifi-jetty</module>
|
||||
<module>nifi-web-content-access</module>
|
||||
</modules>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
@ -52,6 +54,12 @@
|
||||
<type>war</type>
|
||||
<version>0.1.0-incubating-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-content-viewer</artifactId>
|
||||
<type>war</type>
|
||||
<version>0.1.0-incubating-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-ui</artifactId>
|
||||
|
@ -63,6 +63,11 @@
|
||||
<artifactId>nifi-client-dto</artifactId>
|
||||
<version>0.1.0-incubating-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-web-content-access</artifactId>
|
||||
<version>0.1.0-incubating-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security</artifactId>
|
||||
|
@ -78,20 +78,24 @@ abstract class AbstractKiteProcessor extends AbstractProcessor {
|
||||
|
||||
protected static final Validator RECOGNIZED_URI = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(String subject, String uri,
|
||||
ValidationContext context) {
|
||||
public ValidationResult validate(String subject, String uri, ValidationContext context) {
|
||||
String message = "not set";
|
||||
boolean isValid = true;
|
||||
if (uri == null || uri.isEmpty()) {
|
||||
|
||||
if (uri.trim().isEmpty()) {
|
||||
isValid = false;
|
||||
} else if (!uri.contains("$")) {
|
||||
try {
|
||||
new URIBuilder(URI.create(uri)).build();
|
||||
} catch (RuntimeException e) {
|
||||
message = e.getMessage();
|
||||
isValid = false;
|
||||
} else {
|
||||
final boolean elPresent = context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(uri);
|
||||
if (!elPresent) {
|
||||
try {
|
||||
new URIBuilder(URI.create(uri)).build();
|
||||
} catch (RuntimeException e) {
|
||||
message = e.getMessage();
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ValidationResult.Builder()
|
||||
.subject(subject)
|
||||
.input(uri)
|
||||
@ -157,16 +161,16 @@ abstract class AbstractKiteProcessor extends AbstractProcessor {
|
||||
protected static final Validator SCHEMA_VALIDATOR = new Validator() {
|
||||
@Override
|
||||
public ValidationResult validate(String subject, String uri, ValidationContext context) {
|
||||
Configuration conf = getConfiguration(
|
||||
context.getProperty(CONF_XML_FILES).getValue());
|
||||
|
||||
Configuration conf = getConfiguration(context.getProperty(CONF_XML_FILES).getValue());
|
||||
String error = null;
|
||||
if (!uri.contains("$")) {
|
||||
try {
|
||||
getSchema(uri, conf);
|
||||
} catch (SchemaNotFoundException e) {
|
||||
error = e.getMessage();
|
||||
}
|
||||
|
||||
final boolean elPresent = context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(uri);
|
||||
if (!elPresent) {
|
||||
try {
|
||||
getSchema(uri, conf);
|
||||
} catch (SchemaNotFoundException e) {
|
||||
error = e.getMessage();
|
||||
}
|
||||
}
|
||||
return new ValidationResult.Builder()
|
||||
.subject(subject)
|
||||
@ -177,8 +181,7 @@ abstract class AbstractKiteProcessor extends AbstractProcessor {
|
||||
}
|
||||
};
|
||||
|
||||
protected static final List<PropertyDescriptor> ABSTRACT_KITE_PROPS
|
||||
= ImmutableList.<PropertyDescriptor>builder()
|
||||
protected static final List<PropertyDescriptor> ABSTRACT_KITE_PROPS = ImmutableList.<PropertyDescriptor>builder()
|
||||
.add(CONF_XML_FILES)
|
||||
.build();
|
||||
|
||||
|
@ -88,8 +88,7 @@ public class ConvertCSVToAvro extends AbstractKiteProcessor {
|
||||
static final PropertyDescriptor SCHEMA =
|
||||
new PropertyDescriptor.Builder()
|
||||
.name("Record schema")
|
||||
.description(
|
||||
"Outgoing Avro schema for each record created from a CSV row")
|
||||
.description("Outgoing Avro schema for each record created from a CSV row")
|
||||
.addValidator(SCHEMA_VALIDATOR)
|
||||
.expressionLanguageSupported(true)
|
||||
.required(true)
|
||||
@ -253,7 +252,6 @@ public class ConvertCSVToAvro extends AbstractKiteProcessor {
|
||||
} catch (ProcessException | DatasetIOException e) {
|
||||
getLogger().error("Failed reading or writing", e);
|
||||
session.transfer(flowFile, FAILURE);
|
||||
|
||||
} catch (DatasetException e) {
|
||||
getLogger().error("Failed to read FlowFile", e);
|
||||
session.transfer(flowFile, FAILURE);
|
||||
|
@ -65,8 +65,7 @@ public class ConvertJSONToAvro extends AbstractKiteProcessor {
|
||||
static final PropertyDescriptor SCHEMA
|
||||
= new PropertyDescriptor.Builder()
|
||||
.name("Record schema")
|
||||
.description(
|
||||
"Outgoing Avro schema for each record created from a JSON object")
|
||||
.description("Outgoing Avro schema for each record created from a JSON object")
|
||||
.addValidator(SCHEMA_VALIDATOR)
|
||||
.expressionLanguageSupported(true)
|
||||
.required(true)
|
||||
|
@ -68,8 +68,7 @@ public class StoreInKiteDataset extends AbstractKiteProcessor {
|
||||
public static final PropertyDescriptor KITE_DATASET_URI =
|
||||
new PropertyDescriptor.Builder()
|
||||
.name("Target dataset URI")
|
||||
.description(
|
||||
"URI that identifies a Kite dataset where data will be stored")
|
||||
.description("URI that identifies a Kite dataset where data will be stored")
|
||||
.addValidator(RECOGNIZED_URI)
|
||||
.expressionLanguageSupported(true)
|
||||
.required(true)
|
||||
|
@ -0,0 +1,71 @@
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-standard-bundle</artifactId>
|
||||
<version>0.1.0-incubating-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-standard-content-viewer</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<version>1.9.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet.jsp</groupId>
|
||||
<artifactId>javax.servlet.jsp-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.el</groupId>
|
||||
<artifactId>javax.el-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet.jsp.jstl</groupId>
|
||||
<artifactId>javax.servlet.jsp.jstl-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.TransformerFactoryConfigurationError;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import org.apache.nifi.web.ViewableContent.DisplayMode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@WebServlet(name = "StandardContentViewer", urlPatterns = {"/view-content"})
|
||||
public class StandardContentViewerController extends HttpServlet {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param request servlet request
|
||||
* @param response servlet response
|
||||
* @throws ServletException if a servlet-specific error occurs
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
final ViewableContent content = (ViewableContent) request.getAttribute(ViewableContent.CONTENT_REQUEST_ATTRIBUTE);
|
||||
|
||||
// handle json/xml
|
||||
if ("application/json".equals(content.getContentType()) || "application/xml".equals(content.getContentType()) || "text/plain".equals(content.getContentType())) {
|
||||
final String formatted;
|
||||
|
||||
// leave the content alone if specified
|
||||
if (DisplayMode.Original.equals(content.getDisplayMode())) {
|
||||
formatted = content.getContent();
|
||||
} else {
|
||||
if ("application/json".equals(content.getContentType())) {
|
||||
// format json
|
||||
final ObjectMapper mapper = new ObjectMapper();
|
||||
final Object objectJson = mapper.readValue(content.getContent(), Object.class);
|
||||
formatted = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectJson);
|
||||
} else if ("application/xml".equals(content.getContentType())) {
|
||||
// format xml
|
||||
final StringWriter writer = new StringWriter();
|
||||
|
||||
try {
|
||||
final StreamSource source = new StreamSource(content.getContentStream());
|
||||
final StreamResult result = new StreamResult(writer);
|
||||
|
||||
final TransformerFactory transformFactory = TransformerFactory.newInstance();
|
||||
final Transformer transformer = transformFactory.newTransformer();
|
||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
|
||||
transformer.transform(source, result);
|
||||
} catch (final TransformerFactoryConfigurationError | TransformerException te) {
|
||||
throw new IOException("Unable to transform content as XML: " + te, te);
|
||||
}
|
||||
|
||||
// get the transformed xml
|
||||
formatted = writer.toString();
|
||||
} else {
|
||||
// leave plain text alone when formatting
|
||||
formatted = content.getContent();
|
||||
}
|
||||
}
|
||||
|
||||
// defer to the jsp
|
||||
request.setAttribute("mode", content.getContentType());
|
||||
request.setAttribute("content", formatted);
|
||||
request.getRequestDispatcher("/WEB-INF/jsp/codemirror.jsp").include(request, response);
|
||||
} else {
|
||||
final PrintWriter out = response.getWriter();
|
||||
out.println("Unexpected content type: " + content.getContentType());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
nifi-web-docs
|
||||
Copyright 2014-2015 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
===========================================
|
||||
Apache Software License v2
|
||||
===========================================
|
||||
|
||||
The following binary components are provided under the Apache Software License v2
|
||||
|
||||
(ASLv2) Apache Commons Lang
|
||||
The following NOTICE information applies:
|
||||
Apache Commons Lang
|
||||
Copyright 2001-2014 The Apache Software Foundation
|
||||
|
||||
This product includes software from the Spring Framework,
|
||||
under the Apache License 2.0 (see: StringUtils.containsWhitespace())
|
@ -0,0 +1,3 @@
|
||||
application/xml
|
||||
application/json
|
||||
text/plain
|
@ -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.
|
||||
--%>
|
||||
<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
|
||||
<link rel="stylesheet" href="../nifi/js/codemirror/lib/codemirror.css" type="text/css" />
|
||||
<link rel="stylesheet" href="../nifi/js/codemirror/addon/fold/foldgutter.css" type="text/css" />
|
||||
<script type="text/javascript" src="../nifi/js/codemirror/lib/codemirror-compressed.js"></script>
|
||||
<script type="text/javascript" src="../nifi/js/jquery/jquery-2.1.1.min.js"></script>
|
||||
|
||||
<textarea id="codemirror-content">${content}</textarea>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
var field = document.getElementById('codemirror-content');
|
||||
var editor = CodeMirror.fromTextArea(field, {
|
||||
mode: '${mode}',
|
||||
lineNumbers: true,
|
||||
matchBrackets: true,
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
readOnly: true
|
||||
});
|
||||
|
||||
var setEditorSize = function() {
|
||||
editor.setSize($(window).width() - 150, $(window).height() - 150);
|
||||
};
|
||||
|
||||
// reset the editor size when the window changes
|
||||
$(window).resize(setEditorSize);
|
||||
|
||||
// initialize the editor size
|
||||
setEditorSize();
|
||||
});
|
||||
</script>
|
@ -0,0 +1,29 @@
|
||||
<?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.
|
||||
-->
|
||||
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
|
||||
<display-name>nifi-standard-content-viewer</display-name>
|
||||
<servlet>
|
||||
<servlet-name>StandardContentViewer</servlet-name>
|
||||
<servlet-class>org.apache.nifi.web.StandardContentViewerController</servlet-class>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>StandardContentViewer</servlet-name>
|
||||
<url-pattern>/view-content</url-pattern>
|
||||
</servlet-mapping>
|
||||
<welcome-file-list>
|
||||
<welcome-file>view-content</welcome-file>
|
||||
</welcome-file-list>
|
||||
</web-app>
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
@ -40,5 +40,10 @@
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-standard-reporting-tasks</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-standard-content-viewer</artifactId>
|
||||
<type>war</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -155,7 +155,7 @@ public class ControlRate extends AbstractProcessor {
|
||||
break;
|
||||
}
|
||||
|
||||
final ValidationResult rateResult = rateValidator.validate("Maximum Rate", context.getProperty(MAX_RATE).getValue(), null);
|
||||
final ValidationResult rateResult = rateValidator.validate("Maximum Rate", context.getProperty(MAX_RATE).getValue(), context);
|
||||
if (!rateResult.isValid()) {
|
||||
validationResults.add(rateResult);
|
||||
}
|
||||
|
@ -16,10 +16,19 @@
|
||||
*/
|
||||
package org.apache.nifi.processors.standard;
|
||||
|
||||
import com.jayway.jsonpath.DocumentContext;
|
||||
import com.jayway.jsonpath.InvalidJsonException;
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import com.jayway.jsonpath.PathNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.annotation.behavior.DynamicProperty;
|
||||
@ -27,7 +36,6 @@ import org.apache.nifi.annotation.behavior.EventDriven;
|
||||
import org.apache.nifi.annotation.behavior.SideEffectFree;
|
||||
import org.apache.nifi.annotation.behavior.SupportsBatching;
|
||||
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
||||
import org.apache.nifi.annotation.behavior.DynamicRelationship;
|
||||
import org.apache.nifi.annotation.documentation.Tags;
|
||||
import org.apache.nifi.annotation.lifecycle.OnRemoved;
|
||||
import org.apache.nifi.components.PropertyDescriptor;
|
||||
@ -44,12 +52,10 @@ import org.apache.nifi.processor.io.OutputStreamCallback;
|
||||
import org.apache.nifi.stream.io.BufferedOutputStream;
|
||||
import org.apache.nifi.util.ObjectHolder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import com.jayway.jsonpath.DocumentContext;
|
||||
import com.jayway.jsonpath.InvalidJsonException;
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import com.jayway.jsonpath.PathNotFoundException;
|
||||
|
||||
@EventDriven
|
||||
@SideEffectFree
|
||||
|
@ -54,7 +54,7 @@ public class HandleHttpResponse extends AbstractProcessor {
|
||||
.name("HTTP Status Code")
|
||||
.description("The HTTP Status Code to use when responding to the HTTP Request. See Section 10 of RFC 2616 for more information.")
|
||||
.required(true)
|
||||
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||
.addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR)
|
||||
.expressionLanguageSupported(true)
|
||||
.build();
|
||||
public static final PropertyDescriptor HTTP_CONTEXT_MAP = new PropertyDescriptor.Builder()
|
||||
|
@ -203,7 +203,7 @@ public final class InvokeHTTP extends AbstractProcessor {
|
||||
.description("Remote URL which will be connected to, including scheme, host, port, path.")
|
||||
.required(true)
|
||||
.expressionLanguageSupported(true)
|
||||
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||
.addValidator(StandardValidators.URL_VALIDATOR)
|
||||
.build();
|
||||
|
||||
PropertyDescriptor PROP_CONNECT_TIMEOUT = new PropertyDescriptor.Builder()
|
||||
|
@ -146,6 +146,7 @@ public class PostHTTP extends AbstractProcessor {
|
||||
.description("The URL to POST to. The first part of the URL must be static. However, the path of the URL may be defined using the Attribute Expression Language. For example, https://${hostname} is not valid, but https://1.1.1.1:8080/files/${nf.file.name} is valid.")
|
||||
.required(true)
|
||||
.addValidator(StandardValidators.createRegexMatchingValidator(Pattern.compile("https?\\://.*")))
|
||||
.addValidator(StandardValidators.URL_VALIDATOR)
|
||||
.expressionLanguageSupported(true)
|
||||
.build();
|
||||
public static final PropertyDescriptor SEND_AS_FLOWFILE = new PropertyDescriptor.Builder()
|
||||
|
@ -22,18 +22,22 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.activation.DataHandler;
|
||||
import javax.mail.Authenticator;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.Message.RecipientType;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.PasswordAuthentication;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.URLName;
|
||||
import javax.mail.Transport;
|
||||
import javax.mail.internet.AddressException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeBodyPart;
|
||||
@ -61,8 +65,6 @@ import org.apache.nifi.processor.exception.ProcessException;
|
||||
import org.apache.nifi.processor.io.InputStreamCallback;
|
||||
import org.apache.nifi.processor.util.StandardValidators;
|
||||
|
||||
import com.sun.mail.smtp.SMTPTransport;
|
||||
|
||||
@SupportsBatching
|
||||
@Tags({"email", "put", "notify", "smtp"})
|
||||
@CapabilityDescription("Sends an e-mail to configured recipients for each incoming FlowFile")
|
||||
@ -72,6 +74,7 @@ public class PutEmail extends AbstractProcessor {
|
||||
.name("SMTP Hostname")
|
||||
.description("The hostname of the SMTP host")
|
||||
.required(true)
|
||||
.expressionLanguageSupported(true)
|
||||
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||
.build();
|
||||
public static final PropertyDescriptor SMTP_PORT = new PropertyDescriptor.Builder()
|
||||
@ -79,21 +82,56 @@ public class PutEmail extends AbstractProcessor {
|
||||
.description("The Port used for SMTP communications")
|
||||
.required(true)
|
||||
.defaultValue("25")
|
||||
.expressionLanguageSupported(true)
|
||||
.addValidator(StandardValidators.PORT_VALIDATOR)
|
||||
.build();
|
||||
public static final PropertyDescriptor SMTP_USERNAME = new PropertyDescriptor.Builder()
|
||||
.name("SMTP Username")
|
||||
.description("Username for the SMTP account")
|
||||
.expressionLanguageSupported(true)
|
||||
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||
.required(false)
|
||||
.build();
|
||||
public static final PropertyDescriptor SMTP_PASSWORD = new PropertyDescriptor.Builder()
|
||||
.name("SMTP Password")
|
||||
.description("Password for the SMTP account")
|
||||
.expressionLanguageSupported(true)
|
||||
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||
.required(false)
|
||||
.sensitive(true)
|
||||
.build();
|
||||
public static final PropertyDescriptor SMTP_AUTH = new PropertyDescriptor.Builder()
|
||||
.name("SMTP Auth")
|
||||
.description("Flag indicating whether authentication should be used")
|
||||
.required(true)
|
||||
.expressionLanguageSupported(true)
|
||||
.addValidator(StandardValidators.BOOLEAN_VALIDATOR)
|
||||
.defaultValue("true")
|
||||
.build();
|
||||
public static final PropertyDescriptor SMTP_TLS = new PropertyDescriptor.Builder()
|
||||
.name("SMTP TLS")
|
||||
.description("Flag indicating whether TLS should be enabled")
|
||||
.required(true)
|
||||
.expressionLanguageSupported(true)
|
||||
.addValidator(StandardValidators.BOOLEAN_VALIDATOR)
|
||||
.defaultValue("false")
|
||||
.build();
|
||||
public static final PropertyDescriptor SMTP_SOCKET_FACTORY = new PropertyDescriptor.Builder()
|
||||
.name("SMTP Socket Factory")
|
||||
.description("Socket Factory to use for SMTP Connection")
|
||||
.required(true)
|
||||
.expressionLanguageSupported(true)
|
||||
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||
.defaultValue("javax.net.ssl.SSLSocketFactory")
|
||||
.build();
|
||||
public static final PropertyDescriptor HEADER_XMAILER = new PropertyDescriptor.Builder()
|
||||
.name("SMTP X-Mailer Header")
|
||||
.description("X-Mailer used in the header of the outgoing email")
|
||||
.required(true)
|
||||
.expressionLanguageSupported(true)
|
||||
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||
.defaultValue("NiFi")
|
||||
.build();
|
||||
public static final PropertyDescriptor FROM = new PropertyDescriptor.Builder()
|
||||
.name("From")
|
||||
.description("Specifies the Email address to use as the sender")
|
||||
@ -152,12 +190,27 @@ public class PutEmail extends AbstractProcessor {
|
||||
.allowableValues("true", "false")
|
||||
.defaultValue("false")
|
||||
.build();
|
||||
|
||||
|
||||
public static final Relationship REL_SUCCESS = new Relationship.Builder().name("success").description("FlowFiles that are successfully sent will be routed to this relationship").build();
|
||||
public static final Relationship REL_FAILURE = new Relationship.Builder().name("failure").description("FlowFiles that fail to send will be routed to this relationship").build();
|
||||
|
||||
private List<PropertyDescriptor> properties;
|
||||
private Set<Relationship> relationships;
|
||||
|
||||
/**
|
||||
* Mapping of the mail properties to the NiFi PropertyDescriptors that will be evaluated at runtime
|
||||
*/
|
||||
private static Map<String, PropertyDescriptor> propertyToContext = new HashMap<String, PropertyDescriptor>();
|
||||
static {
|
||||
propertyToContext.put("mail.smtp.host", SMTP_HOSTNAME);
|
||||
propertyToContext.put("mail.smtp.port", SMTP_PORT);
|
||||
propertyToContext.put("mail.smtp.socketFactory.port", SMTP_PORT);
|
||||
propertyToContext.put("mail.smtp.socketFactory.class", SMTP_SOCKET_FACTORY);
|
||||
propertyToContext.put("mail.smtp.auth", SMTP_AUTH);
|
||||
propertyToContext.put("mail.smtp.starttls.enable", SMTP_TLS);
|
||||
propertyToContext.put("mail.smtp.user", SMTP_USERNAME);
|
||||
propertyToContext.put("mail.smtp.password", SMTP_PASSWORD);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init(final ProcessorInitializationContext context) {
|
||||
@ -166,6 +219,10 @@ public class PutEmail extends AbstractProcessor {
|
||||
properties.add(SMTP_PORT);
|
||||
properties.add(SMTP_USERNAME);
|
||||
properties.add(SMTP_PASSWORD);
|
||||
properties.add(SMTP_AUTH);
|
||||
properties.add(SMTP_TLS);
|
||||
properties.add(SMTP_SOCKET_FACTORY);
|
||||
properties.add(HEADER_XMAILER);
|
||||
properties.add(FROM);
|
||||
properties.add(TO);
|
||||
properties.add(CC);
|
||||
@ -214,9 +271,10 @@ public class PutEmail extends AbstractProcessor {
|
||||
return;
|
||||
}
|
||||
|
||||
final Properties properties = new Properties();
|
||||
properties.setProperty("smtp.mail.host", context.getProperty(SMTP_HOSTNAME).getValue());
|
||||
final Session mailSession = Session.getInstance(properties);
|
||||
final Properties properties = this.getMailPropertiesFromFlowFile(context, flowFile);
|
||||
|
||||
final Session mailSession = this.createMailSession(properties);
|
||||
|
||||
final Message message = new MimeMessage(mailSession);
|
||||
final ProcessorLog logger = getLogger();
|
||||
|
||||
@ -232,7 +290,7 @@ public class PutEmail extends AbstractProcessor {
|
||||
final InternetAddress[] bccAddresses = toInetAddresses(context.getProperty(BCC).evaluateAttributeExpressions(flowFile).getValue());
|
||||
message.setRecipients(RecipientType.BCC, bccAddresses);
|
||||
|
||||
message.setHeader("X-Mailer", "NiFi");
|
||||
message.setHeader("X-Mailer", context.getProperty(HEADER_XMAILER).evaluateAttributeExpressions(flowFile).getValue());
|
||||
message.setSubject(context.getProperty(SUBJECT).evaluateAttributeExpressions(flowFile).getValue());
|
||||
String messageText = context.getProperty(MESSAGE).evaluateAttributeExpressions(flowFile).getValue();
|
||||
|
||||
@ -264,18 +322,8 @@ public class PutEmail extends AbstractProcessor {
|
||||
multipart.addBodyPart(mimeFile);
|
||||
message.setContent(multipart);
|
||||
}
|
||||
|
||||
final String smtpHost = context.getProperty(SMTP_HOSTNAME).getValue();
|
||||
final SMTPTransport transport = new SMTPTransport(mailSession, new URLName(smtpHost));
|
||||
try {
|
||||
final int smtpPort = context.getProperty(SMTP_PORT).asInteger();
|
||||
final String smtpUsername = context.getProperty(SMTP_USERNAME).getValue();
|
||||
final String smtpPassword = context.getProperty(SMTP_PASSWORD).getValue();
|
||||
transport.connect(smtpHost, smtpPort, smtpUsername, smtpPassword);
|
||||
transport.sendMessage(message, message.getAllRecipients());
|
||||
} finally {
|
||||
transport.close();
|
||||
}
|
||||
|
||||
Transport.send(message);
|
||||
|
||||
session.getProvenanceReporter().send(flowFile, "mailto:" + message.getAllRecipients()[0].toString());
|
||||
session.transfer(flowFile, REL_SUCCESS);
|
||||
@ -287,7 +335,66 @@ public class PutEmail extends AbstractProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
public static final String BODY_SEPARATOR = "\n\n--------------------------------------------------\n";
|
||||
/**
|
||||
* Based on the input properties, determine whether an authenticate or unauthenticated session
|
||||
* should be used. If authenticated, creates a Password Authenticator for use in sending the email.
|
||||
*
|
||||
* @param properties
|
||||
* @return
|
||||
*/
|
||||
private Session createMailSession(final Properties properties) {
|
||||
String authValue = properties.getProperty("mail.smtp.auth");
|
||||
Boolean auth = Boolean.valueOf(authValue);
|
||||
|
||||
/*
|
||||
* Conditionally create a password authenticator if the 'auth' parameter is set.
|
||||
*/
|
||||
final Session mailSession = auth ? Session.getInstance(properties, new Authenticator() {
|
||||
@Override
|
||||
public PasswordAuthentication getPasswordAuthentication() {
|
||||
String username = properties.getProperty("mail.smtp.user"),
|
||||
password = properties.getProperty("mail.smtp.password");
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
}) : Session.getInstance(properties); // without auth
|
||||
return mailSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the mapping of javax.mail properties to NiFi PropertyDescriptors to build
|
||||
* the required Properties object to be used for sending this email
|
||||
*
|
||||
* @param context
|
||||
* @param flowFile
|
||||
* @return
|
||||
*/
|
||||
private Properties getMailPropertiesFromFlowFile(final ProcessContext context, final FlowFile flowFile) {
|
||||
|
||||
final Properties properties = new Properties();
|
||||
|
||||
final ProcessorLog logger = this.getLogger();
|
||||
|
||||
for(Entry<String, PropertyDescriptor> entry : propertyToContext.entrySet()) {
|
||||
|
||||
// Evaluate the property descriptor against the flow file
|
||||
String flowFileValue = context.getProperty(entry.getValue()).evaluateAttributeExpressions(flowFile).getValue();
|
||||
|
||||
String property = entry.getKey();
|
||||
|
||||
logger.debug("Evaluated Mail Property: {} with Value: {}", new Object[]{property, flowFileValue});
|
||||
|
||||
// Nullable values are not allowed, so filter out
|
||||
if(null != flowFileValue) {
|
||||
properties.setProperty(property, flowFileValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return properties;
|
||||
|
||||
}
|
||||
|
||||
public static final String BODY_SEPARATOR = "\n\n--------------------------------------------------\n";
|
||||
|
||||
private static String formatAttributes(final FlowFile flowFile, final String messagePrepend) {
|
||||
StringBuilder message = new StringBuilder(messagePrepend);
|
||||
|
@ -19,14 +19,19 @@ package org.apache.nifi.processors.standard;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.nifi.flowfile.FlowFile;
|
||||
import org.apache.nifi.processor.ProcessContext;
|
||||
import org.apache.nifi.processor.ProcessSession;
|
||||
import org.apache.nifi.util.TestRunner;
|
||||
import org.apache.nifi.util.TestRunners;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class TestPutEmail {
|
||||
|
||||
@Test
|
||||
public void testHotNotFound() {
|
||||
public void testHostNotFound() {
|
||||
// verifies that files are routed to failure when the SMTP host doesn't exist
|
||||
final TestRunner runner = TestRunners.newTestRunner(new PutEmail());
|
||||
runner.setProperty(PutEmail.SMTP_HOSTNAME, "host-doesnt-exist123");
|
||||
@ -42,4 +47,37 @@ public class TestPutEmail {
|
||||
runner.assertQueueEmpty();
|
||||
runner.assertAllFlowFilesTransferred(PutEmail.REL_FAILURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmailPropertyFormatters() {
|
||||
// verifies that files are routed to failure when the SMTP host doesn't exist
|
||||
final TestRunner runner = TestRunners.newTestRunner(new PutEmail());
|
||||
runner.setProperty(PutEmail.HEADER_XMAILER, "TestingNiFi");
|
||||
runner.setProperty(PutEmail.SMTP_HOSTNAME, "smtp-host");
|
||||
runner.setProperty(PutEmail.SMTP_SOCKET_FACTORY, "${dynamicSocketFactory}");
|
||||
runner.setProperty(PutEmail.HEADER_XMAILER, "TestingNiFi");
|
||||
runner.setProperty(PutEmail.FROM, "test@apache.org");
|
||||
runner.setProperty(PutEmail.MESSAGE, "Message Body");
|
||||
runner.setProperty(PutEmail.TO, "recipient@apache.org");
|
||||
|
||||
ProcessSession session = runner.getProcessSessionFactory().createSession();
|
||||
FlowFile ff = session.create();
|
||||
ff = session.putAttribute(ff, "dynamicSocketFactory", "testingSocketFactory");
|
||||
ProcessContext context = runner.getProcessContext();
|
||||
|
||||
String xmailer = context.getProperty(PutEmail.HEADER_XMAILER).evaluateAttributeExpressions(ff).getValue();
|
||||
assertEquals("X-Mailer Header", "TestingNiFi", xmailer);
|
||||
|
||||
String socketFactory = context.getProperty(PutEmail.SMTP_SOCKET_FACTORY).evaluateAttributeExpressions(ff).getValue();
|
||||
assertEquals("Socket Factory", "testingSocketFactory", socketFactory);
|
||||
|
||||
final Map<String, String> attributes = new HashMap<>();
|
||||
runner.enqueue("Some Text".getBytes(), attributes);
|
||||
|
||||
runner.run();
|
||||
|
||||
runner.assertQueueEmpty();
|
||||
runner.assertAllFlowFilesTransferred(PutEmail.REL_FAILURE);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
@ -26,8 +27,9 @@
|
||||
<module>nifi-standard-processors</module>
|
||||
<module>nifi-standard-prioritizers</module>
|
||||
<module>nifi-standard-reporting-tasks</module>
|
||||
<module>nifi-standard-content-viewer</module>
|
||||
<module>nifi-standard-nar</module>
|
||||
</modules>
|
||||
</modules>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@ -45,6 +47,12 @@
|
||||
<artifactId>nifi-standard-reporting-tasks</artifactId>
|
||||
<version>0.1.0-incubating-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-standard-content-viewer</artifactId>
|
||||
<type>war</type>
|
||||
<version>0.1.0-incubating-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user