/*
* 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.service.props.impl;
import static java.lang.System.*;
import static och.util.Util.*;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import och.service.props.Props;
import och.service.props.net.GetUpdateReq;
import och.service.props.net.GetUpdateResp;
import och.util.model.CircularFifoBuffer;
import och.util.model.Pair;
import och.util.model.SecureKeyHolder;
import och.util.socket.json.server.JsonSocketServer;
import och.util.socket.json.server.ReqController;
import org.apache.commons.logging.Log;
public class NetPropsServer implements SecureKeyHolder {
private static final int historySize = 20;
private Log log = getLog(getClass());
private JsonSocketServer serverImpl;
private final Props props;
//model
private ReadWriteLock rw = new ReentrantReadWriteLock();
private Lock read = rw.readLock();
private Lock write = rw.writeLock();
private CircularFifoBuffer<Pair<Long, Set<String>>> updateHistory;
private long delta = currentTimeMillis();
public NetPropsServer(int port, int maxThreads, String secureKey, Props props) {
this.props = props;
serverImpl = new JsonSocketServer("NetPropsServer("+props+")", port, maxThreads);
serverImpl.setSecureKey(secureKey);
updateHistory = new CircularFifoBuffer<>(historySize);
props.addChangedListener((keys)->{
addToHist(keys);
});
}
public void runAsync() throws IOException {
init();
serverImpl.runAsync();
}
public void runWait() throws IOException {
init();
serverImpl.runWait();
}
private void init() throws IOException{
serverImpl.putController(GetUpdateReq.class, new ReqController<GetUpdateReq, GetUpdateResp>() {
@Override
public GetUpdateResp processReq(GetUpdateReq req, SocketAddress remoteAddress) throws Exception {
GetUpdateResp out = null;
if(req.delta < 1){
out = getFullPropsUpdateResp(getDelta());
log.info("send props to "+remoteAddress+": "+out.getAllKeys());
} else {
out = getUpdateResp(req.delta);
if(out != null) log.info("send updates to "+remoteAddress+": "+out.getAllKeys());
}
return out;
}
});
}
@Override
public void setSecureKey(String key){
serverImpl.setSecureKey(key);
}
@Override
public boolean isSecuredByKey(){
return serverImpl.isSecuredByKey();
}
public void shutdownAsync(){
serverImpl.shutdownAsync();
}
public void shutdownWait(){
serverImpl.shutdownWait();
}
private void addToHist(Set<String> keys) {
write.lock();
try {
long oldDelta = delta;
delta++;
updateHistory.add(new Pair<>(oldDelta, keys));
}finally {
write.unlock();
}
}
public long getDelta(){
read.lock();
try {
return delta;
}finally {
read.unlock();
}
}
public GetUpdateResp getUpdateResp(long clientDelta) {
long curDelta = 0;
boolean clientDeltaInHist = false;
Set<String> updatedKeys = null;
read.lock();
try {
curDelta = delta;
if(clientDelta == curDelta) return null;
for(Pair<Long, Set<String>> item : updateHistory){
if(clientDelta > item.first) continue;
if(clientDelta == item.first){
clientDeltaInHist = true;
updatedKeys = new HashSet<>();
updatedKeys.addAll(item.second);
continue;
}
//clientDelta < item.first
if( ! clientDeltaInHist) break;
updatedKeys.addAll(item.second);
}
}finally {
read.unlock();
}
if( ! clientDeltaInHist){
return getFullPropsUpdateResp(curDelta);
}
return getUpdateResp(curDelta, updatedKeys);
}
private GetUpdateResp getUpdateResp(long curDelta, Set<String> keys) {
Map<String, String> updated = new HashMap<>();
for (String key : keys) {
updated.put(key, props.getVal(key));
}
return new GetUpdateResp(false, updated, curDelta);
}
private GetUpdateResp getFullPropsUpdateResp(long curDelta) {
Map<String, String> updated = props.toMap();
return new GetUpdateResp(true, updated, curDelta);
}
}