package com.kaltura.client; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import javax.xml.xpath.XPathExpressionException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.ProxyHost; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.multipart.FilePart; import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; import org.apache.commons.httpclient.methods.multipart.Part; import org.apache.commons.httpclient.methods.multipart.PartSource; import org.apache.commons.httpclient.methods.multipart.StringPart; import org.apache.commons.httpclient.params.HttpConnectionManagerParams; import org.apache.commons.httpclient.params.HttpMethodParams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import com.kaltura.client.enums.KalturaSessionType; import com.kaltura.client.utils.XmlUtils; /** * Contains non-generated client logic. Includes the doQueue method which is responsible for * making HTTP calls to the Kaltura server. * * @author jpotts * @author azeckoski */ abstract public class KalturaClientBase { protected KalturaConfiguration kalturaConfiguration; protected String sessionId; protected List<KalturaServiceActionCall> callsQueue; protected boolean isMultiRequest; protected KalturaParams multiRequestParamsMap; private static Logger logger = LoggerFactory.getLogger(KalturaClientBase.class); public KalturaClientBase() { } public KalturaClientBase(KalturaConfiguration config) { this.kalturaConfiguration = config; this.callsQueue = new ArrayList<KalturaServiceActionCall>(); this.multiRequestParamsMap = new KalturaParams(); } abstract String getApiVersion(); public String getSessionId() { return this.sessionId; } public void setSessionId(String sessionId) { this.sessionId = sessionId; } public boolean isMultiRequest() { return isMultiRequest; } public void setMultiRequest(boolean isMultiRequest) { this.isMultiRequest = isMultiRequest; } public void setKalturaConfiguration(KalturaConfiguration kalturaConfiguration) { this.kalturaConfiguration = kalturaConfiguration; } public void queueServiceCall(String service, String action, KalturaParams kparams) { this.queueServiceCall(service, action, kparams, new KalturaFiles()); } public void queueServiceCall(String service, String action, KalturaParams kparams, KalturaFiles kfiles) { // in start session partner id is optional (default -1). if partner id was not set, use the one in the config if (!kparams.containsKey("partnerId")) kparams.addIntIfNotNull("partnerId", this.kalturaConfiguration.getPartnerId()); if (kparams.get("partnerId").equals("-1")) kparams.addIntIfNotNull("partnerId", this.kalturaConfiguration.getPartnerId()); kparams.addStringIfNotNull("ks", this.sessionId); KalturaServiceActionCall call = new KalturaServiceActionCall(service, action, kparams, kfiles); this.callsQueue.add(call); } public Element doQueue() throws KalturaApiException { if (this.callsQueue.isEmpty()) return null; if (logger.isDebugEnabled()) { logger.debug("service url: [" + this.kalturaConfiguration.getEndpoint() + "]"); } KalturaParams kparams = new KalturaParams(); KalturaFiles kfiles = new KalturaFiles(); // append the basic params kparams.put("apiVersion", this.getApiVersion()); kparams.put("clientTag", this.kalturaConfiguration.getClientTag()); kparams.addIntIfNotNull("format", this.kalturaConfiguration.getServiceFormat().getHashCode()); String url = this.kalturaConfiguration.getEndpoint() + "/api_v3/index.php?service="; if (isMultiRequest) { url += "multirequest"; int i = 1; for (KalturaServiceActionCall call : this.callsQueue) { KalturaParams callParams = call.getParamsForMultiRequest(i++); kparams.add(callParams); kfiles.add(call.getFiles()); } // map params for (String key : this.multiRequestParamsMap.keySet()) { String requestParam = key; String resultParam = this.multiRequestParamsMap.get(key); if (kparams.containsKey(requestParam)) { kparams.put(requestParam, resultParam); } } } else { KalturaServiceActionCall call = this.callsQueue.get(0); url += call.getService() + "&action=" + call.getAction(); kparams.add(call.getParams()); kfiles.add(call.getFiles()); } // cleanup this.callsQueue.clear(); this.isMultiRequest = false; this.multiRequestParamsMap.clear(); kparams.put("sig", this.signature(kparams)); if (logger.isDebugEnabled()) { logger.debug("full reqeust url: [" + url + "?" + kparams.toQueryString() + "]"); } // build request HttpClient client = new HttpClient(); // added by Unicon to handle proxy hosts String proxyHost = System.getProperty( "http.proxyHost" ); if ( proxyHost != null ) { int proxyPort = -1; String proxyPortStr = System.getProperty( "http.proxyPort" ); if (proxyPortStr != null) { try { proxyPort = Integer.parseInt( proxyPortStr ); } catch (NumberFormatException e) { logger.warn("Invalid number for system property http.proxyPort ("+proxyPortStr+"), using default port instead"); } } ProxyHost proxy = new ProxyHost( proxyHost, proxyPort ); client.getHostConfiguration().setProxyHost( proxy ); } // added by Unicon to force encoding to UTF-8 String utf8CharSet = "UTF-8"; client.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, utf8CharSet); client.getParams().setParameter(HttpMethodParams.HTTP_ELEMENT_CHARSET, utf8CharSet); client.getParams().setParameter(HttpMethodParams.HTTP_URI_CHARSET, utf8CharSet); HttpConnectionManagerParams connParams = client.getHttpConnectionManager().getParams(); connParams.setSoTimeout(this.kalturaConfiguration.getTimeout()); client.getHttpConnectionManager().setParams(connParams); PostMethod method = new PostMethod(url); method.setRequestHeader("Accept","text/xml,application/xml,*/*"); method.setRequestHeader("Accept-Charset","utf-8,ISO-8859-1;q=0.7,*;q=0.5"); if (!kfiles.isEmpty()) { method = this.getPostMultiPartWithFiles(method, kparams, kfiles); } else { method = this.addParams(method, kparams); } // Provide custom retry handler is necessary method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler (3, false)); String responseString = ""; try { // Execute the method. int statusCode = client.executeMethod(method); Header[] headers = method.getRequestHeaders(); if (logger.isDebugEnabled()) { for (Header header : headers) { logger.debug("Header [" + header.getName() + " value [" + header.getValue() + "]"); } } if (statusCode != HttpStatus.SC_OK) { System.err.println ( "Method failed: " + method.getStatusLine ( ) ); } // Read the response body. byte[] responseBody = method.getResponseBody ( ); // Deal with the response. // Use caution: ensure correct character encoding and is not binary data responseString = new String(responseBody, utf8CharSet); // Unicon: this MUST be set to UTF-8 charset -AZ if (logger.isDebugEnabled()) { logger.debug(responseString); } } catch ( HttpException e ) { System.err.println ( "Fatal protocol violation: " + e.getMessage ( ) ); e.printStackTrace ( ); } catch ( IOException e ) { System.err.println ( "Fatal transport error: " + e.getMessage ( ) ); e.printStackTrace ( ); } finally { // Release the connection. method.releaseConnection ( ); } Element responseXml = XmlUtils.parseXml(responseString); this.validateXmlResult(responseXml); Element resultXml = null; try { resultXml = XmlUtils.getElementByXPath(responseXml, "/xml/result"); } catch (XPathExpressionException xee) { throw new KalturaApiException("XPath expression exception evaluating result"); } this.throwExceptionOnAPIError(resultXml); return resultXml; } public void startMultiRequest() { isMultiRequest = true; } public KalturaMultiResponse doMultiRequest() throws KalturaApiException { Element multiRequestResult = doQueue(); KalturaMultiResponse multiResponse = new KalturaMultiResponse(); for(int i = 0; i < multiRequestResult.getChildNodes().getLength(); i++) { Element arrayNode = (Element)multiRequestResult.getChildNodes().item(i); KalturaApiException exception = getExceptionOnAPIError(arrayNode); if (exception != null) { multiResponse.add(exception); } else if (arrayNode.getElementsByTagName("objectType").getLength() == 0) { multiResponse.add(arrayNode.getTextContent()); } else { multiResponse.add(KalturaObjectFactory.create(arrayNode)); } } return multiResponse; } public void mapMultiRequestParam(int resultNumber, int requestNumber, String requestParamName) { this.mapMultiRequestParam(resultNumber, null, requestNumber, requestParamName); } public void mapMultiRequestParam(int resultNumber, String resultParamName, int requestNumber, String requestParamName) { String resultParam = "{" + resultNumber + ":result"; if (resultParamName != null && resultParamName != "") resultParam += resultParamName; resultParam += "}"; String requestParam = requestNumber + ":" + requestParamName; this.multiRequestParamsMap.put(requestParam, resultParam); } private String signature(KalturaParams kparams) { String str = ""; for (String key : kparams.keySet()) { str += (key + kparams.get(key)); } MessageDigest mdEnc = null; try { mdEnc = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } mdEnc.update(str.getBytes(), 0, str.length()); String md5 = new BigInteger(1, mdEnc.digest()).toString(16); // Encrypted string return md5; } private void validateXmlResult(Element resultXml) throws KalturaApiException { Element resultElement = null; try { resultElement = XmlUtils.getElementByXPath(resultXml, "/xml/result"); } catch (XPathExpressionException xee) { throw new KalturaApiException("XPath expression exception evaluating result"); } if (resultElement != null) { return; } else { throw new KalturaApiException("Invalid result"); } } private KalturaApiException getExceptionOnAPIError(Element result) throws KalturaApiException { try { Element errorElement = XmlUtils.getElementByXPath(result, "error"); if (errorElement == null) { return null; } Element messageElement = XmlUtils.getElementByXPath(errorElement, "message"); Element codeElement = XmlUtils.getElementByXPath(errorElement, "code"); if (messageElement == null || codeElement == null) { return null; } return new KalturaApiException(messageElement.getTextContent(), codeElement.getTextContent()); } catch (XPathExpressionException xee) { throw new KalturaApiException("XPath expression exception evaluating result"); } } private void throwExceptionOnAPIError(Element result) throws KalturaApiException { Element resultElement = null; try { resultElement = XmlUtils.getElementByXPath(result, "error"); } catch (XPathExpressionException xee) { throw new KalturaApiException("XPath expression exception evaluating result"); } if (resultElement != null) { throw new KalturaApiException(resultElement.getTextContent()); } } private PostMethod getPostMultiPartWithFiles(PostMethod postMethod, KalturaParams kparams, KalturaFiles kfiles) { String boundary = "---------------------------" + System.currentTimeMillis(); List <Part> parts = new ArrayList<Part>(); parts.add(new StringPart (HttpMethodParams.MULTIPART_BOUNDARY, boundary)); for (String key : kparams.keySet()) { parts.add(new StringPart (key,kparams.get(key))); } for (String key : kfiles.keySet()) { final KalturaFile kFile = kfiles.get(key); parts.add(new StringPart (key, "filename="+kFile.getName())); if (kFile.getFile() != null) { // use the file File file = kFile.getFile(); try { parts.add(new FilePart(key, file)); } catch (FileNotFoundException e) { // TODO this sort of leaves the submission in a weird state... -AZ logger.error("Exception while iterating over kfiles", e); } } else { // use the input stream PartSource fisPS = new PartSource() { public long getLength() { return kFile.getSize(); } public String getFileName() { return kFile.getName(); } public InputStream createInputStream() throws IOException { return kFile.getInputStream(); } }; parts.add(new FilePart(key, fisPS)); } } Part allParts[] = new Part[parts.size()]; int i=0; for (Part p : parts) { allParts[i] = p; ++i; } postMethod.setRequestEntity(new MultipartRequestEntity(allParts, postMethod.getParams())); return postMethod; } private PostMethod addParams(PostMethod method, KalturaParams kparams) { for (String key : kparams.keySet()) { method.addParameter(key, kparams.get(key)); } return method; } /* Gonen fix - UTF8PostMethod instead of PostMethod - 20110803 * Unicon: not using this for now -AZ public static class UTF8PostMethod extends PostMethod { public UTF8PostMethod(String url) { super(url); } @Override public String getRequestCharSet() { // return super.getRequestCharSet(); return "UTF-8"; } } */ public String generateSession(String adminSecretForSigning) throws Exception { return this.generateSession(adminSecretForSigning, ""); } public String generateSession(String adminSecretForSigning, String userId) throws Exception { return this.generateSession(adminSecretForSigning, userId, KalturaSessionType.USER); } public String generateSession(String adminSecretForSigning, String userId, KalturaSessionType type) throws Exception { return this.generateSession(adminSecretForSigning, userId, type, -1); } public String generateSession(String adminSecretForSigning, String userId, KalturaSessionType type, int partnerId) throws Exception { return this.generateSession(adminSecretForSigning, userId, type, partnerId, 86400); } public String generateSession(String adminSecretForSigning, String userId, KalturaSessionType type, int partnerId, int expiry) throws Exception { return this.generateSession(adminSecretForSigning, userId, type, partnerId, expiry, ""); } public String generateSession(String adminSecretForSigning, String userId, KalturaSessionType type, int partnerId, int expiry, String privileges) throws Exception { try { // initialize required values int rand = (int)(Math.random() * 32000); expiry = (int)(System.currentTimeMillis() / 1000) + 86400; // build info string StringBuilder sbInfo = new StringBuilder(); sbInfo.append(this.kalturaConfiguration.partnerId).append(";"); // index 0 - partner ID sbInfo.append(this.kalturaConfiguration.partnerId).append(";"); // index 1 - partner pattern - using partner ID sbInfo.append(expiry).append(";"); // index 2 - expiration timestamp sbInfo.append(type.getHashCode()).append(";"); // index 3 - session type sbInfo.append(rand).append(";"); // index 4 - random number sbInfo.append(userId).append(";"); // index 5 - user ID sbInfo.append(privileges); // index 6 - privileges // sign info with SHA1 algorithm MessageDigest algorithm = MessageDigest.getInstance("SHA1"); algorithm.reset(); algorithm.update(adminSecretForSigning.getBytes()); algorithm.update(sbInfo.toString().getBytes()); byte infoSignature[] = algorithm.digest(); // convert signature to hex: String signature = this.convertToHex(infoSignature); // build final string to base64 encode StringBuilder sbToEncode = new StringBuilder(); sbToEncode.append(signature.toString()).append("|").append(sbInfo.toString()); // NOTE: do not use sun classes //BASE64Encoder encoder = new BASE64Encoder(); //String hashedString = encoder.encode(sbToEncode.toString().getBytes()); // encode the signature and info with base64 String hashedString = Base64.encodeBase64String(sbToEncode.toString().getBytes()); // remove line breaks in the session string String ks = hashedString.replace("\n", ""); // return the generated session key (KS) return ks; } catch (NoSuchAlgorithmException ex) { throw new Exception(ex); } } // new function to convert byte array to Hex private String convertToHex(byte[] data) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < data.length; i++) { int halfbyte = (data[i] >>> 4) & 0x0F; int two_halfs = 0; do { if ((0 <= halfbyte) && (halfbyte <= 9)) buf.append((char) ('0' + halfbyte)); else buf.append((char) ('a' + (halfbyte - 10))); halfbyte = data[i] & 0x0F; } while(two_halfs++ < 1); } return buf.toString(); } }