Merge remote-tracking branch 'upstream/develop' into nifi-solr-bundle

Conflicts:
	nifi/nifi-assembly/pom.xml
This commit is contained in:
bbende 2015-03-23 19:46:14 -04:00
commit 4107a0e934
70 changed files with 2241 additions and 269 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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>

View File

@ -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) {

View File

@ -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());
}

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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: &lt;p id="p-id"&gt;text&lt;/p&gt;
*
* @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");
}

View File

@ -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");

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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.");

View File

@ -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;

View File

@ -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;

View File

@ -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">

View File

@ -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>

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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");
}
};
}
}

View File

@ -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())

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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.
========================================================================

View File

@ -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; }

View File

@ -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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2f;'
};
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);
});
});

View File

@ -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 {

View File

@ -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>

View File

@ -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";
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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();

View File

@ -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);

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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());
}
}
}

View File

@ -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())

View File

@ -0,0 +1,3 @@
application/xml
application/json
text/plain

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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);
}

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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>