/* Copyright 2004-2014 Jim Voris
*
* 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 com.qumasoft.qvcslib;
import com.qumasoft.qvcslib.response.ServerResponseError;
import com.qumasoft.qvcslib.response.ServerResponseLogin;
import com.qumasoft.qvcslib.response.ServerResponseTransactionBegin;
import com.qumasoft.qvcslib.response.ServerResponseTransactionEnd;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Server response factory.
* @author Jim Voris
*/
public class ServerResponseFactory implements ServerResponseFactoryInterface {
// Create our logger object
private static final Logger LOGGER = Logger.getLogger("com.qumasoft.qvcslib");
private java.io.ObjectOutputStream objectOutputStream = null;
private java.io.OutputStream outputStream = null;
private final Object outputStreamSyncObject = new Object();
private final Set<ArchiveDirManagerInterface> directoryManagers = new HashSet<>();
private String userName = null;
private String serverName = null;
private boolean isUserLoggedInFlag = false;
private int clientPort = -1;
private String clientIPAddress = null;
private boolean connectionAliveFlag = false;
private HeartBeatTimerTask heartBeatTimerTask = null;
private static final long HEART_BEAT_COUNT_BEFORE_DECLARING_FAILURE = 8;
/** This can be static because there is only one server. */
private static boolean shutdownInProgressFlag = false;
/**
* Creates new ServerResponseFactory.
* @param oStream the output stream. Typically, this will be the output stream of a Socket.
* @param cPort the client port associated with the socket.
* @param cIPAddress the client IP address associated with the socket.
*/
public ServerResponseFactory(java.io.OutputStream oStream, final int cPort, final String cIPAddress) {
try {
outputStream = oStream;
objectOutputStream = new ObjectOutputStream(oStream);
clientPort = cPort;
clientIPAddress = cIPAddress;
connectionAliveFlag = true;
initKeepAliveTimer();
} catch (IOException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
objectOutputStream = null;
}
}
/**
* Set the shutdown in progress flag.
* @param flag the shutdown in progress flag.
*/
public static void setShutdownInProgress(boolean flag) {
shutdownInProgressFlag = flag;
}
/**
* Get the shutdown in progress flag.
* @return the shutdown in progress flag.
*/
public static boolean getShutdownInProgress() {
return shutdownInProgressFlag;
}
/**
* {@inheritDoc}
*/
@Override
public void createServerResponse(java.io.Serializable responseObject) {
if (null != responseObject) {
try {
MutableByteArray responseArray = new MutableByteArray();
// Make sure the user is logged in before we actually share any
// info with them.
if (!getIsUserLoggedIn()) {
if (responseObject instanceof ServerResponseLogin) {
ServerResponseLogin response = (ServerResponseLogin) responseObject;
LOGGER.log(Level.WARNING, "User [" + response.getUserName() + "] failed to login.");
} else if (responseObject instanceof ServerResponseTransactionBegin) {
LOGGER.log(Level.FINE, "Sending transaction begin without being logged in.");
} else if (responseObject instanceof ServerResponseTransactionEnd) {
LOGGER.log(Level.FINE, "Sending transaction end without being logged in.");
} else {
ServerResponseError error = new ServerResponseError("Not logged in!!", null, null, null);
responseObject = error;
}
}
synchronized (outputStreamSyncObject) {
// Cancel the heartbeat timer so we won't kill the connection for really long/big responses.
heartBeatTimerTask.cancel();
// Compress the response
if (compress(responseObject, responseArray)) {
// Things compressed... send the compressed result.
objectOutputStream.writeObject(responseArray.getValue());
} else {
// Things would not compress... just send the original object.
objectOutputStream.writeObject(responseObject);
}
objectOutputStream.flush();
objectOutputStream.reset();
}
clientIsAlive();
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Caught exception on ServerResponseFactory.createServerResponse: " + e.getClass().toString() + ": " + e.getLocalizedMessage());
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
try {
outputStream.close();
} catch (IOException ioe) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(ioe));
} finally {
connectionAliveFlag = false;
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void addArchiveDirManager(ArchiveDirManagerInterface archiveDirManager) {
directoryManagers.add(archiveDirManager);
}
/**
* Get the directory managers associated with this client connection.
* @return the directory managers associated with this client connection.
*/
public Set<ArchiveDirManagerInterface> getDirectoryManagers() {
return directoryManagers;
}
private boolean compress(java.io.Serializable responseObject, MutableByteArray compressedArray) {
boolean retVal;
try {
Compressor compressor = new ZlibCompressor();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream compressedObjectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
compressedObjectOutputStream.writeObject(responseObject);
compressedObjectOutputStream.flush();
compressedObjectOutputStream.close();
byteArrayOutputStream.close();
byte[] inputByteArray = byteArrayOutputStream.toByteArray();
retVal = compressor.compress(inputByteArray);
if (retVal) {
compressedArray.setValue(compressor.getCompressedBuffer());
LOGGER.log(Level.FINEST, "Compressed server response for " + responseObject.getClass().toString() + " from: " + inputByteArray.length + " to: "
+ compressedArray.getValue().length);
}
} catch (java.lang.OutOfMemoryError e) {
retVal = false;
// If they are trying to create an archive for a really big file,
// we might have problems.
LOGGER.log(Level.WARNING, "Out of memory trying to compress response object");
} catch (IOException e) {
retVal = false;
}
return retVal;
}
/**
* Is the user logged in.
* @return true if the user is logged in; false if not logged in.
*/
public boolean getIsUserLoggedIn() {
return isUserLoggedInFlag;
}
/**
* Set the logged in flag.
* @param flag the logged in flag.
*/
public void setIsUserLoggedIn(boolean flag) {
isUserLoggedInFlag = flag;
}
/**
* {@inheritDoc}
*/
@Override
public String getUserName() {
return userName;
}
/**
* Set the user name.
* @param name the user name.
*/
public void setUserName(String name) {
userName = name;
}
/**
* {@inheritDoc}
*/
@Override
public String getServerName() {
return serverName;
}
/**
* Set the server name.
* @param server the server name.
*/
public void setServerName(String server) {
serverName = server;
}
/**
* {@inheritDoc}
*/
@Override
public int getClientPort() {
return clientPort;
}
/**
* {@inheritDoc}
*/
@Override
public String getClientIPAddress() {
return clientIPAddress;
}
private void initKeepAliveTimer() {
heartBeatTimerTask = new HeartBeatTimerTask();
TimerManager.getInstance().getTimer().schedule(heartBeatTimerTask, HEART_BEAT_COUNT_BEFORE_DECLARING_FAILURE * QVCSConstants.HEART_BEAT_SLEEP_TIME);
}
/**
* {@inheritDoc}
*/
@Override
public void clientIsAlive() {
heartBeatTimerTask.cancel();
heartBeatTimerTask = new HeartBeatTimerTask();
TimerManager.getInstance().getTimer().schedule(heartBeatTimerTask, HEART_BEAT_COUNT_BEFORE_DECLARING_FAILURE * QVCSConstants.HEART_BEAT_SLEEP_TIME);
}
/**
* {@inheritDoc}
*/
@Override
public boolean getConnectionAliveFlag() {
return connectionAliveFlag;
}
private void setConnectionAliveFlag(boolean flag) {
connectionAliveFlag = flag;
}
class HeartBeatTimerTask extends TimerTask {
@Override
public void run() {
setConnectionAliveFlag(false);
try {
outputStream.close();
} catch (IOException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
}
}
}
}