/* 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.ServerResponseProjectControl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Abstract transport proxy. Abstract class that supplies default implementations for most of the behavior required by the transport proxy interface.
* @author Jim Voris
*/
public abstract class AbstractTransportProxy implements TransportProxyInterface {
// Create our logger object
private static final Logger LOGGER = Logger.getLogger("com.qumasoft.qvcslib");
private final ServerProperties serverProperties;
private final Object readLock;
private final Map<String, ArchiveDirManagerInterface> listeners = Collections.synchronizedMap(new TreeMap<String, ArchiveDirManagerInterface>());
private boolean isLoggedInToServerFlag = false;
private String username = null; // The name the user is logged in as.
private ObjectOutputStream objectRequestStream = null;
private ObjectInputStream objectResponseStream = null;
private final Compressor decompressor;
private final Compressor compressor;
private TransportProxyListenerInterface proxyListener = null;
private boolean isOpenFlag = false;
private final Object requestStreamSyncObject = new Object();
private final Object responseStreamSyncObject = new Object();
private VisualCompareInterface visualCompareInterface = null;
private HeartbeatThread heartbeatThread = null;
/**
* Construct the common parts of a transport.
* @param serverPropertiesArg the server properties.
* @param proxyListenerArg the proxy listener.
* @param visualCompareInterfaceArg the visual compare interface.
*/
public AbstractTransportProxy(ServerProperties serverPropertiesArg, TransportProxyListenerInterface proxyListenerArg, VisualCompareInterface visualCompareInterfaceArg) {
this.readLock = new Object();
serverProperties = serverPropertiesArg;
proxyListener = proxyListenerArg;
compressor = new ZlibCompressor();
decompressor = new ZlibCompressor();
visualCompareInterface = visualCompareInterfaceArg;
}
@Override
public ServerProperties getServerProperties() {
return serverProperties;
}
/**
* Get the server name.
* @return the server name.
*/
public String getServerName() {
return serverProperties.getServerName();
}
@Override
public Object getReadLock() {
return readLock;
}
@Override
public VisualCompareInterface getVisualCompareInterface() {
return this.visualCompareInterface;
}
@Override
public void setHeartBeatThread(HeartbeatThread thread) {
this.heartbeatThread = thread;
}
@Override
public HeartbeatThread getHeartBeatThread() {
return this.heartbeatThread;
}
private String buildKeyValue(ArchiveDirManagerInterface listener) {
return buildKeyValue(listener.getProjectName(), listener.getViewName(), listener.getAppendedPath());
}
private String buildKeyValue(final String projectName, final String viewName, final String appendedPath) {
String standardAppendedPath = Utility.convertToStandardPath(appendedPath);
return projectName + ":" + viewName + ":" + standardAppendedPath;
}
@Override
public void addReadListener(ArchiveDirManagerInterface listener) {
String keyValue = buildKeyValue(listener);
listeners.put(keyValue, listener);
}
@Override
public void removeReadListener(ArchiveDirManagerInterface listener) {
String keyValue = buildKeyValue(listener);
listeners.remove(keyValue);
}
@Override
public void removeAllListeners() {
TransportProxyFactory proxyFactory = TransportProxyFactory.getInstance();
String projectName;
String viewName;
for (ArchiveDirManagerInterface directoryManager : listeners.values()) {
LOGGER.log(Level.FINE, "Removing directory manager for: " + directoryManager.getProjectName() + ":" + directoryManager.getAppendedPath());
String projectType = QVCSConstants.QVCS_REMOTE_PROJECT_TYPE;
DirectoryManagerFactory.getInstance().removeDirectoryManager(getServerName(), directoryManager.getProjectName(), directoryManager.getViewName(),
projectType, directoryManager.getAppendedPath());
projectName = directoryManager.getProjectName();
viewName = directoryManager.getViewName();
// We're looking at the project node.
String appendedPath = directoryManager.getAppendedPath();
if (appendedPath.length() == 0) {
ServerResponseProjectControl projectControl = new ServerResponseProjectControl();
projectControl.setServerName(getServerName());
projectControl.setProjectName(projectName);
projectControl.setViewName(viewName);
projectControl.setRemoveFlag(true);
proxyFactory.notifyListeners(projectControl);
}
}
// No more listeners here.
listeners.clear();
}
@Override
public ArchiveDirManagerInterface getDirectoryManager(final String projectName, final String viewName, final String appendedPath) {
return listeners.get(buildKeyValue(projectName, viewName, appendedPath));
}
@Override
public void setIsLoggedInToServer(boolean flag) {
isLoggedInToServerFlag = flag;
}
@Override
public boolean getIsLoggedInToServer() {
return isLoggedInToServerFlag;
}
@Override
public String getUsername() {
return username;
}
@Override
public void setUsername(String user) {
this.username = user;
}
@Override
public boolean getIsOpen() {
return isOpenFlag;
}
protected void setIsOpen(boolean flag) {
isOpenFlag = flag;
}
@Override
public Object read() {
Object retVal = null;
boolean closeFlag = false;
synchronized (responseStreamSyncObject) {
if (objectResponseStream != null) {
try {
retVal = objectResponseStream.readObject();
if (retVal instanceof byte[]) {
// We'll need to de-compress this.
byte[] compressedInput = (byte[]) retVal;
retVal = decompress(compressedInput);
}
} catch (java.io.EOFException e) {
// Server has shut down...
LOGGER.log(Level.INFO, "*=*=*=*=*=*= Client thinks server has shut down.");
retVal = null;
closeFlag = true;
} catch (java.net.SocketException e) {
// Server has died...
LOGGER.log(Level.INFO, "#=#=#=#=#=#= Client thinks server has died.");
retVal = null;
closeFlag = true;
} catch (java.io.IOException | ClassNotFoundException e) {
// Server has died...
LOGGER.log(Level.WARNING, "#=#=#=#=#=#=#= Something died.");
retVal = null;
closeFlag = true;
}
}
}
if (closeFlag) {
close();
}
return retVal;
}
@Override
public void write(Object object) {
boolean closeFlag = false;
synchronized (requestStreamSyncObject) {
if (objectRequestStream != null) {
try {
Object retVal = compress(object);
objectRequestStream.writeObject(retVal);
objectRequestStream.flush();
// To avoid memory leaks.
objectRequestStream.reset();
} catch (IOException e) {
closeFlag = true;
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
}
}
}
if (closeFlag) {
close();
}
}
@Override
public abstract boolean open(int port);
@Override
public abstract void close();
private Object compress(Object object) {
Object retVal = object;
byte[] inputBuffer;
try {
try (ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOutputStream)) {
objectOutputStream.writeObject(object);
objectOutputStream.flush();
inputBuffer = byteOutputStream.toByteArray();
}
if (compressor.compress(inputBuffer)) {
retVal = compressor.getCompressedBuffer();
LOGGER.log(Level.WARNING, "* * * * * * * * * Compressed * * * * * * * * * * * *" + object.getClass().toString() + " from " + inputBuffer.length + " to "
+ compressor.getCompressedBuffer().length);
}
} catch (java.lang.OutOfMemoryError e) {
// 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 object for transport layer.");
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Caught exception: " + e.getClass().toString() + " " + e.getLocalizedMessage());
}
return retVal;
}
private Object decompress(byte[] compressedInput) {
Object retVal = null;
byte[] expandedBuffer = decompressor.expand(compressedInput);
ByteArrayInputStream byteInputStream;
ObjectInputStream objectInputStream;
try {
byteInputStream = new ByteArrayInputStream(expandedBuffer);
objectInputStream = new ObjectInputStream(byteInputStream);
retVal = objectInputStream.readObject();
objectInputStream.close();
byteInputStream.close();
} catch (IOException | ClassNotFoundException e) {
LOGGER.log(Level.WARNING, "Caught exception trying to decompress an object: " + e.getClass().toString() + " " + e.getLocalizedMessage());
}
return retVal;
}
@Override
public TransportProxyListenerInterface getProxyListener() {
return proxyListener;
}
/**
* Get the object request stream.
* @return the object request stream.
*/
public ObjectOutputStream getObjectRequestStream() {
return objectRequestStream;
}
/**
* Set the object request stream.
* @param requestStream the object request stream.
*/
public void setObjectRequestStream(ObjectOutputStream requestStream) {
this.objectRequestStream = requestStream;
}
/**
* Get the object response stream.
* @return the object response stream.
*/
public ObjectInputStream getObjectResponseStream() {
return objectResponseStream;
}
/**
* Set the object response stream.
* @param responseStream the object response stream.
*/
public void setObjectResponseStream(ObjectInputStream responseStream) {
this.objectResponseStream = responseStream;
}
}