All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.exactpro.sf.services.fast.FASTAbstractClient Maven / Gradle / Ivy

There is a newer version: 3.3.241
Show newest version
/******************************************************************************
 * Copyright 2009-2018 Exactpro (Exactpro Systems Limited)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.exactpro.sf.services.fast;

import static com.exactpro.sf.common.messages.structures.StructureUtils.getAttributeValue;

import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.openfast.Context;
import org.openfast.Message;
import org.openfast.MessageBlockReader;
import org.openfast.session.Connection;
import org.openfast.session.FastConnectionException;
import org.openfast.template.TemplateRegistry;
import org.openfast.template.loader.XMLMessageTemplateLoader;
import org.openfast.util.RecordingInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.exactpro.sf.aml.script.actions.WaitAction;
import com.exactpro.sf.common.messages.IMessage;
import com.exactpro.sf.common.messages.MsgMetaData;
import com.exactpro.sf.common.messages.structures.IDictionaryStructure;
import com.exactpro.sf.common.messages.structures.IMessageStructure;
import com.exactpro.sf.common.services.ServiceInfo;
import com.exactpro.sf.common.services.ServiceName;
import com.exactpro.sf.common.util.EPSCommonException;
import com.exactpro.sf.configuration.IDataManager;
import com.exactpro.sf.configuration.IDictionaryManager;
import com.exactpro.sf.configuration.ILoggingConfigurator;
import com.exactpro.sf.configuration.suri.SailfishURI;
import com.exactpro.sf.configuration.workspace.IWorkspaceDispatcher;
import com.exactpro.sf.scriptrunner.actionmanager.actioncontext.IActionContext;
import com.exactpro.sf.services.IInitiatorService;
import com.exactpro.sf.services.IServiceContext;
import com.exactpro.sf.services.IServiceHandler;
import com.exactpro.sf.services.IServiceMonitor;
import com.exactpro.sf.services.IServiceSettings;
import com.exactpro.sf.services.ISession;
import com.exactpro.sf.services.MessageHelper;
import com.exactpro.sf.services.ServiceException;
import com.exactpro.sf.services.ServiceHandlerRoute;
import com.exactpro.sf.services.ServiceStatus;
import com.exactpro.sf.services.fast.converter.ConverterException;
import com.exactpro.sf.services.fast.converter.FastToIMessageConverter;
import com.exactpro.sf.services.fast.filter.IFastMessageFilter;
import com.exactpro.sf.services.fast.filter.SimpleMessageFilter;
import com.exactpro.sf.services.fast.fixup.EofCheckedStream;
import com.exactpro.sf.services.fast.fixup.EofIOException;
import com.exactpro.sf.services.util.ServiceUtil;
import com.exactpro.sf.storage.IMessageStorage;

public abstract class FASTAbstractClient implements IInitiatorService {

    private final Logger logger = LoggerFactory.getLogger(ILoggingConfigurator.getLoggerName(this));

	private volatile ServiceStatus curStatus;

    private ISession session;

	private final AtomicBoolean sessionClosed = new AtomicBoolean(true);

	private FASTClientSettings settings;
	protected ServiceName serviceName;
	private IServiceHandler handler;
	protected IMessageStorage msgStorage;
	private IServiceMonitor monitor;
	protected ServiceInfo serviceInfo;
	protected IWorkspaceDispatcher workspaceDispatcher;

	protected IServiceContext serviceContext;
	protected IDictionaryManager dictionaryManager;
	protected IDictionaryStructure dictionary;
	protected MessageHelper messageHelper;
	protected IFastMessageFilter messageFilter;

    private TemplateRegistry registry;

	private FastToIMessageConverter converter;

    protected Connection connection;

	protected FASTMessageInputStream msgInStream;

    private Thread thread;

	private RecordingInputStream recordingInputStream;

	private ILoggingConfigurator logConfigurator;

    private Context fastContext;

	@Override
	public void init(
			IServiceContext serviceContext,
            IServiceMonitor serviceMonitor,
            IServiceHandler handler,
            IServiceSettings settings,
            ServiceName name) {

		logger.debug("Initializing AbstractFastClient {}", this);

		try {
            changeStatus(ServiceStatus.INITIALIZING, "Service initializing", null);
            logger.info("Initializing service {} ...", this);

            this.serviceName = Objects.requireNonNull(name, "'Service name' parameter");

            this.serviceContext = Objects.requireNonNull(serviceContext, "'Service context' parameter");

            internalInit(name, handler, settings, serviceMonitor, serviceContext);

            changeStatus(ServiceStatus.INITIALIZED, "Service initialized", null);
			logger.debug("Abstract client initialized");

		} catch ( RuntimeException e ) {
            logger.error("Exception during service [{}] initializing", serviceName, e);
            changeStatus(ServiceStatus.ERROR, "", e);
			throw new ServiceException(e);
		}
	}

	protected void internalInit(
            ServiceName name,
            IServiceHandler handler,
            IServiceSettings settings,
            IServiceMonitor serviceMonitor,
            IServiceContext serviceContext) {

        this.monitor = Objects.requireNonNull(serviceMonitor, "'Service monitor' parameter");

        this.msgStorage = Objects.requireNonNull(this.serviceContext.getMessageStorage(), "'Message storage' parameter");

        if (settings == null) {
            throw new NullPointerException("'settings' parameter");
        }
        setSettings((FASTClientSettings)settings);

        this.handler = Objects.requireNonNull(handler, "'Service handler' parameter");

        this.dictionaryManager = Objects.requireNonNull(serviceContext.getDictionaryManager(), "'Dictionary manager' parameter");

        if(getSettings().getDictionaryName() == null) {
            throw new NullPointerException("settings.dictionaryName is null");
        }

        this.workspaceDispatcher = Objects.requireNonNull(serviceContext.getWorkspaceDispatcher(), "'Workspace dispatcher' parameter");

        this.logConfigurator = Objects.requireNonNull(this.serviceContext.getLoggingConfigurator(), "'Logging configurator' parameter");
        this.serviceInfo = Objects.requireNonNull(serviceContext.lookupService(serviceName), "serviceInfo cannot be null");

        logger.info("Initializing service [{}] ... done", serviceName);

        this.messageFilter = configureMessageFilter();
        SailfishURI dictionaryName = getSettings().getDictionaryName();
        this.dictionary = dictionaryManager.getDictionary(dictionaryName);

        String templateName = Objects.requireNonNull(getAttributeValue(dictionary, FASTMessageHelper.TEMPLATE_ATTRIBYTE), "'Template attribute' parameter");
        loadFastTemplates(serviceContext.getDataManager(), dictionaryName.getPluginAlias(), templateName);

        this.messageHelper = new FASTMessageHelper();
        messageHelper.init(dictionaryManager.getMessageFactory(dictionaryName), dictionary);
    }

	private IFastMessageFilter configureMessageFilter() {
		FASTClientSettings settigns = getSettings();
		String requiredValues = settigns.getMessageFilterExpression();
        return requiredValues == null ? new SimpleMessageFilter() : new SimpleMessageFilter(requiredValues);
    }


	protected FastToIMessageConverter createConverter() {
        if(converter == null) {
			FastToIMessageConverter converter = new FastToIMessageConverter(
					dictionaryManager.getMessageFactory(getSettings().getDictionaryName()),
					dictionary
			);
			this.converter = converter;
		}
		return converter;
	}

    private void loadFastTemplates(IDataManager dataManager, String pluginAlias, String templateName) {
		XMLMessageTemplateLoader loader = new XMLMessageTemplateLoader();
		loader.setLoadTemplateIdFromAuxId(true);

		try (InputStream templateStream = dataManager.getDataInputStream(pluginAlias, FASTMessageHelper.getTemplatePath(templateName))) {
			loader.load(templateStream);
		} catch (IOException e) {
            logger.warn("Can not read template {} from resources", templateName, e);
			throw new EPSCommonException("Can not read template " + templateName + " from resources", e);
		}

        setRegistry(loader.getTemplateRegistry());
	}

	protected void changeStatus(ServiceStatus status, String message, Throwable e) {
		this.curStatus = status;
		ServiceUtil.changeStatus(this, monitor, status, message, e);
	}

	@Override
	public IServiceHandler getServiceHandler() {
        return handler;
	}

	@Override
	public void dispose() {
        changeStatus(ServiceStatus.DISPOSING, "Service is disposing", null);
		doDispose();
        changeStatus(ServiceStatus.DISPOSED, "Service disposed", null);

		if(logConfigurator != null) {
            logConfigurator.destroyAppender(getServiceName());
		}
	}

	protected void doDispose() {
		closeSession();
	}

	@Override
	public String getName() {
		return serviceName.toString();
	}

	@Override
	public ServiceName getServiceName() {
		return serviceName;
	}

	@Override
	public void start() {
	    logConfigurator.createAndRegister(getServiceName(), this);
        changeStatus(ServiceStatus.STARTING, "Service is starting", null);
		doStart();
        changeStatus(ServiceStatus.STARTED, "Service is started", null);
	}

	protected abstract void doStart();

	@Override
	public ServiceStatus getStatus() {
		return curStatus;
	}

	@Override
	public ISession getSession() {
		if (session == null) {
            logger.error("Session is null, method getSession returned null");
			return new FASTInvalidSession(getName());
		}
		return session;
	}

    protected ISession createSession() {
        return new FASTSession(this, messageHelper);
    }

	protected Context createFastContext() {
		Context context = new Context();

		context.setTemplateRegistry(getRegistry());
		return context;
	}

	protected abstract void send(Object message) throws InterruptedException;

	protected boolean isLoggedOn() {
		return false;
	}

	protected long getLastActivityTime() {
		return 0;
	}

	protected void setSettings(FASTClientSettings settings) {
		this.settings = settings;
	}

	@Override
	public FASTClientSettings getSettings() {
		return settings;
	}

	public void setRegistry(TemplateRegistry registry) {
		this.registry = registry;
	}

	public TemplateRegistry getRegistry() {
		return registry;
	}

	protected void initConnection() {
		logger.debug("initConnection");
		this.session = createSession();

		int port = getSettings().getPort();
		String remoteAddr = getSettings().getAddress();
		String interfaceAddress = getSettings().getNetworkInterface();
		logger.info("Initializing connection to {}:{} from interface {}", remoteAddr, port, interfaceAddress);

		try {
			this.connection = getConnection(remoteAddr, port, interfaceAddress);
            sessionClosed.set(false);
		} catch (FastConnectionException e) {
			closeSession();
			logger.error("Failed to connect to {}:{}", remoteAddr, port, e);
			throw new EPSCommonException(
					"Failed to connect to " + remoteAddr + ":" + port);
		}
		fastContext = createFastContext();
		InputStream inputStream;
		try {
			inputStream = connection.getInputStream();
			inputStream = new EofCheckedStream(inputStream);
			recordingInputStream = createRecordingInputStream(inputStream);
			inputStream = recordingInputStream;
		} catch (IOException e) {
			closeSession();
			logger.error("Failed to get input stream from connection", e);
			throw new EPSCommonException("Failed to get input stream " +
					"from multicast connection", e);
		}
		msgInStream = new FASTMessageInputStream(inputStream, fastContext);
		msgInStream.setBlockReader(getBlockReader());

		createMsgReadThread();
		logger.debug("initConnection exit");

	}

	protected RecordingInputStream createRecordingInputStream(InputStream inputStream) {
		return new ResizableRecordingInputStream(inputStream);
	}

	protected abstract MessageBlockReader getBlockReader();

	protected abstract Connection getConnection(String remoteAddr, int port, String interfaceAddress) throws FastConnectionException ;

	private void createMsgReadThread() {
		thread = new Thread(new Runnable() {

			@Override
			public void run() {
				logger.debug("In the message receiving thread");

				FastToIMessageConverter converter = createConverter();
				Thread currentThread = Thread.currentThread();

				while(!currentThread.isInterrupted()) {
					Message fastMsg = null;
					try {
						logger.debug("Before reading message from stream");
						fastMsg = msgInStream.readMessage(settings.getSkipInitialByteAmount());
						logger.debug("Message read from stream :{}", fastMsg);
					} catch (Exception e) {
                        if(e.getCause() instanceof EofIOException){
                            closeSession();
                            logger.error("Exception received while reading message: ", e.getMessage());
                        } else {
                            getServiceHandler().exceptionCaught(getSession(), e);
                            logger.error("Exception received while reading message: ", e);
                        }
                        recordingInputStream.clear();
						ServiceStatus status = getStatus();
						if (
								status == ServiceStatus.DISPOSING ||
								status == ServiceStatus.DISPOSED) {
							return;
						}
						if (!recoverFromInputError(msgInStream.getUnderlyingStream())) {
							return;
						}
						continue;
					}
					if (fastMsg == null) {
						ServiceStatus status = getStatus();
						if (status == ServiceStatus.DISPOSING || status == ServiceStatus.DISPOSED) {
							return;
						}
						logger.warn("Received null message");
						continue;
					}
					byte[] rawMessage = recordingInputStream.getBuffer();
					recordingInputStream.clear();
					handleReceivedMessage(fastMsg, converter, rawMessage);
				}
			}

		});
		thread.start();
		logger.debug("Message receiving thread created and started");
	}

	protected boolean recoverFromInputError(InputStream underlyingStream) {
		return false;
	}

	protected void handleReceivedMessage(Message fastMessage, FastToIMessageConverter converter, byte[] rawMessage) {
		logger.debug("handleReceivedMessage");
		IMessage iMsg = null;
		if (!messageFilter.isMessageAcceptable(fastMessage)) {
			logger.debug("Message filtered by messageFilter: {}", fastMessage);
			return;
		}
		try {
			logger.debug("Converting FAST message");
			iMsg = converter.convert(fastMessage);

            IMessageStructure structure = dictionary.getMessages().get(iMsg.getName());
            Boolean isAdmin = getAttributeValue(structure, "IsAdmin");
			if (isAdmin == null) {
				isAdmin = false;
			}

			MsgMetaData metaData = iMsg.getMetaData();
			metaData.setAdmin(isAdmin);
			metaData.setFromService(getSettings().getAddress() +  ":" + getSettings().getPort());
			metaData.setToService(getName());
			metaData.setRawMessage(rawMessage);
			metaData.setServiceInfo(serviceInfo);

			//				metaData.setFromService(session.getTargetCompID());
			logger.debug("passing message to ServericeHandler");
			if (iMsg.getMetaData().isAdmin()) {
				metaData.setAdmin(true);
				getServiceHandler().putMessage(getSession(), ServiceHandlerRoute.FROM_ADMIN, iMsg);
			} else {
				metaData.setAdmin(false);
				getServiceHandler().putMessage(getSession(), ServiceHandlerRoute.FROM_APP, iMsg);
			}
			logger.debug("message passed to ServericeHandler");

//			String humanMessage = iMsg +"," +
//			"(original fast message: " + fastMessage + ")";
			logger.debug("message passed to msgStorage");
            msgStorage.storeMessage(iMsg);

			logger.debug("message stored");
            handleIMessage(iMsg);
		} catch (ConverterException e) {
			logger.info("Conversion of FAST msg to IMessage failed\nfastMsg:{})", fastMessage, e);
			getServiceHandler().exceptionCaught(getSession(), e);
		} catch (Exception e) {
			logger.info("Caught exception while executin fromApp or fromAdmin methods of service handler\nIMessage:{})", iMsg, e);
			getServiceHandler().exceptionCaught(getSession(), e);
		}
	}

    protected void handleIMessage(IMessage iMessage){

    }

    protected synchronized void closeSession() {
        if(sessionClosed.compareAndSet(false, true)) {
            logger.debug("Closing session");
            if (thread != null) {
                thread.interrupt();
            }

            if (connection != null) {
                connection.close();
                connection = null;
            }
            if (msgInStream != null) {
                msgInStream.close();
                msgInStream = null;
            }

            if (thread != null) {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    logger.warn("Current thread interrupted while waiting for another thread", e);
                }
                thread = null;
            }

            if (session != null) {
                session.close();
            }
            logger.debug("Session closed");
        }
    }


	@Override
	public void setServiceHandler(IServiceHandler handler) {
		throw new UnsupportedOperationException("This operation not supported for this service type");
	}

	@Override
	public String toString() {
		return new ToStringBuilder(this).append("name", serviceName).toString();
	}

    protected Context getFastContext() {
        return fastContext;
    }

    public boolean isSessionClosed() {
        return sessionClosed.get();
    }

	@Override
    public IMessage receive(IActionContext actionContext, IMessage msg) throws InterruptedException {
        msg = messageHelper.prepareMessageToEncode(msg, null);
		return WaitAction.waitForMessage(actionContext, msg, !msg.getMetaData().isAdmin());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy