/*
* 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.spring.bootstrap;
import java.io.Closeable;
import java.io.IOException;
import java.net.URLClassLoader;
import java.util.Map;
import org.apache.nifi.spring.SpringDataExchanger;
import org.apache.nifi.spring.SpringNiFiConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.MessageBuilder;
/**
* Scopes instance of itself to a dedicated {@link ClassLoader}, thus allowing
* Spring Application Context and its class path to be modified and refreshed by
* simply re-starting SpringContextProcessor. Also ensures that there are no
* class path collisions between multiple instances of Spring Context Processor
* which are loaded by the same NAR Class Loader.
*/
/*
* This class is for internal use only and must never be instantiated by the NAR
* Class Loader (hence in a isolated package with nothing referencing it). It is
* loaded by a dedicated CL via byte array that represents it ensuring that this
* class can be loaded multiple times by multiple Class Loaders within a single
* instance of NAR.
*/
final class SpringContextDelegate implements Closeable, SpringDataExchanger {
private final Logger logger = LoggerFactory.getLogger(SpringContextDelegate.class);
private final ClassPathXmlApplicationContext applicationContext;
private final MessageChannel toSpringChannel;
private final PollableChannel fromSpringChannel;
private final String configName;
/**
*
*/
private SpringContextDelegate(String configName) {
this.configName = configName;
ClassLoader orig = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
if (logger.isDebugEnabled()) {
logger.debug("Using " + Thread.currentThread().getContextClassLoader()
+ " as context class loader while loading Spring Context '" + configName + "'.");
}
try {
this.applicationContext = new ClassPathXmlApplicationContext(configName);
if (this.applicationContext.containsBean(SpringNiFiConstants.FROM_NIFI)){
this.toSpringChannel = this.applicationContext.getBean(SpringNiFiConstants.FROM_NIFI, MessageChannel.class);
if (logger.isDebugEnabled()) {
logger.debug("Spring Application Context defined in '" + configName
+ "' is capable of receiving messages from NiFi since 'fromNiFi' channel was discovered.");
}
} else {
this.toSpringChannel = null;
}
if (this.applicationContext.containsBean(SpringNiFiConstants.TO_NIFI)){
this.fromSpringChannel = this.applicationContext.getBean(SpringNiFiConstants.TO_NIFI, PollableChannel.class);
if (logger.isDebugEnabled()) {
logger.debug("Spring Application Context defined in '" + configName
+ "' is capable of sending messages to " + "NiFi since 'toNiFi' channel was discovered.");
}
} else {
this.fromSpringChannel = null;
}
if (logger.isInfoEnabled() && this.toSpringChannel == null && this.fromSpringChannel == null){
logger.info("Spring Application Context is headless since neither 'fromNiFi' nor 'toNiFi' channels were defined. "
+ "No data will be exchanged.");
}
} finally {
Thread.currentThread().setContextClassLoader(orig);
}
}
/**
*
*/
@Override
public <T> boolean send(T payload, Map<String, ?> messageHeaders, long timeout) {
if (this.toSpringChannel != null){
return this.toSpringChannel.send(MessageBuilder.withPayload(payload).copyHeaders(messageHeaders).build(), timeout);
} else {
throw new IllegalStateException("Failed to send message to '" + this.configName
+ "'. There are no 'fromNiFi' channels configured which means the Application Conetxt is not set up to receive messages from NiFi");
}
}
/**
*
*/
@SuppressWarnings("unchecked")
@Override
public <T> SpringResponse<T> receive(long timeout) {
if (this.fromSpringChannel != null) {
final Message<T> message = (Message<T>) this.fromSpringChannel.receive(timeout);
if (message != null) {
if (!(message.getPayload() instanceof byte[]) && !(message.getPayload() instanceof String)) {
throw new IllegalStateException("Failed while receiving message from Spring due to the "
+ "payload type being other then byte[] or String which are the only types that are supported. Please "
+ "apply transformation/conversion on Spring side when sending message back to NiFi");
}
return new SpringResponse<T>(message.getPayload(), message.getHeaders());
}
}
return null;
}
/**
*
*/
@Override
public void close() throws IOException {
logger.info("Closing Spring Application Context");
this.applicationContext.close();
if (logger.isInfoEnabled()) {
logger.info("Closing " + this.getClass().getClassLoader());
}
((URLClassLoader) this.getClass().getClassLoader()).close();
logger.info("Successfully closed Spring Application Context and its ClassLoader.");
}
}