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

org.simplity.service.ServiceAgent Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2015 EXILANT Technologies Private Limited (www.exilant.com)
 * Copyright (c) 2016 simplity.org
 *
 * 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, OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.simplity.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.Date;

import org.simplity.json.JSONWriter;
import org.simplity.kernel.Application;
import org.simplity.kernel.ApplicationError;
import org.simplity.kernel.Messages;
import org.simplity.kernel.Tracer;
import org.simplity.kernel.comp.ComponentManager;
import org.simplity.kernel.file.FileManager;
import org.simplity.kernel.value.Value;
import org.simplity.kernel.value.ValueType;

/***
 * Sole agent to be approached for any service from the app. App functionality
 * is delivered strictly thru this one class. Agent prepares the right
 * infrastructure for the service before calling it.
 *
 * This is to be used "internally" by another class, after taking care of
 * authentication, session management etc.. ServiceAgent assumes that the caller
 * is entitled for this service, and it is up to service to do any additional
 * security/based on userId. It also assumes that the caller has authenticated
 * the userId.
 */
public class ServiceAgent {

	/**
	 * singleton instance that is instantiated with right parameters
	 */
	private static ServiceAgent instance;

	/**
	 * Set plugins and parameters for agent
	 * @param autoLoginUserId 
	 *
	 * @param userIdIsNumber
	 * @param login
	 * @param logout
	 * @param cacher
	 * @param guard
	 */
	public static void setUp(String autoLoginUserId, boolean userIdIsNumber, String login, String logout, ServiceCacheManager cacher,
			AccessController guard) {
		instance = new ServiceAgent(autoLoginUserId,userIdIsNumber, login, logout, cacher, guard);
	}

	/**
	 * @return an instance for use
	 */
	public static ServiceAgent getAgent() {
		if (instance == null) {
			throw new ApplicationError("Service Agent is not set up, but there are requests for service!!");
		}
		return instance;
	}

	/**
	 * is user Id a numeric value? default is text
	 */
	private final boolean numericUserId;
	/**
	 * login service to be called. If null, we use a dummy user id of 100 to
	 * auto-login
	 */
	private final String loginService;

	/**
	 * application may need to so something on logout
	 */
	private final String logoutService;

	/**
	 * service response may be cached. This may also be used to have fake
	 * responses during development
	 */
	private final ServiceCacheManager cacheManager;
	/**
	 * registered access control class
	 */
	private final AccessController securityManager;
	/**
	 * autologin ID
	 */
	private final String autoLoginUserId;

	/***
	 * We create an immutable instance fully equipped with all plug-ins
	 * @param autoLoginUserId 
	 */
	private ServiceAgent(String autoLoginUserId, boolean userIdIsNumber, String login, String logout, ServiceCacheManager cacher,
			AccessController guard) {
		this.autoLoginUserId = autoLoginUserId;
		this.numericUserId = userIdIsNumber;
		this.loginService = login;
		this.logoutService = logout;
		this.cacheManager = cacher;
		this.securityManager = guard;
	}

	/**
	 * ask service to handle this special service
	 *
	 * @param inputData
	 *
	 * @return fields to be put into session. Note that we do not return any
	 *         response to client to data returned by standard service call.
	 *         This is typically used as global fields for the user session.
	 */
	public ServiceData login(ServiceData inputData) {
		boolean isAutoLogin = false;
		if(!autoLoginUserId.isEmpty() && autoLoginUserId!=null && autoLoginUserId.equals(inputData.get(ServiceProtocol.USER_ID).toString()))
			isAutoLogin = true;
		
		inputData.put(ServiceProtocol.IS_AUTO_LOGIN, Value.newBooleanValue(isAutoLogin));
		
		ServiceData result = null;
		if (this.loginService == null) {
			result = this.dummyLogin(inputData);
		} else {
			result = ComponentManager.getService(this.loginService).respond(inputData);
		}
		if (result.hasErrors() == false) {
			Object uid = result.get(ServiceProtocol.USER_ID);
			if (uid == null) {
				Tracer.trace("Login service did not set value for " + ServiceProtocol.USER_ID
						+ ". This implies that the login has failed.");
			} else {
				if (uid instanceof Value == false) {
					throw new ApplicationError("Login service returned userId as a field in " + ServiceProtocol.USER_ID
							+ " but instead of being an instance of Value we found it an instance of "
							+ uid.getClass().getName());
				}
				result.setUserId((Value) uid);
			}
		}
		return result;
	}

	private ServiceData dummyLogin(ServiceData inData) {
		ServiceData result = new ServiceData();
		Tracer.trace("No login service is attached. we use a dummy login.");
		String userId = "100";
		Object obj = inData.get(ServiceProtocol.USER_ID);
		if (obj != null) {
			userId = obj.toString();
		}
		Value userIdValue = this.numericUserId ? Value.parseValue(userId, ValueType.INTEGER)
				: Value.newTextValue(userId);
		if (Value.isNull(userIdValue)) {
			Tracer.trace("I would have cleared userId " + userId + " but for the fact that we insist on a number");
		} else {
			Tracer.trace("we cleared userId=" + userId + " with no authentication whatsoever.");
			result.put(ServiceProtocol.USER_ID, userIdValue);
		}
		return result;
	}

	/**
	 * user has logged-out. Take care of any update on the server
	 *
	 * @param inputData
	 */
	public void logout(ServiceData inputData) {
		if (this.logoutService != null) {
			ComponentManager.getService(this.logoutService).respond(inputData);
		}
	}

	/**
	 * @param inputData
	 *            fields are assumed to be session data, and payLoad is the
	 *            request string from client
	 * @return response to be returned to client payLoad is response text, while
	 *         fields collection is data to be set to session. Null if the
	 *         service not found or the logged-in user is not entitled for the
	 *         service
	 *
	 */
	public ServiceData executeService(ServiceData inputData) {
		/*
		 * this is the entry point for the app-side. This method may be invoked
		 * either remotely, or with HttpAgent on the same JVM. This is
		 * distinction need not be detected, except for a small issue with
		 * trace.
		 */
		boolean isRemoteCall = Tracer.acucumulationIsOn() == false;
		if (isRemoteCall) {
			Tracer.startAccumulation();
		}

		String serviceName = inputData.getServiceName();
		Value userId = inputData.getUserId();
		ServiceInterface service = ComponentManager.getServiceOrNull(serviceName);
		ServiceData response = null;
		Date startTime = new Date();

		/*
		 * do block is convenient to put breaks and avoid over-dose of else-if
		 */
		do {
			/*
			 * do we have this service?
			 */
			if (service == null) {
				Tracer.trace("Service " + serviceName + " is missing in action !!");
				response = this.defaultResponse(inputData);
				response.addMessage(Messages.getMessage(Messages.NO_SERVICE, serviceName));
				break;
			}

			/*
			 * is it accessible to user?
			 */
			if (this.securityManager != null && this.securityManager.okToServe(service, inputData) == false) {
				response = this.defaultResponse(inputData);
				/*
				 * should we say you are not authorized?
				 */
				Tracer.trace("Logged in user " + userId + " is not granted access to this service");
				response.addMessage(Messages.getMessage(Messages.NO_ACCESS));
				break;
			}
			/*
			 * is it cached?
			 */
			if (this.cacheManager != null) {
				response = this.cacheManager.respond(inputData);
				if (response != null) {
					break;
				}
			}
			/*
			 * is this to be run in the background always?
			 */
			if (service.toBeRunInBackground()) {
				response = this.runInBackground(inputData, service);
				break;
			}
			/*
			 * OK. here we go and call the actual service
			 */
			try {
				Tracer.trace("Invoking service " + serviceName);
				response = service.respond(inputData);
				boolean hasErrors = response != null && response.hasErrors();
				if (hasErrors) {
					Tracer.trace(serviceName + " returned with errors.");
				} else {
					Tracer.trace(serviceName + " responded with all OK signal");
				}
				if (this.cacheManager != null && hasErrors == false) {
					this.cacheManager.cache(inputData, response);
				}
			} catch (Exception e) {
				Application.reportApplicationError(inputData, e);
				Tracer.trace(e, "Exception thrown by service " + serviceName);
				response = this.defaultResponse(inputData);
				response.addMessage(Messages.getMessage(Messages.INTERNAL_ERROR, e.getMessage()));
			}
		} while (false);

		Date endTime = new Date();
		long diffTime = endTime.getTime() - startTime.getTime();
		if (response != null) {
			response.setExecutionTime((int) diffTime);
			if (isRemoteCall) {
				response.setTrace(Tracer.stopAccumulation());
			}
		}
		return response;
	}

	/**
	 * @param inData
	 * @return response to be sent back to client
	 */
	@SuppressWarnings("resource")
	private ServiceData runInBackground(ServiceData inData, ServiceInterface service) {
		ServiceData outData = this.defaultResponse(inData);
		ObjectOutputStream stream = null;
		String token = null;
		File file = FileManager.createTempFile();
		try {
			stream = new ObjectOutputStream(new FileOutputStream(file));
			token = file.getName();
			inData.put(ServiceProtocol.HEADER_FILE_TOKEN, token);
		} catch (Exception e) {
			throw new ApplicationError(e, "Error while creating file for output from  bckground job");
		}
		JSONWriter writer = new JSONWriter();
		writer.object().key(ServiceProtocol.HEADER_FILE_TOKEN).value(token).endObject();
		outData.setPayLoad(writer.toString());
		ServiceSubmitter submitter = new ServiceSubmitter(inData, service, stream);
		Thread thread = Application.createThread(submitter);
		thread.start();

		return outData;
	}

	/**
	 * create a response with right headers..
	 *
	 * @param inData
	 * @return
	 */
	private ServiceData defaultResponse(ServiceData inData) {
		return new ServiceData(inData.getUserId(), inData.getServiceName());
	}

	/**
	 * invalidate any cached response for this service
	 *
	 * @param serviceName
	 */
	public static void invalidateCache(String serviceName) {
		if (instance.cacheManager != null) {
			instance.cacheManager.invalidate(serviceName);
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy