/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florent Guillaume
*/
package org.eclipse.ecr.core.storage.sql.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.core.storage.sql.InvalidationsQueue;
import org.eclipse.ecr.core.storage.sql.Mapper;
import org.eclipse.ecr.core.storage.sql.Repository;
import org.eclipse.ecr.core.storage.sql.RepositoryResolver;
import org.eclipse.ecr.core.storage.sql.Mapper.Identification;
/**
* Servlet receiving remote {@link MapperClient} requests and sending them to an
* actual mapper.
*/
public class MapperServlet extends HttpServlet {
public static final String SERVER_THREAD_NAME_PREFIX = "Nuxeo-VCS-Server-";
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(MapperServlet.class);
public static final String PARAM_RID = "rid";
public static final String PARAM_MID = "mid";
private final AtomicLong repositoryCounter = new AtomicLong(0);
private final String repositoryName;
private Repository repository;
/** Event invalidations to return to each repository. */
private final Map<String, InvalidationsQueue> eventQueues;
public MapperServlet(String repositoryName) {
this.repositoryName = repositoryName;
eventQueues = Collections.synchronizedMap(new HashMap<String, InvalidationsQueue>());
}
public static String getName(String repositoryName) {
return MapperServlet.class.getSimpleName() + '-' + repositoryName;
}
private boolean initialized;
// currently connected sessions
// TODO GC after timeout
private Map<String, MapperInvoker> invokers;
protected synchronized void initialize() {
if (initialized) {
return;
}
initialized = true;
repository = RepositoryResolver.getRepository(repositoryName);
invokers = Collections.synchronizedMap(new HashMap<String, MapperInvoker>());
}
@Override
public void destroy() {
if (invokers != null) {
for (Entry<String, MapperInvoker> es : invokers.entrySet()) {
MapperInvoker invoker = es.getValue();
try {
invoker.call(Mapper.CLOSE);
invoker.close();
} catch (Throwable e) {
log.error("Cannot close invoker " + es.getKey());
}
}
}
super.destroy();
}
private final AtomicInteger threadNumber = new AtomicInteger();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
initialize();
String rid = req.getParameter(PARAM_RID);
if (rid == null || "".equals(rid)) {
// assign a new repositoryId. client calls this from a synchronized
// method
rid = "" + repositoryCounter.incrementAndGet();
}
String mid = req.getParameter(PARAM_MID);
if ("".equals(mid)) {
mid = null;
}
InputStream is = req.getInputStream();
try {
// invoker
MapperInvoker invoker;
if (mid == null) {
// new session
String name = SERVER_THREAD_NAME_PREFIX
+ threadNumber.incrementAndGet();
InvalidationsQueue eventQueue = eventQueues.get(rid);
if (eventQueue == null) {
eventQueues.put(rid, eventQueue = new InvalidationsQueue(
"servlet-for-" + rid));
}
String remoteIP = req.getRemoteAddr();
String remotePrincipal = req.getHeader("X-Nuxeo-Principal");
invoker = new MapperInvoker(repository, name, eventQueue, new MapperClientInfo(remoteIP, remotePrincipal));
Identification id = (Identification) invoker.call(Mapper.GET_IDENTIFICATION);
mid = id.mapperId;
invokers.put(mid, invoker);
} else {
// existing session
invoker = invokers.get(mid);
if (invoker == null) {
throw new RuntimeException(
"Unknown session id (maybe timed out): " + mid);
}
}
invoker.clientInfo.handledRequest(req);
// set up output stream
resp.setContentType("application/octet-stream");
// resp.setCharacterEncoding("ISO-8859-1"); // important
Writer writer = resp.getWriter();
ObjectOutputStream oos = new ObjectOutputStream(
new OutputStreamToWriter(writer));
// read method and args
ObjectInputStream ois = new ObjectInputStream(is);
String methodName = (String) ois.readObject();
List<Object> args = new LinkedList<Object>();
while (true) {
Object object = ois.readObject();
if (object == MapperClient.EOF) {
break;
}
args.add(object);
}
// invoke method
Object res = invoker.call(methodName, args.toArray());
// close?
if (Mapper.CLOSE.equals(methodName)) {
// close session
invoker.close();
invokers.remove(mid);
}
// getIdentification
else if (Mapper.GET_IDENTIFICATION.equals(methodName)) {
// add repositoryId to identification
Identification id = (Identification) res;
res = new Identification(rid, id.mapperId);
}
// write result
oos.writeObject(res);
oos.flush();
oos.close();
} catch (Throwable e) {
log.error(e, e);
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
e.toString());
}
}
public Collection<MapperClientInfo> getClientInfos() {
if (invokers == null) {
return Collections.emptyList();
}
List<MapperClientInfo> infos = new ArrayList<MapperClientInfo>(invokers.size());
for (MapperInvoker invoker : invokers.values()) {
infos.add(invoker.clientInfo);
}
return infos;
}
}