Java Source Code: org.outerj.daisy.repository.clientimpl.infrastructure.DaisyHttpClient


   1: /*
   2:  * Copyright 2004 Outerthought bvba and Schaubroeck nv
   3:  *
   4:  * Licensed under the Apache License, Version 2.0 (the "License");
   5:  * you may not use this file except in compliance with the License.
   6:  * You may obtain a copy of the License at
   7:  *
   8:  *     http://www.apache.org/licenses/LICENSE-2.0
   9:  *
  10:  * Unless required by applicable law or agreed to in writing, software
  11:  * distributed under the License is distributed on an "AS IS" BASIS,
  12:  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13:  * See the License for the specific language governing permissions and
  14:  * limitations under the License.
  15:  */
  16: package org.outerj.daisy.repository.clientimpl.infrastructure;
  17: 
  18: import org.apache.commons.httpclient.*;
  19: import org.apache.xmlbeans.XmlObject;
  20: import org.apache.xmlbeans.XmlOptions;
  21: import org.outerj.daisy.repository.RepositoryException;
  22: import org.outerj.daisy.repository.AuthenticationFailedException;
  23: import org.outerj.daisy.repository.RepositoryRuntimeException;
  24: import org.outerj.daisy.xmlutil.LocalSAXParserFactory;
  25: import org.outerx.daisy.x10.ErrorDocument;
  26: import org.outerx.daisy.x10.CauseType;
  27: 
  28: import java.util.Map;
  29: import java.util.ArrayList;
  30: import java.util.HashMap;
  31: import java.util.List;
  32: import java.util.concurrent.ConcurrentHashMap;
  33: import java.lang.reflect.Method;
  34: import java.lang.reflect.Constructor;
  35: import java.lang.reflect.InvocationTargetException;
  36: 
  37:	  public class DaisyHttpClient {
  38:    private static Map<Class, Method> xmlObjectParseMethodCache = new ConcurrentHashMap<Class, Method>(20, .75f, 2);
  39:    private static final boolean validateResponses = false;
  40:    private HttpClient sharedHttpClient;
  41:    private HttpState httpState;
  42:    private HostConfiguration sharedHostConfiguration;
  43:    private String login;
  44:
  45:	      public DaisyHttpClient(HttpClient sharedHttpClient, HostConfiguration sharedHostConfiguration, HttpState httpState, String login) {
  46:        this.sharedHttpClient = sharedHttpClient;
  47:        this.httpState = httpState;
  48:        this.sharedHostConfiguration = sharedHostConfiguration;
  49:        this.login = login;
  50:    }
  51:
  52:	      public static HttpState buildHttpState(String login, String password, long[] activeRoleIds) {
  53:        HttpState httpState = new HttpState();
  54:        httpState.setAuthenticationPreemptive(true);
  55:        // @'s in the login should be escaped by doubling them
  56:        login = login.replaceAll("@", "@@");
  57:        if (activeRoleIds != null)
  58:            login += "@" + getActiveRoleString(activeRoleIds);
  59:        UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(login, password);
  60:        httpState.setCredentials(null, null, credentials);
  61:
  62:        return httpState;
  63:    }
  64:
  65:	      private static String getActiveRoleString(long[] activeRoleIds) {
  66:        StringBuilder buffer = new StringBuilder(activeRoleIds.length * 4);
  67:	          for (int i = 0; i < activeRoleIds.length; i++) {
  68:            if (i > 0)
  69:                buffer.append(',');
  70:            buffer.append(activeRoleIds[i]);
  71:        }
  72:        return buffer.toString();
  73:    }
  74:
  75:    /**
  76:     * Executes the given method, and handles the response to take care of exceptions
  77:     * or non-OK responses, and optionally parses the response body according to the specified
  78:     * XmlObject class. If this method returns without throwing an exception, one can
  79:     * assume that the execution of the HTTP method was successful.
  80:     *
  81:     * @param xmlObjectResponseClass an Apache XmlBeans generated class (having a Factory inner class).
  82:     * @return the XmlObject resulting from the parsing of the response body, or null if no XmlObject
  83:     *         class was specified.
  84:     */
  85:	      public XmlObject executeMethod(HttpMethod method, Class xmlObjectResponseClass, boolean releaseConnection) throws RepositoryException {
  86:	          try {
  87:            int statusCode;
  88:	              try {
  89:                statusCode = sharedHttpClient.executeMethod(sharedHostConfiguration, method, httpState);
  90:            } catch (Exception e) {
  91:                throw new RepositoryException("Problems connecting to repository server.", e);
  92:            }
  93:
  94:	              if (statusCode == HttpStatus.SC_OK) {
  95:	                  if (xmlObjectResponseClass != null) {
  96:                    Method parseMethod = getParseMethod(xmlObjectResponseClass);
  97:                    XmlObject parseResult;
  98:	                      try {
  99:                        parseResult = (XmlObject)parseMethod.invoke(null, method.getResponseBodyAsStream());
 100:                        if (validateResponses)
 101:                            parseResult.validate();
 102:                    } catch (Exception e) {
 103:                        throw new RepositoryException("Error parsing reponse from repository server.", e);
 104:                    }
 105:
 106:                    return parseResult;
 107:                } else {
 108:                    return null;
 109:                }
 110:            } else {
 111:                handleNotOkResponse(method);
 112:                // handleNotOkResponse always throws an exception, thus...
 113:                throw new RuntimeException("This statement should be unreacheable.");
 114:            }
 115:        } finally {
 116:            if (releaseConnection)
 117:                method.releaseConnection();
 118:        }
 119:    }
 120:
 121:	      private static Method getParseMethod(Class xmlObjectClass) {
 122:        Object parseMethod = xmlObjectParseMethodCache.get(xmlObjectClass);
 123:	          if (parseMethod != null) {
 124:            return (Method)parseMethod;
 125:        } else {
 126:            Class[] classes = xmlObjectClass.getClasses();
 127:            Class factoryClass = null;
 128:	              for (Class clazz : classes) {
 129:	                  if (clazz.getName().equals(xmlObjectClass.getName() + "$Factory")) {
 130:                    factoryClass = clazz;
 131:                    break;
 132:                }
 133:            }
 134:
 135:	              if (factoryClass == null) {
 136:                throw new RuntimeException("Missing Factory class in class " + xmlObjectClass.getName());
 137:            }
 138:
 139:            Method newParseMethod;
 140:	              try {
 141:                newParseMethod = factoryClass.getMethod("parse", java.io.InputStream.class);
 142:            } catch (NoSuchMethodException e) {
 143:                throw new RuntimeException("Missing parse method on XmlObject Factory class for " + xmlObjectClass.getName(), e);
 144:            }
 145:
 146:            xmlObjectParseMethodCache.put(xmlObjectClass, newParseMethod);
 147:            return newParseMethod;
 148:        }
 149:    }
 150:
 151:	      public static String getContentType(HttpMethod method) throws RepositoryException {
 152:        String contentType = null;
 153:	          try {
 154:            if (method.getResponseHeader("Content-Type") != null)
 155:                contentType = method.getResponseHeader("Content-Type").getValues()[0].getName();
 156:        } catch (HttpException e) {
 157:            throw new RepositoryException("Error getting Content-Type of the reponse.", e);
 158:        }
 159:        return contentType;
 160:    }
 161:
 162:	      public void handleNotOkResponse(HttpMethod method) throws RepositoryException {
 163:	          if ("text/xml".equals(getContentType(method))) {
 164:            // an error occured server side
 165:            ErrorDocument.Error errorXml;
 166:	              try {
 167:                XmlOptions xmlOptions = new XmlOptions().setLoadUseXMLReader(LocalSAXParserFactory.newXmlReader());
 168:                ErrorDocument errorDocument = ErrorDocument.Factory.parse(method.getResponseBodyAsStream(), xmlOptions);
 169:                errorXml = errorDocument.getError();
 170:            } catch (Exception e) {
 171:                throw new RepositoryException("Error reading error response from repositoryserver", e);
 172:            }
 173:	              if (errorXml.getDescription() != null) {
 174:                throw new RepositoryException("Repository server answered with an error: " + errorXml.getDescription());
 175:            } else {
 176:                CauseType causeXml = errorXml.getCause();
 177:                if (causeXml.getExceptionData() != null)
 178:                    tryRestoreOriginalException(causeXml);
 179:                Exception cause = restoreException(causeXml);
 180:                throw new RepositoryException("Received exception from repository server.", cause);
 181:            }
 182:        } else {
 183:	              if (method.getStatusCode() == 401) {
 184:                throw new AuthenticationFailedException(this.login);
 185:            } else {
 186:                throw new RepositoryException("Unexpected response from repositoryserver: " + method.getStatusCode() + " : " + HttpStatus.getStatusText(method.getStatusCode()));
 187:            }
 188:        }
 189:    }
 190:
 191:	      private static Exception restoreException(CauseType causeXml) {
 192:        String message = causeXml.getException().getMessage();
 193:        String className = causeXml.getException().getType();
 194:
 195:        List<MyStackTraceElement> stackTrace = new ArrayList<MyStackTraceElement>();
 196:        CauseType.StackTrace.StackTraceElement[] stackTraceElements = causeXml.getStackTrace().getStackTraceElementArray();
 197:	          for (CauseType.StackTrace.StackTraceElement stackTraceElement : stackTraceElements) {
 198:            stackTrace.add(new MyStackTraceElement(stackTraceElement.getClassName(), stackTraceElement.getFileName(), stackTraceElement.getLineNumber(), stackTraceElement.getMethodName(), stackTraceElement.getNativeMethod()));
 199:        }
 200:        MyStackTraceElement[] remoteStackTrace = stackTrace.toArray(new MyStackTraceElement[0]);
 201:
 202:        DaisyPropagatedException exception = new DaisyPropagatedException(message, className, remoteStackTrace);
 203:
 204:        CauseType nestedCauseXml = causeXml.getCause();
 205:	          if (nestedCauseXml != null) {
 206:            Exception cause = restoreException(nestedCauseXml);
 207:            exception.initCause(cause);
 208:        }
 209:
 210:        return exception;
 211:    }
 212:
 213:    /**
 214:     * This method handles exceptions which can be restored, ie RepositoryException's
 215:     * whose getState method returned a Map and have a constructor that takes a Map
 216:     * as argument.
 217:     *
 218:     * <p>If the exception could be restored, this method will throw it immediatelly,
 219:     * otherwise it will simply return. Only call this method if there is actually
 220:     * ExceptionData, otherwise this will throw a NPE.
 221:     */
 222:	      private static void tryRestoreOriginalException(CauseType causeXml) throws RepositoryException {
 223:        String className = causeXml.getException().getType();
 224:        CauseType.ExceptionData exceptionData = causeXml.getExceptionData();
 225:
 226:        Map<String, String> state = new HashMap<String, String>();
 227:	          for (CauseType.ExceptionData.Parameter parameter : exceptionData.getParameterArray()) {
 228:            state.put(parameter.getName(), parameter.getValue());
 229:        }
 230:
 231:        Class clazz ;
 232:	          try {
 233:            clazz = DaisyHttpClient.class.getClassLoader().loadClass(className);
 234:        } catch (ClassNotFoundException e) {
 235:            return;
 236:        }
 237:
 238:        if (!RepositoryException.class.isAssignableFrom(clazz) && !RepositoryRuntimeException.class.isAssignableFrom(clazz))
 239:            return;
 240:
 241:        Constructor constructor;
 242:	          try {
 243:            constructor = clazz.getConstructor(Map.class);
 244:        } catch (NoSuchMethodException e) {
 245:            return;
 246:        }
 247:
 248:        RepositoryException restoredException;
 249:	          try {
 250:            restoredException = (RepositoryException)constructor.newInstance(state);
 251:        } catch (InstantiationException e) {
 252:            return;
 253:        } catch (IllegalAccessException e) {
 254:            return;
 255:        } catch (InvocationTargetException e) {
 256:            return;
 257:        }
 258:
 259:	          if (causeXml.getCause() != null) {
 260:            Exception cause = restoreException(causeXml.getCause());
 261:            restoredException.initCause(cause);
 262:        }
 263:
 264:	          if (restoredException != null) {
 265:            throw restoredException;
 266:        }
 267:    }
 268:}