/* 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.server;
import com.qumasoft.qvcslib.MutableByteArray;
import com.qumasoft.qvcslib.QVCSConstants;
import com.qumasoft.qvcslib.QVCSException;
import com.qumasoft.qvcslib.RevisionHeader;
import com.qumasoft.qvcslib.RevisionInformation;
import com.qumasoft.qvcslib.Utility;
import com.qumasoft.qvcslib.commandargs.GetRevisionCommandArgs;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Get a revision.
* @author Jim Voris
*/
class LogFileOperationGetRevision extends AbstractLogFileOperation {
// Create our logger object
private static final Logger LOGGER = Logger.getLogger("com.qumasoft.server");
private final GetRevisionCommandArgs commandLineArgs;
private final String revisionString;
private final String labelString;
private final String fetchToFilename;
private final AtomicReference<String> mutableRevisionString;
private final MutableByteArray processedBuffer = new MutableByteArray();
/**
* Creates a new instance of LogFileOperationCheckOut
* @param args the arguments. a[0] is the logfileImpl; a[1] is the command argument object.
*/
public LogFileOperationGetRevision(Object[] args) {
super(args, (LogFileImpl) args[0]);
fetchToFilename = (String) args[1];
commandLineArgs = (GetRevisionCommandArgs) args[2];
revisionString = commandLineArgs.getRevisionString();
labelString = commandLineArgs.getLabel();
mutableRevisionString = new AtomicReference<>(revisionString);
}
@Override
public boolean execute() throws QVCSException {
boolean retVal = false;
// Do the real work here.
try {
// 1. Retrieve the revision into the requested file
retVal = getRevision();
commandLineArgs.setRevisionString(mutableRevisionString.get());
} catch (Exception e) {
LOGGER.log(Level.WARNING, "LogFileOperationCheckOut exception: " + e.toString() + ": " + e.getMessage());
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
retVal = false;
} finally {
// Remove any old archives.
getLogFileImpl().getTempFile().delete();
getLogFileImpl().getOldFile().delete();
}
return retVal;
}
private boolean getRevision() {
boolean bRetVal = true;
if (!getLogFileImpl().isArchiveInformationRead()) {
bRetVal = getLogFileImpl().readInformation();
}
if (bRetVal) {
// Figure out the default revision string if we need to.
if ((revisionString != null) && (revisionString.length() > 0)) {
if (0 == mutableRevisionString.get().compareTo(QVCSConstants.QVCS_DEFAULT_REVISION)) {
mutableRevisionString.set(getLogFileImpl().getDefaultRevisionHeader().getRevisionString());
}
} else if (commandLineArgs.getByLabelFlag()) {
// The label may not be present here.
bRetVal = false;
// This is a 'get by label' request. Find the label, if we can.
mutableRevisionString.set(getRevisionStringFromLabel(labelString));
if (mutableRevisionString.get() != null) {
bRetVal = true;
} else {
commandLineArgs.setFailureReason("Revision not found for label: [" + labelString + "].");
}
} else if (commandLineArgs.getByDateFlag()) {
// Find the revision in effect at the requested date... If the file
// hadn't yet been created, then we'll have to skip it. Note that
// we have to honor the 'default branch', so that we need to walk
// the revision tree to find only those revisions associated with
// the 'default branch', and then choose the correct revision from
// among that set of revisions.
bRetVal = getRevisionInEffectAtRequestedDate();
} else {
bRetVal = false;
commandLineArgs.setFailureReason("Internal error: Invalid command sent to getRevision()");
}
if (bRetVal) {
AtomicInteger revisionIndex = new AtomicInteger();
// Confirm that the revision exists in the archive
bRetVal = getLogFileImpl().findRevision(mutableRevisionString.get(), revisionIndex);
if (bRetVal) {
try {
if (getLogFileImpl().open()) {
bRetVal = getLogFileImpl().fetchRevision(getLogFileImpl().getRevisionHeader(revisionIndex.get()), fetchToFilename, false, processedBuffer);
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
commandLineArgs.setFailureReason(e.getLocalizedMessage());
bRetVal = false;
} finally {
getLogFileImpl().close();
}
} else {
commandLineArgs.setFailureReason("Revision: " + mutableRevisionString.get() + " not found in " + getLogFileImpl().getShortWorkfileName());
}
}
} else {
commandLineArgs.setFailureReason("Failed to read logfile information for " + getLogFileImpl().getShortWorkfileName());
}
return bRetVal;
}
private boolean getRevisionInEffectAtRequestedDate() {
boolean retVal = false;
Map<String, RevisionHeader> dateMap = new TreeMap<>();
RevisionInformation revisionInformation = getLogFileImpl().getRevisionInformation();
RevisionHeader defaultRevisionHeader = getLogFileImpl().getDefaultRevisionHeader();
if (defaultRevisionHeader.getDepth() == 0) {
for (int i = 0; i < getLogFileImpl().getRevisionCount(); i++) {
RevisionHeader revisionHeader = revisionInformation.getRevisionHeader(i);
if (revisionHeader.getDepth() == 0) {
dateMap.put(createDateKey(revisionHeader.getCheckInDate(), revisionHeader), revisionHeader);
}
}
} else {
for (int i = 0; i < getLogFileImpl().getRevisionCount(); i++) {
RevisionHeader revisionHeader = revisionInformation.getRevisionHeader(i);
if (revisionHeader.getRevisionDescriptor().compareTo(defaultRevisionHeader.getRevisionDescriptor()) <= 0) {
dateMap.put(createDateKey(revisionHeader.getCheckInDate(), revisionHeader), revisionHeader);
}
}
}
// This will iterate in date order, since we are using a Date as the key
// to the TreeMap collection. Find the last revision that is still
// before the requested date.
Iterator<RevisionHeader> it = dateMap.values().iterator();
while (it.hasNext()) {
RevisionHeader revisionHeader = it.next();
if (revisionHeader.getCheckInDate().before(commandLineArgs.getByDateValue())) {
mutableRevisionString.set(revisionHeader.getRevisionString());
retVal = true;
}
}
if (!retVal) {
commandLineArgs.setFailureReason("No revision exists on the requested date: " + commandLineArgs.getByDateValue().toString());
}
return retVal;
}
// We need to include a sortable revision number in the key so that revisions
// that have the same checkin Date will sort in a useful way. This is used
// for getting the correct revision of a DirectoryContents object, since
// the checkin times for those objects are often identical (since the checkin
// Date is taken from the enclosing transaction object).
private String createDateKey(Date date, RevisionHeader revisionHeader) {
long time = date.getTime();
String key = String.format("%020d:%s", time, revisionHeader.getRevisionDescriptor().toSortableString());
return key;
}
}