/*
* ====================
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License("CDDL") (the "License"). You may not use this file
* except in compliance with the License.
*
* You can obtain a copy of the License at
* http://opensource.org/licenses/cddl1.php
* See the License for the specific language governing permissions and limitations
* under the License.
*
* When distributing the Covered Code, include this CDDL Header Notice in each file
* and include the License file at http://opensource.org/licenses/cddl1.php.
* If applicable, add the following below this CDDL Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
* ====================
* Portions Copyrighted 2010-2013 ForgeRock AS.
*/
package org.identityconnectors.framework.server.impl;
import java.io.EOFException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.identityconnectors.common.l10n.CurrentLocale;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.api.ConnectorFacade;
import org.identityconnectors.framework.api.ConnectorFacadeFactory;
import org.identityconnectors.framework.api.ConnectorInfo;
import org.identityconnectors.framework.api.ConnectorInfoManager;
import org.identityconnectors.framework.api.ConnectorInfoManagerFactory;
import org.identityconnectors.framework.api.ConnectorKey;
import org.identityconnectors.framework.api.operations.APIOperation;
import org.identityconnectors.framework.common.exceptions.ConnectorException;
import org.identityconnectors.framework.common.exceptions.InvalidCredentialException;
import org.identityconnectors.framework.common.serializer.SerializerUtil;
import org.identityconnectors.framework.impl.api.ConnectorInfoManagerFactoryImpl;
import org.identityconnectors.framework.impl.api.ObjectStreamHandler;
import org.identityconnectors.framework.impl.api.StreamHandlerUtil;
import org.identityconnectors.framework.impl.api.local.LocalConnectorInfoImpl;
import org.identityconnectors.framework.impl.api.remote.RemoteConnectorInfoImpl;
import org.identityconnectors.framework.impl.api.remote.RemoteFrameworkConnection;
import org.identityconnectors.framework.impl.api.remote.messages.EchoMessage;
import org.identityconnectors.framework.impl.api.remote.messages.HelloRequest;
import org.identityconnectors.framework.impl.api.remote.messages.HelloResponse;
import org.identityconnectors.framework.impl.api.remote.messages.OperationRequest;
import org.identityconnectors.framework.impl.api.remote.messages.OperationRequestMoreData;
import org.identityconnectors.framework.impl.api.remote.messages.OperationResponseEnd;
import org.identityconnectors.framework.impl.api.remote.messages.OperationResponsePart;
import org.identityconnectors.framework.impl.api.remote.messages.OperationResponsePause;
import org.identityconnectors.framework.server.ConnectorServer;
public class ConnectionProcessor implements Runnable {
private static final Log LOG = Log.getLog(ConnectionListener.class);
private static class RemoteResultsHandler implements ObjectStreamHandler {
private static final int PAUSE_INTERVAL = 200;
private final RemoteFrameworkConnection connection;
private long count = 0;
public RemoteResultsHandler(RemoteFrameworkConnection conn) {
connection = conn;
}
public boolean handle(Object obj) {
try {
OperationResponsePart part = new OperationResponsePart(null, obj);
connection.writeObject(part);
count++;
if (count % PAUSE_INTERVAL == 0) {
connection.writeObject(new OperationResponsePause());
Object message = connection.readObject();
return message instanceof OperationRequestMoreData;
} else {
return true;
}
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw new BrokenConnectionException((IOException) e.getCause());
} else {
throw e;
}
}
}
}
private final ConnectorServer connectorServer;
private final RemoteFrameworkConnection connection;
public ConnectionProcessor(ConnectorServer server, Socket socket) {
connectorServer = server;
connection = new RemoteFrameworkConnection(socket);
}
public void run() {
try {
try {
while (true) {
boolean keepGoing = processRequest();
if (!keepGoing) {
break;
}
}
} finally {
try {
connection.close();
} catch (Exception e) {
LOG.error(e, null);
}
}
} catch (Throwable e) {
LOG.error(e, null);
}
}
private boolean processRequest() throws Exception {
Locale locale;
try {
locale = (Locale) connection.readObject();
} catch (RuntimeException e) {
if (e.getCause() instanceof EOFException) {
return false;
}
throw e;
}
CurrentLocale.set(locale);
GuardedString key = (GuardedString) connection.readObject();
boolean authorized;
try {
authorized = key.verifyBase64SHA1Hash(connectorServer.getKeyHash());
} finally {
key.dispose();
}
InvalidCredentialException authException = null;
if (!authorized) {
authException = new InvalidCredentialException("Remote framework key is invalid");
}
Object requestObject = connection.readObject();
if (requestObject instanceof HelloRequest) {
if (authException != null) {
HelloResponse response = new HelloResponse(authException, null, null, null);
connection.writeObject(response);
} else {
HelloResponse response = processHelloRequest((HelloRequest) requestObject);
connection.writeObject(response);
}
} else if (requestObject instanceof OperationRequest) {
if (authException != null) {
OperationResponsePart part = new OperationResponsePart(authException, null);
connection.writeObject(part);
} else {
OperationRequest opRequest = (OperationRequest) requestObject;
OperationResponsePart part = processOperationRequest(opRequest);
connection.writeObject(part);
}
} else if (requestObject instanceof EchoMessage) {
if (authException != null) {
// echo message probably doesn't need auth, but
// it couldn't hurt - actually it does for test connection
EchoMessage part = new EchoMessage(authException, null);
connection.writeObject(part);
} else {
EchoMessage message = (EchoMessage) requestObject;
Object obj = message.getObject();
String xml = message.getXml();
if (xml != null) {
Object xmlClone = SerializerUtil.deserializeXmlObject(xml, true);
xml = SerializerUtil.serializeXmlObject(xmlClone, true);
}
EchoMessage message2 = new EchoMessage(obj, xml);
connection.writeObject(message2);
}
} else {
throw new ConnectorException("Unexpected request: " + requestObject);
}
return true;
}
private ConnectorInfoManager getConnectorInfoManager() {
ConnectorInfoManagerFactoryImpl factory =
(ConnectorInfoManagerFactoryImpl) ConnectorInfoManagerFactory.getInstance();
return factory.getLocalManager(connectorServer.getBundleURLs(), connectorServer
.getBundleParentClassLoader());
}
private HelloResponse processHelloRequest(HelloRequest request) {
List<RemoteConnectorInfoImpl> connectorInfo = null;
List<ConnectorKey> connectorKeys = null;
Map<String, Object> serverInfo = null;
Exception exception = null;
try {
serverInfo = new HashMap<String, Object>(1);
if (request.isServerInfo()) {
serverInfo.put(HelloResponse.SERVER_START_TIME, connectorServer.getStartTime());
}
if (request.isConnectorKeys()) {
ConnectorInfoManager manager = getConnectorInfoManager();
List<ConnectorInfo> localInfos = manager.getConnectorInfos();
connectorKeys = new ArrayList<ConnectorKey>();
for (ConnectorInfo localInfo : localInfos) {
connectorKeys.add(localInfo.getConnectorKey());
}
if (request.isConnectorInfo()) {
connectorInfo = new ArrayList<RemoteConnectorInfoImpl>();
for (ConnectorInfo localInfo : localInfos) {
LocalConnectorInfoImpl localInfoImpl = (LocalConnectorInfoImpl) localInfo;
RemoteConnectorInfoImpl remoteInfo = localInfoImpl.toRemote();
connectorInfo.add(remoteInfo);
}
}
}
} catch (Exception e) {
exception = e;
connectorInfo = null;
}
return new HelloResponse(exception, serverInfo, connectorKeys, connectorInfo);
}
private Method getOperationMethod(OperationRequest request) {
Method[] methods = request.getOperation().getDeclaredMethods();
Method found = null;
for (Method m : methods) {
if (m.getName().equalsIgnoreCase(request.getOperationMethodName())) {
if (found != null) {
throw new ConnectorException("APIOperations are expected "
+ "to have exactly one method of a given name: "
+ request.getOperation());
}
found = m;
}
}
if (found == null) {
throw new ConnectorException("APIOperations are expected "
+ "to have exactly one method of a given name: " + request.getOperation());
}
return found;
}
private OperationResponsePart processOperationRequest(OperationRequest request)
throws IOException {
Object result;
Throwable exception = null;
try {
Method method = getOperationMethod(request);
APIOperation operation = getAPIOperation(request);
List<Object> arguments = request.getArguments();
List<Object> argumentsAndStreamHandlers =
populateStreamHandlers(method.getParameterTypes(), arguments);
try {
result = method.invoke(operation, argumentsAndStreamHandlers.toArray());
} catch (InvocationTargetException e) {
throw e.getCause();
}
boolean anyStreams = argumentsAndStreamHandlers.size() > arguments.size();
if (anyStreams) {
try {
connection.writeObject(new OperationResponseEnd());
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw new BrokenConnectionException((IOException) e.getCause());
} else {
throw e;
}
}
}
} catch (BrokenConnectionException w) {
// at this point the stream is broken - just give up
throw w.getIOException();
} catch (Throwable e) {
LOG.error(e, null);
exception = e;
result = null;
}
return new OperationResponsePart(exception, result);
}
private List<Object> populateStreamHandlers(Class<?>[] paramTypes, List<Object> arguments) {
List<Object> rv = new ArrayList<Object>();
boolean firstStream = true;
Iterator<Object> argIt = arguments.iterator();
for (Class<?> paramType : paramTypes) {
if (StreamHandlerUtil.isAdaptableToObjectStreamHandler(paramType)) {
if (!firstStream) {
throw new UnsupportedOperationException(
"At most one stream handler is supported");
}
ObjectStreamHandler osh = new RemoteResultsHandler(connection);
rv.add(StreamHandlerUtil.adaptFromObjectStreamHandler(paramType, osh));
firstStream = false;
} else {
rv.add(argIt.next());
}
}
return rv;
}
private APIOperation getAPIOperation(OperationRequest request) throws Exception {
ConnectorInfoManager manager = getConnectorInfoManager();
ConnectorInfo info = manager.findConnectorInfo(request.getConnectorKey());
if (info == null) {
throw new ConnectorException("No such connector: " + request.getConnectorKey() + " ");
}
String connectorFacadeKey = request.getConnectorFacadeKey();
ConnectorFacade facade =
ConnectorFacadeFactory.getManagedInstance().newInstance(info, connectorFacadeKey);
return facade.getOperation(request.getOperation());
}
private static class BrokenConnectionException extends ConnectorException {
static final long serialVersionUID = 0L;
public BrokenConnectionException(IOException ex) {
super(ex);
}
public IOException getIOException() {
return (IOException) getCause();
}
}
}