Issue #1243 Made localization lock more intelligently to avoid files getting out of sync and causing errors.

Amend: Fixed LocalizationFileOutputStream so it has option to close and save file and made LocalizationFile.write so it is more clear as to
what it is doing. Made CloudHeightData and AdaptivePlotResourceData not create FileUpdateMessages.

Amend: Rebased

Change-Id: I5aff6402004f4b4d3040c8c71f5ee16dfeb33567

Former-commit-id: fe11e31bfb [formerly dcde9e8951] [formerly 28e61c8285 [formerly 85a0b9bb03887b9ef50e67a1ae3a36afd24b3a58]]
Former-commit-id: 28e61c8285
Former-commit-id: 2e20c3e799
This commit is contained in:
Max Schenkelberg 2012-11-13 14:46:54 -06:00
parent 1540334a35
commit 5aa26e4f13
22 changed files with 735 additions and 656 deletions

View file

@ -29,12 +29,6 @@ import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import com.raytheon.uf.common.localization.FileUpdatedMessage;
import com.raytheon.uf.common.localization.ILocalizationFileObserver;
import com.raytheon.uf.common.localization.IPathManager;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType;
import com.raytheon.uf.common.localization.LocalizationFile;
import com.raytheon.uf.common.localization.PathManagerFactory;
import com.raytheon.uf.common.serialization.ISerializableObject;
import com.raytheon.uf.common.status.IUFStatusHandler;
@ -68,8 +62,6 @@ public class CloudHeightData implements ISerializableObject {
private static final String DATA_FILE = CLOUDHEIGHT_DATA_DIR
+ File.separator + "values.xml";
private static LocalizationFile cloudHeightDir;
private static CloudHeightData theData = null;
@XmlAccessorType(XmlAccessType.NONE)
@ -101,26 +93,9 @@ public class CloudHeightData implements ISerializableObject {
public static synchronized CloudHeightData getCloudHeightData() {
if (theData == null) {
IPathManager pm = PathManagerFactory.getPathManager();
theData = new CloudHeightData();
cloudHeightDir = pm.getLocalizationFile(pm.getContext(
LocalizationType.CAVE_STATIC, LocalizationLevel.BASE),
CLOUDHEIGHT_DATA_DIR);
ILocalizationFileObserver observer = new ILocalizationFileObserver() {
@Override
public void fileUpdated(FileUpdatedMessage message) {
if (DATA_FILE.equals(message.getFileName())) {
populateData(theData, PathManagerFactory
.getPathManager().getStaticFile(DATA_FILE));
}
}
};
// load data
observer.fileUpdated(new FileUpdatedMessage(null, DATA_FILE, null));
// TODO: cloudHeightDir.addFileUpdatedObserver(observer);
populateData(theData, PathManagerFactory.getPathManager()
.getStaticFile(DATA_FILE));
}
return theData;
}

View file

@ -26,6 +26,7 @@ import com.raytheon.uf.common.localization.LocalizationContext;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType;
import com.raytheon.uf.common.localization.LocalizationFile;
import com.raytheon.uf.common.localization.LocalizationFile.ModifiableLocalizationFile;
import com.raytheon.uf.common.localization.exception.LocalizationOpFailedException;
/**
@ -47,20 +48,6 @@ import com.raytheon.uf.common.localization.exception.LocalizationOpFailedExcepti
public class CollaborationLocalizationAdapter implements ILocalizationAdapter {
/*
* (non-Javadoc)
*
* @see
* com.raytheon.uf.common.localization.ILocalizationAdapter#getDirNameForType
* (
* com.raytheon.uf.common.localization.LocalizationContext.LocalizationType)
*/
@Override
public String getDirNameForType(LocalizationType type) {
// TODO Auto-generated method stub
return null;
}
/*
* (non-Javadoc)
*
@ -108,13 +95,12 @@ public class CollaborationLocalizationAdapter implements ILocalizationAdapter {
* (non-Javadoc)
*
* @see
* com.raytheon.uf.common.localization.ILocalizationAdapter#save(java.io
* .File, com.raytheon.uf.common.localization.LocalizationContext,
* java.lang.String)
* com.raytheon.uf.common.localization.ILocalizationAdapter#save(com.raytheon
* .uf.common.localization.LocalizationFile. ModifiableLocalizationFile)
*/
@Override
public boolean save(File localFile, LocalizationContext context,
String fileName) throws LocalizationOpFailedException {
public boolean save(ModifiableLocalizationFile file)
throws LocalizationOpFailedException {
// TODO Auto-generated method stub
return false;
}
@ -182,13 +168,12 @@ public class CollaborationLocalizationAdapter implements ILocalizationAdapter {
* (non-Javadoc)
*
* @see
* com.raytheon.uf.common.localization.ILocalizationAdapter#delete(java.
* io.File, com.raytheon.uf.common.localization.LocalizationContext,
* java.lang.String)
* com.raytheon.uf.common.localization.ILocalizationAdapter#delete(com.raytheon
* .uf.common.localization.LocalizationFile. ModifiableLocalizationFile)
*/
@Override
public boolean delete(File file, LocalizationContext context,
String fileName) throws LocalizationOpFailedException {
public boolean delete(ModifiableLocalizationFile file)
throws LocalizationOpFailedException {
// TODO Auto-generated method stub
return false;
}

View file

@ -20,6 +20,9 @@
package com.raytheon.uf.viz.core.localization;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@ -34,7 +37,9 @@ import com.raytheon.uf.common.localization.LocalizationContext;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType;
import com.raytheon.uf.common.localization.LocalizationFile;
import com.raytheon.uf.common.localization.LocalizationFile.ModifiableLocalizationFile;
import com.raytheon.uf.common.localization.LocalizationInternalFile;
import com.raytheon.uf.common.localization.LockingFileInputStream;
import com.raytheon.uf.common.localization.exception.LocalizationOpFailedException;
import com.raytheon.uf.common.localization.msgs.AbstractUtilityCommand;
import com.raytheon.uf.common.localization.msgs.AbstractUtilityResponse;
@ -72,15 +77,12 @@ public class CAVELocalizationAdapter implements ILocalizationAdapter {
this.contexts = new HashMap<LocalizationType, LocalizationContext[]>();
}
/*
* (non-Javadoc)
/**
* Returns a directory name for the localization type
*
* @see
* com.raytheon.uf.common.localization.ILocalizationAdapter#getDirNameForType
* (
* com.raytheon.uf.common.localization.LocalizationContext.LocalizationType)
* @param type
* @return
*/
@Override
public String getDirNameForType(LocalizationType type) {
if (type == LocalizationType.COMMON_STATIC) {
return "common";
@ -228,21 +230,39 @@ public class CAVELocalizationAdapter implements ILocalizationAdapter {
* (non-Javadoc)
*
* @see
* com.raytheon.uf.common.localization.ILocalizationAdapter#save(java.io
* .File, com.raytheon.uf.common.localization.LocalizationContext,
* java.lang.String)
* com.raytheon.uf.common.localization.ILocalizationAdapter#save(com.raytheon
* .uf.common.localization.LocalizationFile. ModifiableLocalizationFile)
*/
@Override
public boolean save(File localFile, LocalizationContext context,
String fileName) throws LocalizationOpFailedException {
if (context.getLocalizationLevel().isSystemLevel()) {
throw new UnsupportedOperationException(
"Saving to the System Level, "
+ context.getLocalizationLevel()
+ ", is not supported.");
public boolean save(ModifiableLocalizationFile file)
throws LocalizationOpFailedException {
File localFile = file.getLocalFile();
if (localFile.isDirectory() == false && localFile.exists()) {
InputStream in = null;
try {
in = new LockingFileInputStream(localFile);
long serverModTime = manager.upload(file.getContext(),
file.getFileName(), in, localFile.length());
// Success! set potentially changed fields
file.setTimeStamp(new Date(serverModTime));
file.setIsAvailableOnServer(true);
file.setIsDirectory(false);
return true;
} catch (FileNotFoundException e) {
throw new LocalizationOpFailedException(
"Error saving file, does not exist");
} finally {
// Make sure to close input stream
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Ignore close exception
}
}
}
}
return manager.upload(context, fileName, localFile);
return false;
}
/*
@ -443,16 +463,26 @@ public class CAVELocalizationAdapter implements ILocalizationAdapter {
* (non-Javadoc)
*
* @see
* com.raytheon.uf.common.localization.ILocalizationAdapter#delete(java.
* io.File, com.raytheon.uf.common.localization.LocalizationContext,
* java.lang.String)
* com.raytheon.uf.common.localization.ILocalizationAdapter#delete(com.raytheon
* .uf.common.localization.LocalizationFile. ModifiableLocalizationFile)
*/
@Override
public boolean delete(File file, LocalizationContext context,
String fileName) throws LocalizationOpFailedException {
file.delete();
return !file.exists() && manager.delete(context, fileName);
public boolean delete(ModifiableLocalizationFile file)
throws LocalizationOpFailedException {
long deleteTime = manager.delete(file.getContext(), file.getFileName());
// Made it here! file on server succesfully deleted! Delete local file
// reference. If that fails, doesn't matter since file does not exist!
File localFile = file.getLocalFile();
localFile.delete();
// Reset fields
file.setTimeStamp(new Date(deleteTime));
file.setIsAvailableOnServer(false);
file.setFileChecksum(null);
file.setIsDirectory(false);
return true;
}
private ListResponse convertResponse(ListResponseEntry entry,

View file

@ -22,10 +22,9 @@ package com.raytheon.uf.viz.core.localization;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@ -51,8 +50,8 @@ import com.raytheon.uf.common.localization.ILocalizationAdapter;
import com.raytheon.uf.common.localization.LocalizationContext;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel;
import com.raytheon.uf.common.localization.LocalizationFile;
import com.raytheon.uf.common.localization.LockingFileInputStream;
import com.raytheon.uf.common.localization.exception.LocalizationOpFailedException;
import com.raytheon.uf.common.localization.msgs.AbstractPrivilegedUtilityCommand;
import com.raytheon.uf.common.localization.msgs.AbstractUtilityResponse;
import com.raytheon.uf.common.localization.msgs.DeleteUtilityCommand;
import com.raytheon.uf.common.localization.msgs.DeleteUtilityResponse;
@ -76,7 +75,6 @@ import com.raytheon.uf.viz.core.VizApp;
import com.raytheon.uf.viz.core.VizServers;
import com.raytheon.uf.viz.core.exception.VizException;
import com.raytheon.uf.viz.core.requests.PrivilegedRequestFactory;
import com.raytheon.uf.viz.core.requests.ServerRequestException;
import com.raytheon.uf.viz.core.requests.ThriftClient;
/**
@ -131,7 +129,7 @@ public class LocalizationManager implements IPropertyChangeListener {
/** The current localization site */
private String currentSite;
private boolean nationalCenter;
private boolean overrideSite;
@ -398,11 +396,11 @@ public class LocalizationManager implements IPropertyChangeListener {
.getString("-site").toUpperCase();
this.overrideSite = true;
}
this.nationalCenter = false;
if (ProgramArguments.getInstance().getString("-nc") != null) {
this.nationalCenter = true;
this.nationalCenter = true;
}
}
@ -709,150 +707,145 @@ public class LocalizationManager implements IPropertyChangeListener {
}
/**
* Uploads a file to EDEX through the UtilitySrv
* Uploads a stream as a file to EDEX through the UtilitySrv. It is the
* responsibility of the caller to close the stream
*
* @param context
* the context to upload to
* @param filename
* the name of the file
* @param bytes
* the contents of the file
* @return
* @throws LocalizationCommunicationException
* @param in
* @param streamLength
* @return the new server time stamp
* @throws LocalizationOpFailedException
*/
protected boolean upload(LocalizationContext context, String filename,
FileInputStream fin, long totalSize)
protected long upload(LocalizationContext context, String filename,
InputStream in, long streamLength)
throws LocalizationOpFailedException {
boolean success = true;
if (context.getLocalizationLevel().isSystemLevel()) {
throw new UnsupportedOperationException(
"Saving to the System Level, "
+ context.getLocalizationLevel()
+ ", is not supported.");
}
LocalizationStreamPutRequest request;
try {
LocalizationStreamPutRequest request = PrivilegedRequestFactory
request = PrivilegedRequestFactory
.constructPrivilegedRequest(LocalizationStreamPutRequest.class);
request.setMyContextName(getContextName(context
request.setMyContextName(LocalizationManager.getContextName(context
.getLocalizationLevel()));
request.setContext(context);
request.setFileName(filename);
request.setOffset(0);
boolean finished = false;
byte[] bytes = new byte[512 * 1024];
int totalRead = 0;
while (!finished) {
request.setOffset(totalRead);
int read = fin.read(bytes, 0, bytes.length);
} catch (VizException e) {
throw new LocalizationOpFailedException(
"Could not construct privileged utility request", e);
}
long serverModTime = -1;
// Create byte[] buffer
byte[] bytes = new byte[512 * 1024];
// initial offset = 0
int offset = 0;
do {
// set current offset
request.setOffset(offset);
try {
// read in data from input stream
int read = in.read(bytes, 0, bytes.length);
if (read > 0) {
totalRead += read;
bytes = Arrays.copyOf(bytes, read);
request.setBytes(bytes);
request.setEnd(totalRead == totalSize);
// byte read, trim if necessary
offset += read;
if (read < bytes.length) {
bytes = Arrays.copyOf(bytes, read);
}
} else {
request.setBytes(new byte[0]);
request.setEnd(true);
bytes = new byte[0];
// Should be case but paranoia takes over
offset = (int) streamLength;
}
if (request.isEnd()) {
finished = true;
}
success = (ThriftClient.sendLocalizationRequest(request) != null);
}
} catch (ServerRequestException e) {
statusHandler.handle(Priority.PROBLEM, e.getMessage(), e);
success = false;
} catch (Exception e) {
e.printStackTrace();
statusHandler.handle(Priority.PROBLEM,
"Error uploading file to server", e);
success = false;
} finally {
try {
fin.close();
request.setBytes(bytes);
request.setEnd(offset == streamLength);
} catch (IOException e) {
// ignore message
}
}
return success;
}
/**
* Uploads a file to the edex through the UtilitySrv
*
* @param context
* the context to upload to
* @param filename
* the name of the file
* @param localFile
* the local file, used to send the contents of the file to the
* service
* @return
* @throws LocalizationOpFailedException
* @throws LocalizationCommunicationException
*/
protected boolean upload(LocalizationContext context, String path,
File localFile) throws LocalizationOpFailedException {
if (localFile.isDirectory()) {
// TODO: Someday upload entire directory structure onto
// server?
} else if (localFile.exists()) {
// Upload single file
try {
return upload(context, path, new LockingFileInputStream(
localFile), localFile.length());
} catch (FileNotFoundException e) {
throw new LocalizationOpFailedException(
"Error opening input stream on localization file", e);
"Could not save file, failed to read in contents", e);
}
}
return true;
try {
Number modTime = (Number) ThriftClient
.sendLocalizationRequest(request);
if (modTime != null) {
serverModTime = modTime.longValue();
}
} catch (VizException e) {
throw new LocalizationOpFailedException(
"Error storing file contents to server: "
+ e.getLocalizedMessage(), e);
}
} while (request.isEnd() == false);
return serverModTime;
}
/**
* Deletes a localization file
* Deletes a localization file from localization server
*
* @param context
* the context to the file
* @param filename
* the name of the file
* @return
* @return modified time on server
* @throws LocalizationOpFailedException
* @throws LocalizationCommunicationException
*/
protected boolean delete(LocalizationContext context, String filename)
protected long delete(LocalizationContext context, String filename)
throws LocalizationOpFailedException {
// Build list commands
DeleteUtilityCommand[] commands = new DeleteUtilityCommand[1];
commands[0] = new DeleteUtilityCommand(context, filename);
commands[0].setMyContextName(getContextName(context
.getLocalizationLevel()));
PrivilegedUtilityRequestMessage deleteRequest = null;
PrivilegedUtilityRequestMessage request;
try {
deleteRequest = PrivilegedRequestFactory
request = PrivilegedRequestFactory
.constructPrivilegedRequest(PrivilegedUtilityRequestMessage.class);
deleteRequest.setCommands(commands);
} catch (VizException e) {
throw new LocalizationOpFailedException(
"Error constructing privileged utility request", e);
"Could not construct privileged utility request", e);
}
AbstractUtilityResponse[] responseList = makeRequest(deleteRequest);
if (responseList == null) {
return false;
}
for (AbstractUtilityResponse response : responseList) {
if (!(response instanceof DeleteUtilityResponse)) {
DeleteUtilityCommand command = new DeleteUtilityCommand(context,
filename);
command.setMyContextName(getContextName(context.getLocalizationLevel()));
AbstractPrivilegedUtilityCommand[] commands = new AbstractPrivilegedUtilityCommand[] { command };
request.setCommands(commands);
try {
UtilityResponseMessage response = (UtilityResponseMessage) ThriftClient
.sendLocalizationRequest(request);
if (response == null) {
throw new LocalizationOpFailedException(
"Unexpected type returned"
+ response.getClass().getName());
"No response received for delete command");
}
// DeleteUtilityResponse deleteResponse = (DeleteUtilityResponse)
// response;
// TODO log this
AbstractUtilityResponse[] responses = response.getResponses();
if (responses == null || responses.length != commands.length) {
throw new LocalizationOpFailedException(
"Unexpected return type from delete: Expected "
+ commands.length + " responses, received "
+ (responses != null ? responses.length : null));
}
AbstractUtilityResponse rsp = responses[0];
if (rsp instanceof DeleteUtilityResponse) {
DeleteUtilityResponse dur = (DeleteUtilityResponse) rsp;
if (dur.getErrorText() != null) {
throw new LocalizationOpFailedException(
"Error processing delete command: "
+ dur.getErrorText());
}
// Yay, successful execution!
return dur.getTimeStamp();
} else {
throw new LocalizationOpFailedException(
"Unexpected return type from delete: Expected "
+ DeleteUtilityResponse.class + " received "
+ (rsp != null ? rsp.getClass() : null));
}
} catch (VizException e) {
throw new LocalizationOpFailedException(
"Error processing delete command: "
+ e.getLocalizedMessage(), e);
}
return true;
}
/**
@ -958,8 +951,8 @@ public class LocalizationManager implements IPropertyChangeListener {
public boolean isOverrideSite() {
return overrideSite;
}
public boolean isNationalCenter() {
return nationalCenter;
return nationalCenter;
}
}

View file

@ -161,8 +161,7 @@ public class AdaptivePlotResourceData extends AbstractResourceData implements
LoadProperties loadProperties, IDescriptor descriptor)
throws VizException {
if (file == null) {
fileUpdated(new FileUpdatedMessage(null, null,
FileChangeType.DELETED));
fileUpdated(FileChangeType.ADDED, filePath);
}
if (file == null) {
@ -186,10 +185,17 @@ public class AdaptivePlotResourceData extends AbstractResourceData implements
*/
@Override
public synchronized void fileUpdated(FileUpdatedMessage message) {
fileUpdated(message.getChangeType(), message.getFileName());
}
private void fileUpdated(FileChangeType changeType, String filePath) {
Set<PlotObject> newObjects = new HashSet<PlotObject>();
switch (message.getChangeType()) {
switch (changeType) {
case DELETED:
case ADDED: {
if (file != null) {
file.removeFileUpdatedObserver(this);
}
// Our old file was deleted, search for file
file = PathManagerFactory.getPathManager()
.getStaticLocalizationFile(filePath);

View file

@ -33,6 +33,7 @@ import com.raytheon.uf.common.localization.LocalizationContext;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType;
import com.raytheon.uf.common.localization.LocalizationFile;
import com.raytheon.uf.common.localization.LocalizationFile.ModifiableLocalizationFile;
import com.raytheon.uf.common.localization.exception.LocalizationOpFailedException;
import com.raytheon.uf.edex.core.props.EnvProperties;
import com.raytheon.uf.edex.core.props.PropertiesFactory;
@ -73,23 +74,6 @@ public class EDEXLocalizationAdapter implements ILocalizationAdapter {
this.contexts = new HashMap<LocalizationType, LocalizationContext[]>();
}
/*
* (non-Javadoc)
*
* @see
* com.raytheon.edex.utility.ILocalizationAdapter#getDirNameForType(com.
* raytheon.edex.utility.LocalizationContext.LocalizationType)
*/
@Override
public String getDirNameForType(LocalizationType type) {
if (type == LocalizationType.UNKNOWN) {
throw new IllegalArgumentException("Unsupported type: " + type);
} else {
return type.toString().toLowerCase();
}
}
/*
* (non-Javadoc)
*
@ -294,43 +278,37 @@ public class EDEXLocalizationAdapter implements ILocalizationAdapter {
// --- CRUD Operations ---------------------------------------------------
/**
* <EM>UNSUPPORTED</EM> Create a file
* <p>
* On EDEX all file operations are local and do not need to be created,
* read, updated, or deleted from a remote source. Instead of using the CRUD
* methods provided by the {@link ILocalizationAdapter} interface use
* {@link #getPath(LocalizationContext, String)} and appropriate File object
* methods.
* </p>
/*
* (non-Javadoc)
*
* @see com.raytheon.uf.common.localization.ILocalizationAdapter#save(java.io.File,
* com.raytheon.uf.common.localization.LocalizationContext,
* java.lang.String)
* @see com.raytheon.uf.common.localization.ILocalizationAdapter#save(
* com.raytheon.uf.common.localization.LocalizationFile.
* ModifiableLocalizationFile)
*/
@Override
public boolean save(File localFile, LocalizationContext context,
String fileName) throws LocalizationOpFailedException {
if (context.getLocalizationLevel().equals(LocalizationLevel.BASE)) {
public boolean save(ModifiableLocalizationFile file)
throws LocalizationOpFailedException {
if (file.getContext().getLocalizationLevel()
.equals(LocalizationLevel.BASE)) {
throw new UnsupportedOperationException(
"Saving to the BASE context is not supported.");
}
return true;
}
/**
* Delete a file. Deletes the local file.
/*
* (non-Javadoc)
*
* @see com.raytheon.uf.common.localization.ILocalizationAdapter#delete(java.io.File,
* com.raytheon.uf.common.localization.LocalizationContext,
* java.lang.String)
* @see com.raytheon.uf.common.localization.ILocalizationAdapter#delete(
* com.raytheon.uf.common.localization.LocalizationFile.
* ModifiableLocalizationFile)
*/
@Override
public boolean delete(File file, LocalizationContext context,
String fileName) throws LocalizationOpFailedException {
if (file.exists()) {
return file.delete();
public boolean delete(ModifiableLocalizationFile file)
throws LocalizationOpFailedException {
File localFile = file.getLocalFile();
if (localFile.exists()) {
return localFile.delete();
}
return true;
}

View file

@ -1,6 +0,0 @@
<project basedir="." default="deploy" name="com.raytheon.edex.plugin.gfe">
<available file="../build.edex" property="build.dir.location" value="../build.edex" />
<available file="../../../../../build.edex" property="build.dir.location" value="../../../../../build.edex" />
<import file="${build.dir.location}/basebuilds/component_deploy_base.xml" />
</project>

View file

@ -105,12 +105,11 @@ public class SaveCombinationsFileHandler implements
if (isAdded) {
changeType = FileChangeType.ADDED;
}
EDEXUtil.getMessageProducer()
.sendAsync(
"utilityNotify",
new FileUpdatedMessage(localization, FileUtil.join(
COMBO_FILE_DIR, request.getFileName()),
changeType));
EDEXUtil.getMessageProducer().sendAsync(
"utilityNotify",
new FileUpdatedMessage(localization, FileUtil.join(
COMBO_FILE_DIR, request.getFileName()), changeType,
localFile.lastModified()));
} finally {
if (file != null) {
file.close();

View file

@ -1,7 +0,0 @@
<project basedir="." default="deploy" name="com.raytheon.edex.utilitysrv">
<available file="../build.edex" property="build.dir.location" value="../build.edex"/>
<available file="../../../../../build.edex" property="build.dir.location" value="../../../../../build.edex"/>
<import file="${build.dir.location}/basebuilds/component_deploy_base.xml" />
</project>

View file

@ -29,6 +29,8 @@ import java.util.Map;
import com.raytheon.edex.utility.ProtectedFiles;
import com.raytheon.uf.common.auth.exception.AuthorizationException;
import com.raytheon.uf.common.auth.user.IUser;
import com.raytheon.uf.common.localization.FileLocker;
import com.raytheon.uf.common.localization.FileLocker.Type;
import com.raytheon.uf.common.localization.FileUpdatedMessage;
import com.raytheon.uf.common.localization.FileUpdatedMessage.FileChangeType;
import com.raytheon.uf.common.localization.LocalizationContext;
@ -169,6 +171,8 @@ public class LocalizationStreamHandler
private Object handleStreamingGet(LocalizationStreamGetRequest request,
File file) throws Exception {
// TODO: Copy file to tmp location named from request unique id and
// stream that file for the request to avoid put/delete/read issues
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
@ -214,8 +218,8 @@ public class LocalizationStreamHandler
if (file.getParentFile().exists() == false) {
file.getParentFile().mkdirs();
}
File tmpFile = new File(file.getParentFile(), file.getName() + "."
+ request.getId());
File tmpFile = new File(file.getParentFile(), "." + file.getName()
+ "." + request.getId());
if ((tmpFile.exists() == false) && (request.getOffset() != 0)) {
throw new LocalizationException(
"Illegal state, request has offset set but file "
@ -238,12 +242,10 @@ public class LocalizationStreamHandler
tmpFile.createNewFile();
}
FileOutputStream outputStream = null;
Integer bytesWritten = 0;
try {
outputStream = new FileOutputStream(tmpFile, true);
byte[] bytes = request.getBytes();
bytesWritten += bytes.length;
outputStream.write(bytes);
} finally {
if (outputStream != null) {
@ -252,21 +254,35 @@ public class LocalizationStreamHandler
}
}
if (request.isEnd()) {
FileChangeType changeType = FileChangeType.UPDATED;
if (!file.exists()) {
changeType = FileChangeType.ADDED;
try {
FileLocker.lock(this, file, Type.WRITE);
FileChangeType changeType = FileChangeType.UPDATED;
if (!file.exists()) {
changeType = FileChangeType.ADDED;
}
tmpFile.renameTo(file);
try {
// attempt to generate checksum after change
UtilityManager.writeChecksum(file);
} catch (Exception e) {
// ignore, will be generated next time requested
}
long timeStamp = file.lastModified();
EDEXUtil.getMessageProducer().sendAsync(
UtilityManager.NOTIFY_ID,
new FileUpdatedMessage(request.getContext(), request
.getFileName(), changeType, timeStamp));
return timeStamp;
} finally {
FileLocker.unlock(this, file);
}
tmpFile.renameTo(file);
// tmpFile.delete();
EDEXUtil.getMessageProducer().sendAsync(
UtilityManager.NOTIFY_ID,
new FileUpdatedMessage(request.getContext(), request
.getFileName(), changeType));
}
return bytesWritten;
return tmpFile.lastModified();
}
@Override

View file

@ -21,10 +21,11 @@
package com.raytheon.edex.services;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Date;
@ -33,6 +34,8 @@ import org.apache.commons.logging.LogFactory;
import com.raytheon.edex.utility.ProtectedFiles;
import com.raytheon.uf.common.localization.Checksum;
import com.raytheon.uf.common.localization.FileLocker;
import com.raytheon.uf.common.localization.FileLocker.Type;
import com.raytheon.uf.common.localization.FileUpdatedMessage;
import com.raytheon.uf.common.localization.FileUpdatedMessage.FileChangeType;
import com.raytheon.uf.common.localization.LocalizationContext;
@ -41,7 +44,6 @@ import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType;
import com.raytheon.uf.common.localization.msgs.DeleteUtilityResponse;
import com.raytheon.uf.common.localization.msgs.ListResponseEntry;
import com.raytheon.uf.common.localization.msgs.ListUtilityResponse;
import com.raytheon.uf.common.util.FileUtil;
import com.raytheon.uf.edex.core.EDEXUtil;
import com.raytheon.uf.edex.core.EdexException;
@ -121,35 +123,47 @@ public class UtilityManager {
* @throws EdexException
*/
private static String getFileChecksum(File file) throws EdexException {
File checksumFile = new File(file.toString() + CHECKSUM_FILE_EXTENSION);
FileLocker.lock(UtilityManager.class, file, Type.WRITE);
File checksumFile = getChecksumFile(file);
String chksum = null;
if (!checksumFile.exists()
|| (checksumFile.lastModified() < file.lastModified())) {
// Create a checksum
try {
chksum = Checksum.getMD5Checksum(file);
FileUtil.bytes2File(chksum.getBytes(), checksumFile);
} catch (Exception e) {
// ignore, no checksum will be provided
}
} else {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(checksumFile));
chksum = br.readLine();
} catch (Exception e) {
// ignore, no checksum will be provided
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
// ignore - can't do anything anyway...
}
try {
if (checksumFile.exists()
&& checksumFile.lastModified() >= file.lastModified()) {
BufferedReader reader = new BufferedReader(new FileReader(
checksumFile));
try {
chksum = reader.readLine();
} finally {
reader.close();
}
}
if (chksum == null) {
chksum = writeChecksum(file);
}
} catch (Exception e) {
// log, no checksum will be provided
logger.error("Error determing file checksum for: " + file, e);
} finally {
FileLocker.unlock(UtilityManager.class, file);
}
return chksum;
}
private static File getChecksumFile(File utilityFile) {
return new File(utilityFile.getParentFile(), utilityFile.getName()
+ CHECKSUM_FILE_EXTENSION);
}
public static String writeChecksum(File file) throws Exception {
String chksum = null;
File checksumFile = getChecksumFile(file);
BufferedWriter bw = new BufferedWriter(new FileWriter(checksumFile));
try {
chksum = Checksum.getMD5Checksum(file);
bw.write(chksum);
} finally {
bw.close();
}
return chksum;
}
@ -176,7 +190,20 @@ public class UtilityManager {
File delFile = new File(fullPath);
if (delFile.exists()) {
delFile.delete();
if (!delFile.delete()) {
// Failed to delete file...
msg = "File could not be deleted: ";
if (delFile.isDirectory() && delFile.list().length > 0) {
msg += "Non-empty directory";
} else if (delFile.canWrite() == false) {
msg += "Do not have write permission to file";
} else if (delFile.getParentFile() != null
&& delFile.getParentFile().canWrite() == false) {
msg += "Do not have write permission to file's parent directory";
} else {
msg += "Reason unknown";
}
}
String md5Path = fullPath + ".md5";
File md5File = new File(md5Path);
if (md5File.exists()) {
@ -184,20 +211,22 @@ public class UtilityManager {
}
}
} catch (Exception e) {
return new DeleteUtilityResponse(context, e.getMessage(), fileName);
return new DeleteUtilityResponse(context, e.getMessage(), fileName,
System.currentTimeMillis());
}
long timeStamp = System.currentTimeMillis();
// send notification
try {
EDEXUtil.getMessageProducer().sendAsync(
NOTIFY_ID,
new FileUpdatedMessage(context, fileName,
FileChangeType.DELETED));
FileChangeType.DELETED, timeStamp));
} catch (Exception e) {
logger.error("Error sending file updated message", e);
}
return new DeleteUtilityResponse(context, msg, fileName);
return new DeleteUtilityResponse(context, msg, fileName, timeStamp);
}
/**

View file

@ -1,7 +0,0 @@
<project basedir="." default="deploy" name="com.raytheon.uf.common.localization">
<available file="../build.edex" property="build.dir.location" value="../build.edex"/>
<available file="../../../../../build.edex" property="build.dir.location" value="../../../../../build.edex"/>
<import file="${build.dir.location}/basebuilds/component_deploy_base.xml" />
</project>

View file

@ -63,14 +63,19 @@ public class FileUpdatedMessage implements ISerializableObject {
@XmlElement
private FileChangeType changeType;
@DynamicSerializeElement
@XmlAttribute
private long timeStamp;
public FileUpdatedMessage() {
}
public FileUpdatedMessage(LocalizationContext context, String fileName,
FileChangeType changeType) {
FileChangeType changeType, long timeStamp) {
this.context = context;
this.fileName = fileName;
this.changeType = changeType;
this.timeStamp = timeStamp;
}
public LocalizationContext getContext() {
@ -96,4 +101,20 @@ public class FileUpdatedMessage implements ISerializableObject {
public void setChangeType(FileChangeType changeType) {
this.changeType = changeType;
}
/**
* @return the timeStamp
*/
public long getTimeStamp() {
return timeStamp;
}
/**
* @param timeStamp
* the timeStamp to set
*/
public void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
}
}

View file

@ -25,6 +25,7 @@ import java.util.Date;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType;
import com.raytheon.uf.common.localization.LocalizationFile.ModifiableLocalizationFile;
import com.raytheon.uf.common.localization.exception.LocalizationOpFailedException;
/**
@ -44,19 +45,6 @@ import com.raytheon.uf.common.localization.exception.LocalizationOpFailedExcepti
*/
public interface ILocalizationAdapter {
/**
* Retrieves the directory name for the given localization type.
*
* @param type
* the localization type
* @return the directory name of the directory where the localization type
* should be stored. The returned string is the directory name only,
* NOT the fully qualified path.
*
* @see #getPath(LocalizationContext, String)
*/
public abstract String getDirNameForType(LocalizationType type);
/**
* Return the fully qualified path given a localization context and a file
* name
@ -93,19 +81,14 @@ public interface ILocalizationAdapter {
throws LocalizationOpFailedException;
/**
* Save a file given it's local pointer, a localization context and a file
* name
* Save a file a modifiable localization file
*
* @param localFile
* the local file pointer
* @param context
* the localization context
* @param fileName
* the file name
* @param file
* the modifiable localization file
* @throws LocalizationOpFailedException
*/
public abstract boolean save(File localFile, LocalizationContext context,
String fileName) throws LocalizationOpFailedException;
public abstract boolean save(ModifiableLocalizationFile file)
throws LocalizationOpFailedException;
/**
* List a directory given a set of contexts and a path.
@ -162,18 +145,14 @@ public interface ILocalizationAdapter {
LocalizationLevel level);
/**
* Delete a file given a local file pointer, context and filename
* Delete a file given a modifiable localization file
*
* @param file
* the file pointer
* @param context
* the localization context
* @param fileName
* the name of the file on the server
* the modifiable localization file
* @throws LocalizationOpFailedException
*/
public abstract boolean delete(File file, LocalizationContext context,
String fileName) throws LocalizationOpFailedException;
public abstract boolean delete(ModifiableLocalizationFile file)
throws LocalizationOpFailedException;
public abstract String[] getContextList(LocalizationLevel level)
throws LocalizationOpFailedException;

View file

@ -27,7 +27,10 @@ import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType;
/**
* A generalized interface for constructing LocalizationFiles.
* A generalized interface for constructing LocalizationFiles. NOTE: There will
* only exist a single reference to any LocalizationFile. It is the
* IPathManager's responsibility to ensure multiple objects of the same
* LocalizationFile are not returned
*
* Note: Paths should use IPathManager.SEPARATOR as the separator for
* consistency. The client OS could potentially differ from the localization

View file

@ -23,13 +23,13 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.raytheon.uf.common.localization.FileLocker.Type;
import com.raytheon.uf.common.localization.ILocalizationAdapter.ListResponse;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel;
import com.raytheon.uf.common.localization.exception.LocalizationException;
@ -85,12 +85,60 @@ import com.raytheon.uf.common.status.UFStatus.Priority;
* @version 1.0
*/
public class LocalizationFile implements Comparable<LocalizationFile> {
public final class LocalizationFile implements Comparable<LocalizationFile> {
private static transient IUFStatusHandler statusHandler = UFStatus
.getHandler(LocalizationFile.class);
/**
* Class {@link LocalizationFile} exposes to the
* {@link ILocalizationAdapter} objects so they can modify the file if
* anything changes. Don't want to expose ability to modify
* {@link LocalizationFile} contents to everyone
*
* @author mschenke
* @version 1.0
*/
public class ModifiableLocalizationFile {
private ModifiableLocalizationFile() {
// Private constructor
}
public LocalizationFile getLocalizationFile() {
return LocalizationFile.this;
}
public void setTimeStamp(Date timeStamp) {
getLocalizationFile().fileTimestamp = timeStamp;
}
public void setFileChecksum(String checksum) {
getLocalizationFile().fileCheckSum = checksum;
}
public void setIsAvailableOnServer(boolean isAvailableOnServer) {
getLocalizationFile().isAvailableOnServer = isAvailableOnServer;
}
public void setIsDirectory(boolean isDirectory) {
getLocalizationFile().isDirectory = isDirectory;
}
public File getLocalFile() {
return getLocalizationFile().file;
}
public String getFileName() {
return getLocalizationFile().path;
}
public LocalizationContext getContext() {
return getLocalizationFile().context;
}
}
/** Local file pointer to localization file, will never be null */
private File file;
protected final File file;
/** The file timestamp on the server, may be null if file doesn't exist yet */
private Date fileTimestamp;
@ -108,7 +156,7 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
private boolean isAvailableOnServer;
/** The localization adapter for the file */
final ILocalizationAdapter adapter;
protected final ILocalizationAdapter adapter;
/** The localization path of the file */
private final String path;
@ -120,13 +168,14 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
private Set<ILocalizationFileObserver> observers = new HashSet<ILocalizationFileObserver>();
/** Flag to set if file has been requested */
boolean fileRequested = false;
protected boolean fileRequested = false;
/**
* Construct a null localization file, used to keep track of files that
* cannot exist.
*/
LocalizationFile() {
file = null;
path = null;
adapter = null;
context = null;
@ -138,7 +187,8 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
* @return
*/
boolean isNull() {
return adapter == null && path == null && context == null;
return adapter == null && path == null && context == null
&& file == null;
}
LocalizationFile(ILocalizationAdapter adapter, LocalizationContext context,
@ -173,6 +223,16 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
}
}
/**
* Returns a modifiable version of the localization file. Meant to be used
* internally within localization only which is why package level
*
* @return
*/
ModifiableLocalizationFile getModifiableFile() {
return new ModifiableLocalizationFile();
}
/**
* This returns the time stamp of the file where it is stored, not the local
* version of the file
@ -209,27 +269,33 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
* @return the file
*/
public File getFile(boolean retrieveFile) throws LocalizationException {
if (retrieveFile) {
fileRequested = true;
}
if (isAvailableOnServer && retrieveFile) {
if (isDirectory) {
file.mkdirs();
try {
FileLocker.lock(this, file, Type.WRITE);
if (retrieveFile) {
fileRequested = true;
}
adapter.retrieve(this);
}
if (isDirectory == false && !file.exists()) {
try {
file.getParentFile().mkdirs();
} catch (Throwable t) {
// try to create the file's directory automatically, but if it
// fails, don't report it as it is just something to do to help
// the user of the file for easier creation of the file
if (isAvailableOnServer && retrieveFile) {
if (isDirectory) {
file.mkdirs();
}
adapter.retrieve(this);
}
}
return file;
if (isDirectory == false && !file.exists()) {
try {
file.getParentFile().mkdirs();
} catch (Throwable t) {
// try to create the file's directory automatically, but if
// it fails, don't report it as it is just something to do
// to help the user of the file for easier creation of the
// file
}
}
return file;
} finally {
FileLocker.unlock(this, file);
}
}
/**
@ -255,7 +321,8 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
* @return the InputStream to be used for reading the file
* @throws LocalizationException
*/
public InputStream openInputStream() throws LocalizationException {
public LocalizationFileInputStream openInputStream()
throws LocalizationException {
try {
return new LocalizationFileInputStream(this);
} catch (FileNotFoundException e) {
@ -324,7 +391,8 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
* @return the OutputStream to be used for writing to the file
* @throws LocalizationException
*/
public OutputStream openOutputStream() throws LocalizationException {
public LocalizationFileOutputStream openOutputStream()
throws LocalizationException {
return openOutputStream(false);
}
@ -335,7 +403,7 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
* @return the OutputStream to be used for writing to the file
* @throws LocalizationException
*/
public OutputStream openOutputStream(boolean isAppending)
public LocalizationFileOutputStream openOutputStream(boolean isAppending)
throws LocalizationException {
try {
return new LocalizationFileOutputStream(this, isAppending);
@ -352,23 +420,20 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
* @throws LocalizationException
*/
public void write(byte[] bytes) throws LocalizationException {
OutputStream os = null;
LocalizationFileOutputStream os = null;
try {
os = openOutputStream();
os.write(bytes);
save();
} catch (IOException e) {
throw new LocalizationException("Could not write to file "
+ file.getName(), e);
} finally {
if (os != null) {
try {
os.close();
os.closeAndSave();
} catch (IOException e) {
statusHandler
.handle(Priority.INFO,
"Failed to close output stream to area geometries file",
e);
statusHandler.handle(Priority.INFO,
"Failed to close output stream for file", e);
}
}
}
@ -426,23 +491,28 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
* @throws LocalizationOpFailedException
*/
public boolean save() throws LocalizationOpFailedException {
String checksum = "";
try {
checksum = Checksum.getMD5Checksum(file);
} catch (Exception e) {
// Ignore
}
// Check if file differs from server file
if (!checksum.equals(fileCheckSum)) {
boolean rval = adapter.save(file, context, path);
if (rval) {
fileCheckSum = checksum;
FileLocker.lock(this, file, Type.WRITE);
String checksum = "";
try {
checksum = Checksum.getMD5Checksum(file);
} catch (Exception e) {
// Ignore
}
// Check if file differs from server file
if (!checksum.equals(fileCheckSum)) {
boolean rval = adapter.save(getModifiableFile());
if (rval) {
fileCheckSum = checksum;
}
return rval;
}
return rval;
}
// Local file matches server file, success technically
return true;
// Local file matches server file, success technically
return true;
} finally {
FileLocker.unlock(this, file);
}
}
/**
@ -461,15 +531,20 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
* @throws LocalizationOpFailedException
*/
public boolean delete() throws LocalizationOpFailedException {
if (exists()) {
return adapter.delete(file, context, path);
} else if (file.exists()) {
// Local file does actually exist, delete manually
return file.delete();
}
try {
FileLocker.lock(this, file, Type.WRITE);
if (exists()) {
return adapter.delete(getModifiableFile());
} else if (file.exists()) {
// Local file does actually exist, delete manually
return file.delete();
}
// File doesn't exist, it is deleted, so technically success?
return true;
// File doesn't exist, it is deleted, so technically success?
return true;
} finally {
FileLocker.unlock(this, file);
}
}
/**
@ -478,7 +553,7 @@ public class LocalizationFile implements Comparable<LocalizationFile> {
* @return true if the file exists
*/
public boolean exists() {
return adapter.exists(this);
return isNull() == false && adapter.exists(this);
}
/**

View file

@ -23,6 +23,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import com.raytheon.uf.common.localization.exception.LocalizationException;
import com.raytheon.uf.common.localization.exception.LocalizationOpFailedException;
/**
* Class which opens a LocalizationFile for writing to
@ -43,6 +44,8 @@ import com.raytheon.uf.common.localization.exception.LocalizationException;
public class LocalizationFileOutputStream extends LockingFileOutputStream {
private LocalizationFile file;
/**
* @param file
* @param isAppending
@ -53,6 +56,25 @@ public class LocalizationFileOutputStream extends LockingFileOutputStream {
LocalizationFileOutputStream(LocalizationFile file, boolean isAppending)
throws FileNotFoundException, LocalizationException {
super(file.getFile(false), isAppending);
this.file = file;
}
/**
* Closes input stream for the {@link LocalizationFile} and calls
* {@link LocalizationFile#save()} on the file to ensure contents are
* persisted. Calling {@link #close()} does not trigger a save
*
* @param save
* @throws IOException
*/
public void closeAndSave() throws IOException,
LocalizationOpFailedException {
try {
closeWithoutUnlocking();
file.save();
} finally {
// Make sure we unlock the file
unlock();
}
}
}

View file

@ -1,61 +0,0 @@
/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.common.localization;
import java.lang.ref.WeakReference;
/**
* WeakReference object to Localization File
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jun 21, 2011 mschenke Initial creation
*
* </pre>
*
* @author mschenke
* @version 1.0
*/
public class LocalizationFileRef extends WeakReference<LocalizationFile> {
private final LocalizationFileKey key;
/**
* @param referent
*/
public LocalizationFileRef(LocalizationFile referent) {
super(referent);
key = new LocalizationFileKey(referent.getName(), referent.getContext());
}
@Override
public String toString() {
return key.toString();
}
public Object getKey() {
return key;
}
}

View file

@ -20,14 +20,14 @@
package com.raytheon.uf.common.localization;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.raytheon.uf.common.localization.FileLocker.Type;
import com.raytheon.uf.common.localization.FileUpdatedMessage.FileChangeType;
import com.raytheon.uf.common.localization.ILocalizationAdapter.ListResponse;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel;
@ -58,12 +58,12 @@ import com.raytheon.uf.common.status.UFStatus.Priority;
public class LocalizationNotificationObserver {
private static class LocalizationFileKey {
private static class LocalizationTypeFileKey {
private final LocalizationType type;
private final String path;
public LocalizationFileKey(LocalizationType type, String path) {
public LocalizationTypeFileKey(LocalizationType type, String path) {
super();
this.type = type;
this.path = path;
@ -89,7 +89,7 @@ public class LocalizationNotificationObserver {
if (getClass() != obj.getClass()) {
return false;
}
final LocalizationFileKey other = (LocalizationFileKey) obj;
final LocalizationTypeFileKey other = (LocalizationTypeFileKey) obj;
if (path == null) {
if (other.path != null) {
return false;
@ -119,7 +119,7 @@ public class LocalizationNotificationObserver {
private Set<ILocalizationFileObserver> globalObservers = new HashSet<ILocalizationFileObserver>();
private final Map<LocalizationFileKey, Set<LocalizationFileRef>> observedFiles;
private final Map<LocalizationTypeFileKey, Set<LocalizationFile>> observedFiles;
private PathManager pm;
@ -137,24 +137,25 @@ public class LocalizationNotificationObserver {
* @param lf
*/
void addObservedFile(LocalizationFile lf) {
LocalizationFileKey key = new LocalizationFileKey(lf.getContext()
.getLocalizationType(), lf.getName());
LocalizationTypeFileKey key = new LocalizationTypeFileKey(lf
.getContext().getLocalizationType(), lf.getName());
LocalizationFileRef ref = new LocalizationFileRef(lf);
Set<LocalizationFileRef> lfList;
Set<LocalizationFile> lfList;
synchronized (observedFiles) {
lfList = observedFiles.get(key);
if (lfList == null) {
lfList = new HashSet<LocalizationFileRef>();
// add now so cleanUpRefs() cannot remove the set
lfList.add(ref);
lfList = new HashSet<LocalizationFile>();
observedFiles.put(key, lfList);
}
}
synchronized (lfList) {
lfList.add(ref);
if (lfList.add(lf) == false) {
// Contract between IPathManager/LocalizationFile will force
// this to never occur and will be developer mistake if so
throw new RuntimeException(
"Internal Error: Attempted to register LocalizationFile which had already been registered");
}
}
cleanUpRefs();
}
/**
@ -182,118 +183,79 @@ public class LocalizationNotificationObserver {
}
private LocalizationNotificationObserver() {
observedFiles = new ConcurrentHashMap<LocalizationFileKey, Set<LocalizationFileRef>>();
observedFiles = new ConcurrentHashMap<LocalizationTypeFileKey, Set<LocalizationFile>>();
pm = (PathManager) PathManagerFactory.getPathManager();
}
public void fileUpdateMessageRecieved(FileUpdatedMessage fum) {
public synchronized void fileUpdateMessageRecieved(FileUpdatedMessage fum) {
LocalizationType type = fum.getContext().getLocalizationType();
LocalizationLevel level = fum.getContext().getLocalizationLevel();
String contextName = fum.getContext().getContextName();
String filename = LocalizationUtil.getSplitUnique(fum.getFileName());
// Cleanup LocalizationFiles that have been GC'd
cleanUpRefs();
// Check if file update is older than latest file data
Collection<LocalizationFile> potentialFiles = getLocalizationFiles(
type, filename);
// Find exact match first:
for (LocalizationFile file : potentialFiles) {
if (file.getContext().equals(fum.getContext())) {
// exact match found, skip old updates (in case we have changed
// the file since this update)
try {
FileLocker.lock(this, file.file, Type.WRITE);
Date fileTS = file.getTimeStamp();
if (fileTS != null && fileTS.getTime() > fum.getTimeStamp()) {
// Update is older than latest file data, skip update as
// a newer one should be coming
return;
} else {
// Proceed with update process
processUpdate(fum, file);
break;
}
} finally {
FileLocker.unlock(this, file.file);
}
}
}
// If file deleted, delete from filesystem if non directory
if (fum.getChangeType() == FileChangeType.DELETED) {
File local = pm.adapter.getPath(fum.getContext(), filename);
if (local != null && local.isDirectory() == false && local.exists()) {
local.delete();
try {
FileLocker.lock(this, local, Type.WRITE);
local.delete();
} finally {
FileLocker.unlock(this, local);
}
}
}
// Response map, only request updated data once per file reference key
Map<Object, ListResponse> responseMap = new HashMap<Object, ListResponse>();
// Process other file, skipping context match that was processed above
for (LocalizationFile file : potentialFiles) {
if (file.getContext().equals(fum.getContext()) == false) {
processUpdate(fum, file);
}
}
// Split parts so we update sub directories
String[] parts = LocalizationUtil.splitUnique(filename);
for (int idx = parts.length - 1; idx > 0; --idx) {
String subpath = "";
for (int i = 0; i < idx; ++i) {
subpath += parts[i];
if (i < (idx - 1)) {
subpath += IPathManager.SEPARATOR;
}
}
do {
LocalizationFileKey key = new LocalizationFileKey(type, filename);
// Get the file references for the key to notify
Set<LocalizationFileRef> lfList;
synchronized (observedFiles) {
lfList = observedFiles.get(key);
Collection<LocalizationFile> files = getLocalizationFiles(type,
subpath);
for (LocalizationFile file : files) {
processUpdate(fum, file);
}
if (lfList != null) {
Set<LocalizationFileRef> copy;
synchronized (lfList) {
copy = new HashSet<LocalizationFileRef>(lfList);
}
// Flags so we only delete or request once
boolean requested = false;
for (LocalizationFileRef ref : copy) {
LocalizationFile lf = ref.get();
if (lf != null) {
// If null, means it was garbage collected, will be
// caught next update
int compVal = lf.getContext().getLocalizationLevel()
.compareTo(level);
if (compVal <= 0) {
boolean notify = false;
if (compVal < 0) {
// Different level, check our context name to
// make sure it matches update message
String ourContextName = pm.getContext(type,
level).getContextName();
if ((ourContextName == null && contextName == null)
|| (ourContextName != null && ourContextName
.equals(contextName))) {
notify = true;
}
}
// This file should be NOTIFEID based on update...
ListResponse resp = null;
if (fum.getContext().equals(lf.getContext())) {
// context perfectly matches, make sure we
// notify
notify = true;
// This file should be MODIFIED based on
// update...
if (lf.isDirectory() == false) {
resp = responseMap.get(ref.getKey());
if (resp == null) {
// Make sure we only request metadata
// once per file update
resp = getMetadata(lf);
responseMap.put(ref.getKey(), resp);
}
// Update file with new metadata
lf.update(resp);
// If we are still not a directoy and we
// should request the file, request it
if (lf.isDirectory() == false
&& lf.fileRequested && !requested) {
switch (fum.getChangeType()) {
case UPDATED:
case ADDED: {
requested = true;
lf.getFile();
break;
}
}
}
}
}
if (notify) {
// Notify file of change
lf.notifyObservers(fum);
}
}
}
}
}
int pos = filename.lastIndexOf(IPathManager.SEPARATOR);
if (pos > 0) {
filename = filename.substring(0, pos);
} else {
filename = "";
}
} while (!filename.isEmpty());
}
// Notify system wide listeners
synchronized (globalObservers) {
@ -303,6 +265,74 @@ public class LocalizationNotificationObserver {
}
}
private void processUpdate(FileUpdatedMessage fum, LocalizationFile file) {
LocalizationContext context = fum.getContext();
LocalizationLevel level = context.getLocalizationLevel();
LocalizationType type = context.getLocalizationType();
String contextName = context.getContextName();
int compVal = file.getContext().getLocalizationLevel().compareTo(level);
if (compVal <= 0) {
boolean notify = false;
if (compVal < 0) {
// Different level, check our context name to
// make sure it matches update message
String ourContextName = pm.getContext(type, level)
.getContextName();
if ((ourContextName == null && contextName == null)
|| (ourContextName != null && ourContextName
.equals(contextName))) {
notify = true;
}
}
if (fum.getContext().equals(file.getContext())) {
// context perfectly matches, make sure we
// notify
notify = true;
// This file should be MODIFIED based on
// update...
if (file.isDirectory() == false) {
// Update file with new metadata
file.update(getMetadata(file));
// If we are still not a directoy and we
// should request the file, request it
if (file.isDirectory() == false && file.fileRequested) {
switch (fum.getChangeType()) {
case UPDATED:
case ADDED: {
file.getFile();
break;
}
}
}
}
}
if (notify) {
// Notify file of change
file.notifyObservers(fum);
}
}
}
private Collection<LocalizationFile> getLocalizationFiles(
LocalizationType type, String fileName) {
Set<LocalizationFile> copy = new HashSet<LocalizationFile>();
Set<LocalizationFile> lfList;
synchronized (observedFiles) {
lfList = observedFiles.get(new LocalizationTypeFileKey(type,
fileName));
}
if (lfList != null) {
synchronized (lfList) {
copy = new HashSet<LocalizationFile>(lfList);
}
}
return copy;
}
/**
* @param lf
* @return
@ -328,33 +358,4 @@ public class LocalizationNotificationObserver {
return rval;
}
/**
* Clean up stale references
*/
private void cleanUpRefs() {
List<LocalizationFileKey> keysToRemove = new ArrayList<LocalizationFileKey>();
for (LocalizationFileKey key : observedFiles.keySet()) {
Set<LocalizationFileRef> refs = observedFiles.get(key);
if (refs == null || refs.size() == 0) {
keysToRemove.add(key);
} else {
synchronized (refs) {
List<LocalizationFileRef> toRemove = new ArrayList<LocalizationFileRef>();
for (LocalizationFileRef ref : refs) {
if (ref.get() == null) {
toRemove.add(ref);
}
}
refs.removeAll(toRemove);
if (refs.size() == 0) {
keysToRemove.add(key);
}
}
}
}
for (LocalizationFileKey key : keysToRemove) {
observedFiles.remove(key);
}
}
}

View file

@ -77,11 +77,36 @@ public class LockingFileOutputStream extends FileOutputStream {
@Override
public void close() throws IOException {
close(true);
}
/**
* Closes the output stream without unlocking the file. It is the
* responsibility of the caller to call {@link #unlock()} when they are done
* with the lock.
*/
public void closeWithoutUnlocking() throws IOException {
close(false);
}
/**
* Closes the stream, flag designates if lock will be released or not. By
* default {@link #close()} will unlock the file
*
* @param unlock
* @throws IOException
*/
private void close(boolean unlock) throws IOException {
try {
super.close();
} finally {
FileLocker.unlock(this, file);
if (unlock) {
unlock();
}
}
}
public void unlock() {
FileLocker.unlock(this, file);
}
}

View file

@ -223,22 +223,13 @@ public class PathManager implements IPathManager {
}
if (entry != null) {
for (ListResponse lr : entry) {
// A null File means the file can never exist, therefore we
// set
// the context/name key as a null file object to avoid
// requesting data about the non-existent file again
LocalizationFile file = new LocalizationFile();
File local = adapter.getPath(lr.context, name);
if (local != null) {
file = new LocalizationFile(this.adapter, lr.context,
local, lr.date, name, lr.checkSum,
lr.isDirectory, lr.existsOnServer,
lr.protectedLevel);
availableFiles.put(file.getContext(), file);
synchronized (fileCache) {
for (ListResponse lr : entry) {
LocalizationFile file = createFromResponse(lr);
if (file.isNull() == false) {
availableFiles.put(file.getContext(), file);
}
}
fileCache.put(new LocalizationFileKey(lr.fileName,
lr.context), file);
}
}
}
@ -255,6 +246,43 @@ public class PathManager implements IPathManager {
return rval.toArray(new LocalizationFile[rval.size()]);
}
/**
* Creates a LocalizationFile from a {@link ListResponse}, callers need to
* synchronize on fileCache before calling
*
* @param response
* @return LocalizationFile for response (never null), but be sure to check
* isNull() on file object
*/
private LocalizationFile createFromResponse(ListResponse response) {
// able to resolve file, lf will be set, check cache
LocalizationFileKey key = new LocalizationFileKey(response.fileName,
response.context);
LocalizationFile lf = fileCache.get(key);
if (lf != null && lf.isNull() == false) {
// Ensure latest data for file, will only be null if no File can be
// returned for path/context.
lf.update(response);
} else {
// Not in cache or null reference, see if file can be resolved
if (lf == null) {
// Default to null file if not from cache
lf = new LocalizationFile();
}
File file = this.adapter.getPath(response.context,
response.fileName);
if (file != null) {
// No cache file available and path is resolved, create
lf = new LocalizationFile(this.adapter, response.context, file,
response.date, response.fileName, response.checkSum,
response.isDirectory, response.existsOnServer,
response.protectedLevel);
}
fileCache.put(key, lf);
}
return lf;
}
/*
* (non-Javadoc)
*
@ -288,41 +316,13 @@ public class PathManager implements IPathManager {
ListResponse[] entries = this.adapter.listDirectory(contexts, name,
recursive, filesOnly);
for (ListResponse entry : entries) {
if (entry.isDirectory
|| matchesExtension(entry.fileName, filter)) {
File file = this.adapter.getPath(entry.context,
entry.fileName);
if (file != null) {
LocalizationFileKey key = new LocalizationFileKey(
entry.fileName, entry.context);
LocalizationFile lf = fileCache.get(key);
boolean fromCache = true;
if (lf == null) {
fromCache = false;
// No cache file available
lf = new LocalizationFile(this.adapter,
entry.context, file, entry.date,
entry.fileName, entry.checkSum,
entry.isDirectory, entry.existsOnServer,
entry.protectedLevel);
fileCache.put(key, lf);
}
if (lf.exists()) {
files.add(lf);
} else if (fromCache) {
// File from cache does not exist, check if entry
// from response exists. If so update cache with new
// file created from entry
lf = new LocalizationFile(this.adapter,
entry.context, file, entry.date,
entry.fileName, entry.checkSum,
entry.isDirectory, entry.existsOnServer,
entry.protectedLevel);
if (lf.exists()) {
files.add(lf);
fileCache.put(key, lf);
}
synchronized (fileCache) {
for (ListResponse entry : entries) {
if (entry.isDirectory
|| matchesExtension(entry.fileName, filter)) {
LocalizationFile file = createFromResponse(entry);
if (file.exists()) {
files.add(file);
}
}
}

View file

@ -22,10 +22,12 @@ package com.raytheon.uf.common.localization.msgs;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import com.raytheon.uf.common.localization.LocalizationContext;
import com.raytheon.uf.common.serialization.annotations.DynamicSerialize;
import com.raytheon.uf.common.serialization.annotations.DynamicSerializeElement;
/**
* Defines the delete localization response
@ -47,19 +49,40 @@ import com.raytheon.uf.common.serialization.annotations.DynamicSerialize;
@DynamicSerialize
public class DeleteUtilityResponse extends AbstractUtilityResponse {
@XmlAttribute
@DynamicSerializeElement
private long timeStamp;
public DeleteUtilityResponse() {
}
public DeleteUtilityResponse(LocalizationContext context, String errorText,
String fileName) {
String fileName, long timeStamp) {
super(context, fileName, errorText);
this.timeStamp = timeStamp;
}
/**
* @return the timeStamp
*/
public long getTimeStamp() {
return timeStamp;
}
/**
* @param timeStamp
* the timeStamp to set
*/
public void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
}
/*
* (non-Javadoc)
*
* @see com.raytheon.edex.msg.utility.AbstractUtilityResponse#getFormattedErrorMessage()
* @see com.raytheon.edex.msg.utility.AbstractUtilityResponse#
* getFormattedErrorMessage()
*/
@Override
public String getFormattedErrorMessage() {