/* * Copyright (C) 2014 Intel Corporation * All rights reserved. */ package com.intel.mtwilson.v2.rpc; import com.intel.dcsg.cpg.extensions.Extensions; import com.intel.mtwilson.launcher.ws.ext.RPC; import com.intel.mtwilson.rpc.v2.resource.CallableRpcAdapter; import com.intel.mtwilson.rpc.v2.resource.RpcAdapter; import com.intel.mtwilson.rpc.v2.resource.RunnableRpcAdapter; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Enumeration; import java.util.List; import java.util.concurrent.Callable; import javax.servlet.http.HttpServletRequest; import org.apache.http.HeaderElement; import org.apache.http.NameValuePair; import org.apache.http.message.BasicHeaderElement; import org.apache.http.message.BasicHeaderValueParser; import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.ParserCursor; import org.apache.http.util.CharArrayBuffer; import org.glassfish.jersey.message.MessageBodyWorkers; /** * * @author jbuhacoff */ public class RpcUtil { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RpcUtil.class); /** * The Jersey framework injects MessageBodyWorkers into AsyncRpc via * @Context, and AsyncRpc stores it here so that RpcInvoker running * in the background can still use it to parse and generate messages * from/to the database. * One possible issue is that if the web service is restarted, something * needs to start RpcInvoker in the background to continue processing * RPCs stored in the database... but messageBodyWorkers will be * null until the first RPC request processed by RpcUtil. * If this happens, one approach is to send an RPC request to a * non-existing "init_message_body_workers" call, which the AsyncRpc * will handle and even though the RPC itself will fail due to not * existing, by that time the messagebodyworkers will already be * set here and a background RpcInvoker will be able to use it. */ // private static MessageBodyWorkers messageBodyWorkers = null; /** * Will set the static variable messageBodyWorkers only if it has * not yet been set. * * @param mbw injected by Jersey via @Context */ // public static void offerMessageBodyWorkers(MessageBodyWorkers mbw) { // if( messageBodyWorkers == null ) { // messageBodyWorkers = mbw; // } // } /** * * @return static variable messageBodyWorkers, or null if it has not been set */ // public static MessageBodyWorkers getMessageBodyWorkers() { // return messageBodyWorkers; // } private static RpcAdapter createAdapter(Object rpcObject) throws InstantiationException, IllegalAccessException { Object rpcInstance = rpcObject.getClass().newInstance(); // create a new instance of the RPC object to prevent multi-threaded access to the same instance where client A invokes RPC and sets inputs and then client B invokes RPC and sets inputs and then client A gets the result of client B's inputs if( rpcInstance instanceof Callable ) { return new CallableRpcAdapter((Callable)rpcInstance); } if( rpcInstance instanceof Runnable ) { return new RunnableRpcAdapter((Runnable)rpcInstance); } return null; } public static RpcAdapter findRpcForName(String name) { ArrayList<Object> found = new ArrayList<>(); List<Object> rpcs = Extensions.findAllAnnotated(RPC.class); for(Object rpc : rpcs) { if( rpc.getClass().isAnnotationPresent(RPC.class) ) { RPC rpcAnnotation = rpc.getClass().getAnnotation(RPC.class); if( rpcAnnotation.value() == null ) { continue; } if( rpcAnnotation.value().equals(name) ) { found.add(rpc); } } } if( found.isEmpty() ) { return null; } if( found.size() > 1 ) { log.error("Application configuration error: multiple RPC extensions found for {}: {}", name, found); return null; } try { RpcAdapter adapter = createAdapter(found.get(0)); if( adapter == null ) { log.error("Cannot find RpcAdapter for {}", name); return null; } return adapter; } catch(InstantiationException | IllegalAccessException e ) { log.error("Cannot instantiate RPC {} class {}: {}", name, found.get(0).getClass().getName(), e.getMessage()); return null; } } public static com.intel.dcsg.cpg.util.MultivaluedHashMap<String,String> convertHeadersToMultivaluedMap(HttpServletRequest request) { com.intel.dcsg.cpg.util.MultivaluedHashMap<String,String> map = new com.intel.dcsg.cpg.util.MultivaluedHashMap<String,String>(); Enumeration<String> names = request.getHeaderNames(); while(names.hasMoreElements()) { String name = names.nextElement(); Enumeration<String> values = request.getHeaders(name); while(values.hasMoreElements()) { String value = values.nextElement(); map.add(name, value); } } return map; } /** * accept can look like this: "application/json;0.9, application/xml;0.8, text/plain"; */ public static String getPreferredTypeFromAccept(String accept) { if( accept == null || accept.isEmpty() ) { return "*/*"; } // or should we default to json or xml? // this way doesn't work: // java.lang.IllegalArgumentException: Error parsing media type 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' // Caused by: java.text.ParseException: Expected separator ';' instead of ',' /* CharArrayBuffer buffer = new CharArrayBuffer(accept.length()); buffer.append(accept); BasicHeaderValueParser parser = new BasicHeaderValueParser(); HeaderElement[] headerElements = parser.parseElements(buffer, new ParserCursor(0,accept.length())); // sample headerElements structure: // [{"name":"application/json","value":null,"parameters":[{"name":"0.9","value":null}],"parameterCount":1},{"name":"application/xml","value":null,"parameters":[{"name":"0.8","value":null}],"parameterCount":1},{"name":"text/plain","value":null,"parameters":[],"parameterCount":0},{"name":" * / * ","value":null,"parameters":[],"parameterCount":0}] // sort according to ascending priority ... so most preferred wil be at the end of the array Arrays.sort(headerElements, new AcceptComparator()); // now grab the last element return headerElements[headerElements.length-1].getName(); */ String[] contentTypeArray = accept.replace(" ","").split(","); BasicHeaderElement[] headerElements = new BasicHeaderElement[contentTypeArray.length]; for(int i=0; i<contentTypeArray.length; i++) { String[] typePreference = contentTypeArray[i].split(";"); String name = typePreference[0]; if( typePreference.length > 1 ) { BasicNameValuePair[] parameters = new BasicNameValuePair[typePreference.length-1]; // because index 0 is the cnotent type name itself for(int j=1; j<typePreference.length; j++) { String[] parameterValue = typePreference[j].split("="); BasicNameValuePair nameValuePair = new BasicNameValuePair(parameterValue[0], parameterValue[1]); // like q = 0.9 parameters[j-1] = nameValuePair; } headerElements[i] = new BasicHeaderElement(name, null, parameters); } else { headerElements[i] = new BasicHeaderElement(name, null); } } Arrays.sort(headerElements, new AcceptComparator()); return headerElements[headerElements.length-1].getName(); } public static class AcceptComparator implements Comparator<HeaderElement> { @Override public int compare(HeaderElement o1, HeaderElement o2) { if( o1.getParameterCount() == 0 && o2.getParameterCount() > 0 ) { return -1; } if( o1.getParameterCount() > 0 && o2.getParameterCount() == 0 ) { return 1; } if( o1.getParameterCount() > 0 && o2.getParameterCount() > 0 ) { NameValuePair nvpair1 = o1.getParameter(0); NameValuePair nvpair2 = o2.getParameter(0); try { // Float p1 = Float.parseFloat(nvpair1.getName().replace("q=", "")); // the name is the preference like 0.9, 0.8, etc. // Float p2 = Float.parseFloat(nvpair2.getName().replace("q=", "")); Float p1 = Float.parseFloat(nvpair1.getValue()); // the name is the parameter name "q" as in q=0.9 ; the value is the float like 0.9 or 0.8 Float p2 = Float.parseFloat(nvpair2.getValue()); if( p1 <= p2 ) { return -1; } else { return 1; } //if( p1 > p2 ) { return 1; } } catch(NumberFormatException e) { log.debug("Failed to parse preference in accept header: {}", e.getMessage()); return 0; } //return 1; } return 0; } } }