package com.sap.runlet.expressionpad.views; import integration.binding.HttpBinding; import integration.binding.SimpleUrlPattern; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.net.URI; import java.net.URLDecoder; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import java.util.logging.Logger; import com.sap.runlet.abstractinterpreter.objects.RunletObject; import com.sap.runlet.expressionpad.Activator; import com.sap.runlet.interpreter.RunletInterpreter; import com.sap.runlet.interpreter.RunletStackFrame; import com.sap.runlet.interpreter.objects.FunctionObject; import com.sap.runlet.interpreter.objects.NativeObject; import com.sap.tc.moin.globalmodellistener.GlobalEventListener; import com.sap.tc.moin.globalmodellistener.GlobalEventListenerRegistry; import com.sap.tc.moin.repository.Connection; import com.sap.tc.moin.repository.events.EventChain; import com.sap.tc.moin.repository.events.EventListener; import com.sap.tc.moin.repository.events.UpdateListener; import com.sap.tc.moin.repository.events.filter.AttributeFilter; import com.sap.tc.moin.repository.events.filter.EventFilter; import com.sap.tc.moin.repository.mmi.model.Attribute; import com.sap.tc.moin.repository.mmi.model.MofClass; import com.sap.tc.moin.repository.mql.MQLProcessor; import com.sap.tc.moin.repository.mql.MQLResultSet; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import data.classes.AssociationEnd; import data.classes.ClassTypeDefinition; import data.classes.Parameter; import data.classes.TypeDefinition; import dataaccess.expressions.Expression; public class RunletHTTPHandler implements HttpHandler, Executor { private Logger log = Logger.getLogger(RunletHTTPHandler.class.getName()); private RunletInterpreter interpreter; /** * mapping of URL paths to configured bindings */ private Map<String, HttpBinding> pathBindings; public RunletHTTPHandler(RunletInterpreter theInterpreter) { interpreter = theInterpreter; Connection conn = interpreter.getConnection(); pathBindings = new HashMap<String, HttpBinding>(); loadHTTPBindings(conn); // register for all events on partitions MofClass simpleUrlPattern = interpreter.getConnection().getClass( SimpleUrlPattern.CLASS_DESCRIPTOR).refMetaObject(); Attribute baseUrl = conn.getJmiHelper().getAttributeByName(simpleUrlPattern, "baseUrl", /* includeSupertypes */ false); //$NON-NLS-1$ EventFilter filter = new AttributeFilter(baseUrl); // TODO add more fine-grained filters here, for associations, HttpBinding instance creation/deletion, ... UpdateListener listener = new UpdateListener() { @Override public void notifyUpdate(EventChain event) { // TODO: quite unsave to just reload the binding here without // stopping the HTTP server. loadHTTPBindings(event.getEvents().get(0).getEventTriggerConnection()); } }; // TODO register for service event; when global event registry service appears, register; avoids NPE or checking for NULL GlobalEventListenerRegistry globalRegistry = Activator.getGlobalEventListenerRegistry(); if (globalRegistry == null) { log.severe(Messages.RunletHTTPHandler_2); } else { Map<EventFilter, Map<GlobalEventListener.ListenerType, EventListener>> listeners = new HashMap<EventFilter, Map<GlobalEventListener.ListenerType, EventListener>>(); Map<GlobalEventListener.ListenerType, EventListener> map = new HashMap<GlobalEventListener.ListenerType, EventListener>(); map.put(GlobalEventListener.ListenerType.UPDATE, listener); listeners.put(filter, map); globalRegistry.addFilters(listeners); } } protected void loadHTTPBindings(Connection fromWhereToLoad) { pathBindings.clear(); // Load all HTTP Bindings MQLProcessor mql = fromWhereToLoad.getMQLProcessor(); MQLResultSet queryResult = mql.execute( "select b from integration::binding::HttpBinding as b", mql.getQueryScopeProvider( //$NON-NLS-1$ /* scopeInclusive */false, /* partitionScope */ null, (String[]) null)); for (int i = 0; i < queryResult.getSize(); i++) { HttpBinding binding = (HttpBinding) queryResult.getRefObject(i, "b"); //$NON-NLS-1$ // For now we do not evaluate the Expression upon loading the configuration. // This should be a lot more robust in the presence of changes to the function // implementation. // In the future it should be possible to optimize this part and evaluate the // expressions // right away. This would require to listen for changes on the implementation. pathBindings.put(((SimpleUrlPattern) binding.getUrlPattern()).getBaseUrl(), binding); } } @Override public void handle(HttpExchange t) throws IOException { String response = null; try { // Find a suitable URL binding Expression functionExpression = determineURLBinding(t.getRequestURI()); // Evaluate target to callable FunctionObject RunletInterpreter requestInterpreter = interpreter.spawn(); FunctionObject callTarget = (FunctionObject) requestInterpreter .evaluate(functionExpression); // fill stack frame with actual parameters fillStackFrame(requestInterpreter, callTarget.getImplementation() .getImplementedSignature().getInput(), t.getRequestURI()); // Invoke function RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> result = callTarget.evaluate(requestInterpreter); // Marshall response response = result.toString(); t.sendResponseHeaders(200, response.length()); } catch (IllegalArgumentException iae) { response = Messages.RunletHTTPHandler_5 + iae.getMessage(); t.sendResponseHeaders(404, response.length()); } catch (Exception e) { StringWriter output = new StringWriter(); output.write(Messages.RunletHTTPHandler_6); e.printStackTrace(new PrintWriter(output)); response = output.getBuffer().toString(); t.sendResponseHeaders(500, response.length()); } OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } private void fillStackFrame(RunletInterpreter requestInterpreter, List<Parameter> formalParameters, URI requestURI) throws UnsupportedEncodingException { requestInterpreter.getCallstack().push(new RunletStackFrame()); // get map of parameter names and values encoded in the URL Map<String, String> urlParameters = extractParameters(requestURI); // loop over formal parameters for (Parameter parameter : formalParameters) { String value = urlParameters.get(parameter.getName()); if (value == null) { throw new IllegalStateException(Messages.RunletHTTPHandler_7 + parameter.getName()); } ClassTypeDefinition clazz = (ClassTypeDefinition) parameter.getType(); // for now, only Number and String are supported RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> nativeObject = null; if (clazz.getClazz().getName().equals("Number")) { //$NON-NLS-1$ nativeObject = new NativeObject(clazz, new BigDecimal(value), requestInterpreter.getDefaultSnapshot(), interpreter); } else { // must be a String nativeObject = new NativeObject(clazz, value, requestInterpreter.getDefaultSnapshot(), interpreter); } requestInterpreter.getCallstack().peek().enterValue(parameter, nativeObject); } } private Expression determineURLBinding(URI requestURI) throws IllegalArgumentException { HttpBinding binding = pathBindings.get(requestURI.getPath()); if (binding == null) { StringBuilder str = new StringBuilder(); str.append(Messages.RunletHTTPHandler_9).append(requestURI.getPath()).append("\n"); //$NON-NLS-2$ str.append(Messages.RunletHTTPHandler_11); for (String path : pathBindings.keySet()) { str.append(" ").append(path).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ } throw new IllegalArgumentException(str.toString()); } return binding.getFunction(); } private Map<String, String> extractParameters(URI requestURI) throws UnsupportedEncodingException { Map<String, String> result = new HashMap<String, String>(); String rawQueryString = requestURI.getRawQuery(); if (rawQueryString != null) { String[] parameters = requestURI.getRawQuery().split("&"); //$NON-NLS-1$ for (String parameter : parameters) { String[] pair = parameter.split("="); //$NON-NLS-1$ result.put(URLDecoder.decode(pair[0], "ISO-8859-1"), URLDecoder.decode(pair[1], //$NON-NLS-1$ "ISO-8859-1")); //$NON-NLS-1$ } } return result; } /** * Implementation of handler method from interface Executor */ public void execute(Runnable r) { new Thread(r).start(); } }