/* 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.commandargs.CreateArchiveCommandArgs; import com.qumasoft.qvcslib.requestdata.ClientRequestAddDirectoryData; import com.qumasoft.qvcslib.requestdata.ClientRequestCreateArchiveData; import com.qumasoft.qvcslib.requestdata.ClientRequestRegisterClientListenerData; import com.qumasoft.qvcslib.requestdata.ClientRequestRenameData; import com.qumasoft.qvcslib.response.ServerResponseMessage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Date; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.event.ChangeListener; /** * Archive directory manager proxy. This is the client-side proxy for the archive directory manager. * * @author Jim Voris */ public final class ArchiveDirManagerProxy extends ArchiveDirManagerBase { /** * Create our logger object */ private static final Logger LOGGER = Logger.getLogger("com.qumasoft.qvcslib"); /** * An object we use for synchronization for those cases where we cannot synchronize on the LogfileProxy object */ private final Object synchObject = new Object(); /** * An object we use to sync the transport */ private final Object tranportProxySyncObject = new Object(); /** * These are the server properties that this object is the proxy for. */ private ServerProperties serverProperties = null; /** * This is the transport proxy we'll use to send/receive objects to the remote server. */ private TransportProxyInterface transportProxy = null; private boolean initCompleteFlag = false; private int directoryID = -1; private final Object initSyncObject = new Object(); private Date mostRecentCheckInDate = new Date(0L); /** * Creates a new instance of ArchiveDirManagerProxy. * * @param serverName the name of the server. * @param projectProperties the properties for this project. * @param projectName the name of the project. * @param viewName the name of the view. * @param userName the user's QVCS user name. * @param projectPassword the user's QVCS password. * @param appendedPath the appended path for this directory. */ public ArchiveDirManagerProxy(String serverName, AbstractProjectProperties projectProperties, String projectName, String viewName, String userName, String projectPassword, String appendedPath) { super(projectProperties, viewName, appendedPath, userName); serverProperties = new ServerProperties(serverName); transportProxy = TransportProxyFactory.getInstance().getTransportProxy(serverProperties); } /** * Creates a new instance of ArchiveDirManagerProxy. This constructor is used by the ClientAPI. * * @param projectProperties the properties for this project. * @param servProperties server properties. * @param viewName the name of the view. * @param userName the user's QVCS user name. * @param appendedPath the appended path for this directory. */ public ArchiveDirManagerProxy(AbstractProjectProperties projectProperties, ServerProperties servProperties, String viewName, String userName, String appendedPath) { super(projectProperties, viewName, appendedPath, userName); serverProperties = servProperties; transportProxy = TransportProxyFactory.getInstance().getTransportProxy(servProperties); } /** * Start this instance. Sort of a two step construction. We cannot start the listener threads within the ctor, so we use this method to get things going. */ @Override public void startDirectoryManager() { // So we can be informed when things happen to the transport. transportProxy.addReadListener(this); // Register as a listener for this archive directory. ClientRequestRegisterClientListenerData clientListener = new ClientRequestRegisterClientListenerData(); clientListener.setProjectName(getProjectName()); clientListener.setAppendedPath(getAppendedPath()); clientListener.setViewName(getViewName()); synchronized (tranportProxySyncObject) { int transactionID = ClientTransactionManager.getInstance().sendBeginTransaction(transportProxy); transportProxy.write(clientListener); ClientTransactionManager.getInstance().sendEndTransaction(transportProxy, transactionID); } } /** * Get the transport proxy we use to communicate with the server. * @return the transport proxy we use to communicate with the server. */ public TransportProxyInterface getTransportProxy() { return transportProxy; } @Override public boolean createDirectory() { boolean retVal = false; try { ClientRequestAddDirectoryData addDirectoryData = new ClientRequestAddDirectoryData(); addDirectoryData.setAppendedPath(getAppendedPath()); addDirectoryData.setProjectName(getProjectName()); addDirectoryData.setViewName(getViewName()); synchronized (tranportProxySyncObject) { transportProxy.write(addDirectoryData); retVal = true; } } catch (Exception e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } return retVal; } @Override public boolean createArchive(CreateArchiveCommandArgs commandLineArgs, String fullWorkfilename, ServerResponseFactoryInterface response) throws IOException, QVCSException { boolean retVal = false; int length = 0; FileInputStream fileInputStream = null; ClientRequestCreateArchiveData clientRequest = new ClientRequestCreateArchiveData(); clientRequest.setProjectName(getProjectName()); clientRequest.setViewName(getViewName()); clientRequest.setAppendedPath(getAppendedPath()); clientRequest.setCommandArgs(commandLineArgs); try { File createFile = new File(fullWorkfilename); // Need to read the resulting file into a buffer that we can send to the client. fileInputStream = new FileInputStream(createFile); length = (int) createFile.length(); byte[] buffer = new byte[length]; Utility.readDataFromStream(buffer, fileInputStream); clientRequest.setBuffer(buffer); // Save the contracted workfile buffer so when we get the response we can expand keywords // without having to 'get' the workfile. int cacheIndex = KeywordContractedWorkfileCache.getInstance().addContractedBuffer(getProjectName(), getAppendedPath(), Utility.convertWorkfileNameToShortWorkfileName(fullWorkfilename), buffer); clientRequest.setIndex(cacheIndex); synchronized (tranportProxySyncObject) { transportProxy.write(clientRequest); retVal = true; } } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } 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 create archive for: " + fullWorkfilename + ". File size was: " + length); } finally { if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } } return retVal; } /** * Update the archive information for the given workfile. * * @param shortWorkfileName the short workfile name. * @param skinnyLogfileInfo the skinny logfile information. */ public void updateArchiveInfo(String shortWorkfileName, SkinnyLogfileInfo skinnyLogfileInfo) { synchronized (getArchiveInfoCollection()) { if (getProjectProperties().getIgnoreCaseFlag()) { shortWorkfileName = shortWorkfileName.toLowerCase(); } LogFileProxy existingLogFileProxy = (LogFileProxy) getArchiveInfoCollection().get(shortWorkfileName); if (skinnyLogfileInfo != null) { updateMostRecentActivityDate(skinnyLogfileInfo.getLastCheckInDate()); if (existingLogFileProxy != null) { existingLogFileProxy.setSkinnyLogfileInfo(skinnyLogfileInfo); } else { LogFileProxy logFileProxy = new LogFileProxy(skinnyLogfileInfo, this); getArchiveInfoCollection().put(shortWorkfileName, logFileProxy); } } else { // remove this entry from the container. The server's copy // of the archive is gone, or is obsolete. getArchiveInfoCollection().remove(shortWorkfileName); } } } /** * Remove the archive info from our collection. This does NOT delete the file. It merely removes the file from our collection. * @param shortWorkfileName the short workfile name. */ public void removeArchiveInfo(String shortWorkfileName) { // It doesn't matter whether this is already here or not... we are // going to remove it from the container. synchronized (getArchiveInfoCollection()) { if (getProjectProperties().getIgnoreCaseFlag()) { shortWorkfileName = shortWorkfileName.toLowerCase(); } // Remove this entry from the container. The server's copy // of the archive is gone, or is obsolete. getArchiveInfoCollection().remove(shortWorkfileName); } } /** * We received a String message from the server. Notify our listeners. * @param messageString the message. */ public void updateInfo(String messageString) { LOGGER.log(Level.INFO, messageString); notifyListeners(); } /** * We received a message from the server that might be useful. Notify any of our change listeners. * @param message the message from the server. */ public void updateInfo(ServerResponseMessage message) { synchronized (getChangeListenerArray()) { Object[] listeners = getChangeListenerArray().getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { javax.swing.event.ChangeEvent event = new javax.swing.event.ChangeEvent(message); ((ChangeListener) listeners[i + 1]).stateChanged(event); } } } /** * Get the server properties. * @return the server properties. */ public ServerProperties getServerProperties() { return serverProperties; } /** * Indicate that initialization is complete; notify any other threads that may be waiting. */ public void setInitComplete() { synchronized (initSyncObject) { initCompleteFlag = true; initSyncObject.notifyAll(); } } /** * Wait for initialization to complete. */ public void waitForInitToComplete() { synchronized (initSyncObject) { if (!initCompleteFlag) { try { initSyncObject.wait(); } catch (InterruptedException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } } } @Override public boolean renameArchive(String userName, String oldShortWorkfileName, String newShortWorkfileName, ServerResponseFactoryInterface response) { try { ClientRequestRenameData renameData = new ClientRequestRenameData(); renameData.setUserName(userName); renameData.setAppendedPath(getAppendedPath()); renameData.setProjectName(getProjectName()); renameData.setViewName(getViewName()); renameData.setNewShortWorkfileName(newShortWorkfileName); renameData.setOriginalShortWorkfileName(oldShortWorkfileName); synchronized (tranportProxySyncObject) { // The rename operation MUST be wrapped in a transaction. int transactionID = ClientTransactionManager.getInstance().sendBeginTransaction(transportProxy); transportProxy.write(renameData); ClientTransactionManager.getInstance().sendEndTransaction(transportProxy, transactionID); } } catch (Exception e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } return true; } @Override public int getDirectoryID() { return directoryID; } /** * Set the directory id. * @param dirID the directory id. */ public void setDirectoryID(int dirID) { this.directoryID = dirID; } /** * This is just a placeholder method to satisfy the interface. * @return N/A */ @Override public long getOldestRevision() { return 0L; } /** * This is just a placeholder method to satisfy the interface. * @param response N/A */ @Override public void addLogFileListener(ServerResponseFactoryInterface response) { } /** * This is just a placeholder method to satisfy the interface. * @param response N/A */ @Override public void removeLogFileListener(ServerResponseFactoryInterface response) { } @Override public boolean moveArchive(String userName, String shortWorkfileName, final ArchiveDirManagerInterface targetArchiveDirManager, ServerResponseFactoryInterface response) throws IOException, QVCSException { // This is not implemented on the client proxy. throw new UnsupportedOperationException("This is not implemented on the client proxy."); } @Override public boolean deleteArchive(String userName, String shortWorkfileName, ServerResponseFactoryInterface response) throws IOException, QVCSException { // This is not implemented on the client proxy. throw new UnsupportedOperationException("This is not implemented on the client proxy."); } @Override public boolean unDeleteArchive(String userName, String shortWorkfileName, ServerResponseFactoryInterface response) throws IOException, QVCSException { // This is not implemented on the client proxy. throw new UnsupportedOperationException("This is not implemented on the client proxy."); } /** * Get the sync object that we use for this directory. We use this to synchronize synchronous calls for the cases where we cannot directly use the LogfileProxy object for * synchronization. For example, when promoting a create from a translucent branch, we cannot synchronize on the LogfileProxy object because notifications that we receive * before we receive the response will already have caused the LogfileProxy object to have been removed from the list of files held by this directory manager. * * @return the sync object for this directory manager. */ public Object getSynchronizationObject() { return synchObject; } @Override public Date getMostRecentActivityDate() { return mostRecentCheckInDate; } /** * Update the most recent activity date. This will only update the most recent activity date <i>if</i> the given activity date is newer than the current 'most' recent * activity date. * @param activityDate the most recent activity date. */ public void updateMostRecentActivityDate(Date activityDate) { if (activityDate.after(mostRecentCheckInDate)) { mostRecentCheckInDate = activityDate; } } }