package com.constellio.data.io.concurrent.filesystem; import java.util.ArrayList; import java.util.List; import org.apache.solr.common.cloud.SolrZkClient; import org.apache.zookeeper.KeeperException; import com.constellio.data.io.concurrent.data.DataWithVersion; import com.constellio.data.io.concurrent.exception.AtomicIOException; import com.constellio.data.io.concurrent.exception.FileNotFoundException; import com.constellio.data.io.concurrent.exception.OptimisticLockingException; public class ZookeeperAtomicFileSystem implements AtomicFileSystem{ private SolrZkClient zkClient; private boolean retryOnConnLoss; public ZookeeperAtomicFileSystem(String zkServerAddress, Integer zkClientTimeout){ zkClient = new SolrZkClient(zkServerAddress, zkClientTimeout); retryOnConnLoss = true; } @Override public synchronized DataWithVersion readData(String path) { org.apache.zookeeper.data.Stat zookeeprStat = new org.apache.zookeeper.data.Stat(); try { byte[] data = zkClient.getData(path, null, zookeeprStat, retryOnConnLoss); return new DataWithVersion(data, zookeeprStat.getVersion()); } catch (KeeperException.NoNodeException e){ throw new FileNotFoundException(e); } catch (KeeperException | InterruptedException e) { throw new AtomicIOException(e); } } @Override public synchronized DataWithVersion writeData(String path, DataWithVersion dataWithVersion) { try { Integer newVersion = -1; if (!exists(path)) zkClient.makePath(path, retryOnConnLoss); byte[] data = dataWithVersion.getData(); if (dataWithVersion.getVersion() == null) newVersion = zkClient.setData(path, data, retryOnConnLoss).getVersion(); else newVersion = zkClient.setData(path, data, (Integer)dataWithVersion.getVersion(), retryOnConnLoss).getVersion(); return new DataWithVersion(data, newVersion); } catch (KeeperException.BadVersionException e){ throw new OptimisticLockingException(e); } catch (KeeperException | InterruptedException e) { throw new AtomicIOException(e); } } @Override public synchronized void delete(String path, Object version) { try { if (!exists(path)) return; path = makePathCompatibleWithZookeeper(path); if (path.equals("/") || isDirectory(path)){ for (String subPath: list(path)) delete(subPath, version); } if (!path.equals("/")){ if (version == null) zkClient.delete(path, -1, retryOnConnLoss); else zkClient.delete(path, (Integer)version, retryOnConnLoss); } } catch (KeeperException e) { throw new OptimisticLockingException(e); } catch (InterruptedException e) { throw new AtomicIOException(e); } } @Override public synchronized List<String> list(String path) { try { path = makePathCompatibleWithZookeeper(path); List<String> children = zkClient.getChildren(path, null, retryOnConnLoss); if (children.size() == 0 && !isDirectory(path)){ return null; } List<String> subPathes = new ArrayList<>(); for (String child: children){ String augmentToPath = augmentToPath(path, child); subPathes.add(augmentToPath); } return subPathes; } catch (KeeperException | InterruptedException e) { if (e instanceof KeeperException.NoNodeException) return null; throw new AtomicIOException(e); } } private String augmentToPath(String path, String child) { if (path.equals("/")) return path + child; return path + "/" + child; } @Override public synchronized boolean exists(String path) { try { path = makePathCompatibleWithZookeeper(path); return zkClient.exists(path, retryOnConnLoss); } catch (KeeperException | InterruptedException e) { throw new AtomicIOException(e); } } private synchronized String makePathCompatibleWithZookeeper(String path){ //Zookeeper path should not terminate by '/' if (path.length() != 1 && path.endsWith("/")) path = path.substring(0, path.length() - 1); return path; } @Override public boolean isDirectory(String path) { path = makePathCompatibleWithZookeeper(path); boolean isDir = false; if (exists(path)){ DataWithVersion dirData = readData(path); isDir = dirData.getData() == null ; } return isDir; } @Override public boolean mkdirs(String path) { if (exists(path)) return false; try { zkClient.makePath(path, true); } catch (KeeperException | InterruptedException e) { throw new AtomicIOException(e); } return true; } @Override public void close(){ zkClient.close(); } }