This commit is contained in:
candi
2025-09-04 04:43:31 +08:00
parent 2723b0ddd3
commit 212c19d40a
136 changed files with 96 additions and 766 deletions

View File

@@ -0,0 +1,111 @@
/*******************************************************************************
* Copyright (c) 2009, 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - initial API and implementation and/or initial documentation
*/
package org.eclipse.paho.mqttv5.client.persist;
import java.util.Enumeration;
import java.util.Hashtable;
import org.eclipse.paho.mqttv5.client.MqttClientPersistence;
import org.eclipse.paho.mqttv5.common.MqttPersistable;
import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
/**
* Persistence that uses memory
*
* In cases where reliability is not required across client or device
* restarts memory this memory persistence can be used. In cases where
* reliability is required like when clean session is set to false
* then a non-volatile form of persistence should be used.
*
*/
public class MemoryPersistence implements MqttClientPersistence {
private Hashtable<String, MqttPersistable> data;
/* (non-Javadoc)
* @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#close()
*/
public void close() throws MqttPersistenceException {
if (data != null) {
data.clear();
}
}
/* (non-Javadoc)
* @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#keys()
*/
public Enumeration<String> keys() throws MqttPersistenceException {
checkIsOpen();
return data.keys();
}
/* (non-Javadoc)
* @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#get(java.lang.String)
*/
public MqttPersistable get(String key) throws MqttPersistenceException {
checkIsOpen();
return (MqttPersistable)data.get(key);
}
/* (non-Javadoc)
* @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#open(java.lang.String, java.lang.String)
*/
public void open(String clientId) throws MqttPersistenceException {
this.data = new Hashtable<String, MqttPersistable>();
}
/* (non-Javadoc)
* @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#put(java.lang.String, org.eclipse.paho.mqttv5.client.MqttPersistable)
*/
public void put(String key, MqttPersistable persistable) throws MqttPersistenceException {
checkIsOpen();
data.put(key, persistable);
}
/* (non-Javadoc)
* @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#remove(java.lang.String)
*/
public void remove(String key) throws MqttPersistenceException {
checkIsOpen();
data.remove(key);
}
/* (non-Javadoc)
* @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#clear()
*/
public void clear() throws MqttPersistenceException {
checkIsOpen();
data.clear();
}
/* (non-Javadoc)
* @see org.eclipse.paho.mqttv5.client.MqttClientPersistence#containsKey(java.lang.String)
*/
public boolean containsKey(String key) throws MqttPersistenceException {
checkIsOpen();
return data.containsKey(key);
}
/**
* Checks whether the persistence has been opened.
* @throws MqttPersistenceException if the persistence has not been opened.
*/
private void checkIsOpen() throws MqttPersistenceException {
if (data == null) {
throw new MqttPersistenceException();
}
}
}

View File

@@ -0,0 +1,300 @@
/*******************************************************************************
* Copyright (c) 2009, 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* https://www.eclipse.org/legal/epl-2.0
* and the Eclipse Distribution License is available at
* https://www.eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Dave Locke - initial API and implementation and/or initial documentation
*/
package org.eclipse.paho.mqttv5.client.persist;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;
import org.eclipse.paho.mqttv5.client.MqttClientPersistence;
import org.eclipse.paho.mqttv5.client.internal.FileLock;
import org.eclipse.paho.mqttv5.client.internal.MqttPersistentData;
import org.eclipse.paho.mqttv5.common.MqttPersistable;
import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
/**
* An implementation of the {@link MqttClientPersistence} interface that provides
* file based persistence.
*
* A directory is specified when the Persistence object is created. When the persistence
* is then opened (see {@link #open(String)}), a sub-directory is made beneath the base
* for this client ID and connection key. This allows one persistence base directory
* to be shared by multiple clients.
*
* The sub-directory's name is created from a concatenation of the client ID and connection key
* with any instance of '/', '\\', ':' or ' ' removed.
*/
public class MqttDefaultFilePersistence implements MqttClientPersistence {
private static final String MESSAGE_FILE_EXTENSION = ".msg";
private static final String MESSAGE_BACKUP_FILE_EXTENSION = ".bup";
private static final String LOCK_FILENAME = ".lck";
private File dataDir;
private File clientDir = null;
private FileLock fileLock = null;
//TODO
private static FilenameFilter FILENAME_FILTER;
private static FilenameFilter getFilenameFilter(){
if(FILENAME_FILTER == null){
FILENAME_FILTER = new PersistenceFileNameFilter(MESSAGE_FILE_EXTENSION);
}
return FILENAME_FILTER;
}
public MqttDefaultFilePersistence() { //throws MqttPersistenceException {
this(System.getProperty("user.dir"));
}
/**
* Create an file-based persistent data store within the specified directory.
* @param directory the directory to use.
*/
public MqttDefaultFilePersistence(String directory) { //throws MqttPersistenceException {
dataDir = new File(directory);
}
public void open(String clientId) throws MqttPersistenceException {
if (dataDir.exists() && !dataDir.isDirectory()) {
throw new MqttPersistenceException();
} else if (!dataDir.exists() ) {
if (!dataDir.mkdirs()) {
throw new MqttPersistenceException();
}
}
if (!dataDir.canWrite()) {
throw new MqttPersistenceException();
}
StringBuffer keyBuffer = new StringBuffer();
for (int i=0;i<clientId.length();i++) {
char c = clientId.charAt(i);
if (isSafeChar(c)) {
keyBuffer.append(c);
}
}
synchronized (this) {
if (clientDir == null) {
String key = keyBuffer.toString();
clientDir = new File(dataDir, key);
if (!clientDir.exists()) {
clientDir.mkdir();
}
}
try {
//If lock was previously acquired, release before requesting a new one
if(fileLock != null){
fileLock.release();
}
fileLock = new FileLock(clientDir, LOCK_FILENAME);
} catch (Exception e) {
// TODO - This shouldn't be here according to the interface
// See https://github.com/eclipse/paho.mqtt.java/issues/178
//throw new MqttPersistenceException(MqttPersistenceException.REASON_CODE_PERSISTENCE_IN_USE);
}
// Scan the directory for .backup files. These will
// still exist if the JVM exited during addMessage, before
// the new message was written to disk and the backup removed.
restoreBackups(clientDir);
}
}
/**
* Checks whether the persistence has been opened.
* @throws MqttPersistenceException if the persistence has not been opened.
*/
private void checkIsOpen() throws MqttPersistenceException {
if (clientDir == null) {
throw new MqttPersistenceException();
}
}
public void close() throws MqttPersistenceException {
synchronized (this) {
// checkIsOpen();
if (fileLock != null) {
fileLock.release();
}
if (getFiles().length == 0) {
clientDir.delete();
}
clientDir = null;
}
}
/**
* Writes the specified persistent data to the previously specified persistence directory.
* This method uses a safe overwrite policy to ensure IO errors do not lose messages.
* @param message The {@link MqttPersistable} message to be persisted
* @throws MqttPersistenceException if an exception occurs whilst persisting the message
*/
public void put(String key, MqttPersistable message) throws MqttPersistenceException {
checkIsOpen();
File file = new File(clientDir, key+MESSAGE_FILE_EXTENSION);
File backupFile = new File(clientDir, key+MESSAGE_FILE_EXTENSION+MESSAGE_BACKUP_FILE_EXTENSION);
if (file.exists()) {
// Backup the existing file so the overwrite can be rolled-back
boolean result = file.renameTo(backupFile);
if (!result) {
backupFile.delete();
file.renameTo(backupFile);
}
}
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(message.getHeaderBytes(), message.getHeaderOffset(), message.getHeaderLength());
if (message.getPayloadBytes()!=null) {
fos.write(message.getPayloadBytes(), message.getPayloadOffset(), message.getPayloadLength());
}
fos.getFD().sync();
fos.close();
if (backupFile.exists()) {
// The write has completed successfully, delete the backup
backupFile.delete();
}
}
catch (IOException ex) {
throw new MqttPersistenceException(ex);
}
finally {
if (backupFile.exists()) {
// The write has failed - restore the backup
boolean result = backupFile.renameTo(file);
if (!result) {
file.delete();
backupFile.renameTo(file);
}
}
}
}
public MqttPersistable get(String key) throws MqttPersistenceException {
checkIsOpen();
MqttPersistable result;
try {
File file = new File(clientDir, key+MESSAGE_FILE_EXTENSION);
FileInputStream fis = new FileInputStream(file);
int size = fis.available();
byte[] data = new byte[size];
int read = 0;
while (read<size) {
read += fis.read(data,read,size-read);
}
fis.close();
result = new MqttPersistentData(key, data, 0, data.length, null, 0, 0);
}
catch(IOException ex) {
throw new MqttPersistenceException(ex);
}
return result;
}
/**
* Deletes the data with the specified key from the previously specified persistence directory.
*/
public void remove(String key) throws MqttPersistenceException {
checkIsOpen();
File file = new File(clientDir, key+MESSAGE_FILE_EXTENSION);
if (file.exists()) {
file.delete();
}
}
/**
* Returns all of the persistent data from the previously specified persistence directory.
* @return all of the persistent data from the persistence directory.
* @throws MqttPersistenceException if an exception is thrown whilst getting the keys
*/
public Enumeration<String> keys() throws MqttPersistenceException {
checkIsOpen();
File[] files = getFiles();
Vector<String> result = new Vector<String>(files.length);
for (File file : files) {
String filename = file.getName();
String key = filename.substring(0, filename.length() - MESSAGE_FILE_EXTENSION.length());
result.addElement(key);
}
return result.elements();
}
private File[] getFiles() throws MqttPersistenceException {
checkIsOpen();
File[] files = clientDir.listFiles(getFilenameFilter());
if (files == null) {
throw new MqttPersistenceException();
}
return files;
}
private boolean isSafeChar(char c) {
return Character.isJavaIdentifierPart(c) || c=='-';
}
/**
* Identifies any backup files in the specified directory and restores them
* to their original file. This will overwrite any existing file of the same
* name. This is safe as a stray backup file will only exist if a problem
* occurred whilst writing to the original file.
* @param dir The directory in which to scan and restore backups
*/
private void restoreBackups(File dir) throws MqttPersistenceException {
File[] files = dir.listFiles(new PersistenceFileFilter(MESSAGE_BACKUP_FILE_EXTENSION));
if (files == null) {
throw new MqttPersistenceException();
}
for (File file : files) {
File originalFile = new File(dir, file.getName().substring(0, file.getName().length() - MESSAGE_BACKUP_FILE_EXTENSION.length()));
boolean result = file.renameTo(originalFile);
if (!result) {
originalFile.delete();
file.renameTo(originalFile);
}
}
}
public boolean containsKey(String key) throws MqttPersistenceException {
checkIsOpen();
File file = new File(clientDir, key+MESSAGE_FILE_EXTENSION);
return file.exists();
}
public void clear() throws MqttPersistenceException {
checkIsOpen();
File[] files = getFiles();
for (File file : files) {
file.delete();
}
clientDir.delete();
}
}

View File

@@ -0,0 +1,18 @@
package org.eclipse.paho.mqttv5.client.persist;
import java.io.File;
import java.io.FileFilter;
public class PersistenceFileFilter implements FileFilter{
private final String fileExtension;
public PersistenceFileFilter(String fileExtension){
this.fileExtension = fileExtension;
}
public boolean accept(File pathname) {
return pathname.getName().endsWith(fileExtension);
}
}

View File

@@ -0,0 +1,18 @@
package org.eclipse.paho.mqttv5.client.persist;
import java.io.File;
import java.io.FilenameFilter;
public class PersistenceFileNameFilter implements FilenameFilter{
private final String fileExtension;
public PersistenceFileNameFilter(String fileExtension){
this.fileExtension = fileExtension;
}
public boolean accept(File dir, String name) {
return name.endsWith(fileExtension);
}
}