/*
* Copyright 2015 Evgeny Dolganov (evgenij.dolganov@gmail.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package och.util.socket.json.server;
import static och.util.StringUtil.*;
import static och.util.Util.*;
import static och.util.json.GsonUtil.*;
import static och.util.model.SecureProvider.*;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import och.util.model.SecureProvider;
import och.util.socket.SocketUtil;
import och.util.socket.json.exception.InvalidReqException;
import och.util.socket.json.exception.ValidationException;
import och.util.socket.server.SocketHandler;
import och.util.socket.server.SocketServer;
import org.apache.commons.logging.Log;
public class JsonProtocolSocketHandler implements SocketHandler {
public static final String PROTOCOL_PREFIX = "sckt_jsn:";
public static final String OK = "OK:";
public static final String VALIDATION_ERROR = "VALIDATION_ERROR:";
public static final String UNEXPECTED_ERROR = "UNEXPECTED_ERROR:";
private Log log = getLog(getClass());
private Map<Class<?>, ReqController<? super Object, ?>> controllers;
private SecureProvider secureProvider;
public JsonProtocolSocketHandler(SecureProvider secureProvider) {
this(secureProvider, false);
}
public JsonProtocolSocketHandler(SecureProvider secureProvider, boolean threadSafe) {
this.secureProvider = secureProvider == null? DUMMY_IMPL : secureProvider;
if(!threadSafe) controllers = new HashMap<>();
else controllers = new ConcurrentHashMap<>();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T> void put(Class<T> type, ReqController<T, ?> controller){
controllers.put((Class)type, (ReqController)controller);
}
public void remove(Class<?> type){
controllers.remove(type);
}
@Override
public void process(Socket openedSocket, InputStream socketIn, OutputStream socketOut, SocketServer owner) throws Throwable {
SocketAddress remoteAddress = openedSocket.getRemoteSocketAddress();
BufferedReader reader = SocketUtil.getReaderUTF8(socketIn);
BufferedOutputStream os = new BufferedOutputStream(socketOut);
while( ! owner.wasShutdown()){
String line = reader.readLine();
if(line == null || owner.wasShutdown()) break;
//process req
try {
try {
line = secureProvider.decode(line);
}catch(Throwable t){
throw new InvalidReqException("decode exception: "+t.getMessage());
}
if( ! line.startsWith(PROTOCOL_PREFIX)) throw new InvalidReqException("unknown protocol: "+line);
String data = line.substring(PROTOCOL_PREFIX.length());
int typeSepIndex = data.indexOf(':');
if(typeSepIndex < 1) throw new InvalidReqException("no data type in req: "+line);
String typeStr = data.substring(0, typeSepIndex);
Class<?> type = null;
try {
type = Class.forName(typeStr);
}catch (Exception e) {
throw new InvalidReqException("not java class: "+typeStr);
}
ReqController<? super Object, ?> controller = controllers.get(type);
if(controller == null) throw new InvalidReqException("no controller for type: "+type);
Object req = null;
try {
String jsonReq = data.substring(typeSepIndex+1);
if(hasText(jsonReq)) req = defaultGson.fromJson(jsonReq, type);
}catch (Exception e) {
throw new InvalidReqException("invalid json data in req: "+data);
}
//process
Object resp = controller.processReq(req, remoteAddress);
//send resp
String jsonResp = resp == null? "" : defaultGson.toJson(resp);
writeLine(os, OK + controller.respType + ":" + jsonResp);
os.flush();
}catch (Exception e) {
//connection closed
if(e instanceof SocketException) throw e;
//validation error
else if(e instanceof ValidationException) sendAndFlush(os, validationError((ValidationException) e));
//unexpected error
else {
log.error("unexpected exception: ", e);
sendAndFlush(os, UNEXPECTED_ERROR+e);
}
}
}
}
private void sendAndFlush(OutputStream os, Object obj) throws IOException{
String resp = String.valueOf(obj);
writeLine(os, resp);
os.flush();
}
private void writeLine(OutputStream os, String resp) throws IOException {
//to normal
if(resp.endsWith("\n")) resp = resp.substring(0, resp.length()-1);
//secure
resp = secureProvider.encode(resp);
if(resp.indexOf('\n') > 0) throw new IOException("secureProvider encoded msg with '\n' char");
resp += "\n";
//write
os.write(getBytesUTF8(resp));
}
private static String validationError(ValidationException e) throws IOException {
return VALIDATION_ERROR+e.getMessage();
}
}