package ilarkesto.persistence; import ilarkesto.base.time.Date; import ilarkesto.core.logging.Log; import ilarkesto.fp.Predicate; import ilarkesto.io.IO; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class FileEntityStore implements EntityStore { private static final Log LOG = Log.get(FileEntityStore.class); // --- dependencies --- private Serializer beanSerializer; public void setBeanSerializer(Serializer beanSerializer) { this.beanSerializer = beanSerializer; } private EntityfilePreparator entityfilePreparator; public void setEntityfilePreparator(EntityfilePreparator entityfilePreparator) { this.entityfilePreparator = entityfilePreparator; } private String dir; public void setDir(String dir) { this.dir = dir; } private String backupDir; public void setBackupDir(String backupDir) { this.backupDir = backupDir; } // --- --- public synchronized void save(AEntity entity) { // entity.setLastModified(DateAndTime.now()); String alias = aliases.get(entity.getClass()); File tmpFile = new File(dir + "/tmp/" + entity.getId() + ".xml"); if (!tmpFile.getParentFile().exists()) { tmpFile.getParentFile().mkdirs(); } // save BufferedOutputStream out; try { out = new BufferedOutputStream(new FileOutputStream(tmpFile)); } catch (IOException ex) { throw new RuntimeException(ex); } beanSerializer.serialize(entity, out); try { out.close(); } catch (IOException ex) { throw new RuntimeException(ex); } File file = new File(dir + "/" + alias + "/" + entity.getId() + ".xml"); // backup if (file.exists() && !(entity instanceof BackupHostile)) { backup(file, entity.getDao().getEntityName()); } IO.move(tmpFile, file, true); getDao(entity.getClass()).put(entity.getId(), entity); LOG.debug("Entity saved:", entity, "->", file.getPath()); } public synchronized void delete(AEntity entity) { String alias = aliases.get(entity.getClass()); File file = new File(dir + "/" + alias + "/" + entity.getId() + ".xml"); // backup if (file.exists() && !(entity instanceof BackupHostile)) { backup(file, entity.getDao().getEntityName()); } // delete if (!file.delete() && file.exists()) throw new RuntimeException("Deleting entity file failed: " + file.getAbsolutePath()); getDao(entity.getClass()).remove(entity.getId()); LOG.debug("Entity deleted:", file.getPath(), entity.getClass().getSimpleName(), entity); } private Map<String, AEntity> getDao(Class<? extends AEntity> type) { Map<String, AEntity> dao = data.get(type); if (dao == null) { throw new RuntimeException("Unknown entity type: " + type); } return dao; } @Override public AEntity getById(String id) { for (Map.Entry<Class<AEntity>, Map<String, AEntity>> daoEntry : data.entrySet()) { AEntity entity = daoEntry.getValue().get(id); if (entity != null) return entity; } return null; } public synchronized AEntity getEntity(Predicate<Class> typeFilter, Predicate<AEntity> entityFilter) { for (Map.Entry<Class<AEntity>, Map<String, AEntity>> daoEntry : data.entrySet()) { if (typeFilter != null && !typeFilter.test(daoEntry.getKey())) continue; for (AEntity entity : daoEntry.getValue().values()) { if (entityFilter.test(entity)) return entity; } } return null; } @Override public List<AEntity> getByIds(Collection<String> ids) { List<AEntity> result = new ArrayList<AEntity>(ids.size()); for (Map.Entry<Class<AEntity>, Map<String, AEntity>> entry : data.entrySet()) { Map<String, AEntity> entites = entry.getValue(); for (String id : ids) { AEntity entity = entites.get(id); if (entity != null) result.add(entity); } } return result; } public synchronized Set<AEntity> getEntities(Predicate<Class> typeFilter, Predicate<AEntity> entityFilter) { Set<AEntity> result = new HashSet<AEntity>(); for (Map.Entry<Class<AEntity>, Map<String, AEntity>> entry : data.entrySet()) { if (typeFilter != null && !typeFilter.test(entry.getKey())) continue; if (entityFilter == null) { result.addAll(entry.getValue().values()); } else { for (AEntity entity : entry.getValue().values()) { if (entityFilter.test(entity)) result.add(entity); } } } return result; } public synchronized int getEntitiesCount(Predicate<Class> typeFilter, Predicate<AEntity> entityFilter) { int result = 0; for (Map.Entry<Class<AEntity>, Map<String, AEntity>> entry : data.entrySet()) { if (typeFilter != null && !typeFilter.test(entry.getKey())) continue; if (entityFilter == null) { result += entry.getValue().size(); } else { for (AEntity entity : entry.getValue().values()) { if (entityFilter.test(entity)) result++; } } } return result; } private Map<Class, String> aliases = new HashMap<Class, String>(); private Map<Class<AEntity>, Map<String, AEntity>> data = new HashMap<Class<AEntity>, Map<String, AEntity>>(); public void setAlias(String alias, Class cls) { aliases.put(cls, alias); beanSerializer.setAlias(alias, cls); } public void load(Class<? extends AEntity> cls, String alias) { aliases.put(cls, alias); Map<String, AEntity> entities = new HashMap<String, AEntity>(); data.put((Class<AEntity>) cls, entities); beanSerializer.setAlias(alias, cls); File f = new File(dir + "/" + alias); LOG.info("Loading entities:", alias); // if (!f.exists()) { // LOG.warn("Store directory does not exist. creating:", dir); // if (!f.mkdirs()) throw new RuntimeException("Creating store directory failed: " + dir); // } int count = 0; File[] files = f.listFiles(); if (files != null) { for (int i = 0; i < files.length; i++) { try { if (loadObject(files[i], entities, cls, alias)) count++; } catch (Throwable ex) { throw new RuntimeException("Loading object from " + files[i] + " failed", ex); } } } // LOG.info(" Loaded entities:", alias, count); } private boolean loadObject(File file, Map<String, AEntity> entities, Class type, String alias) { String name = file.getName(); if (!name.endsWith(".xml")) { LOG.warn("Unsupported file. Skipping:", name); return false; } if (entityfilePreparator != null) entityfilePreparator.prepareEntityfile(file, type, alias); BufferedInputStream in; try { in = new BufferedInputStream(new FileInputStream(file)); } catch (FileNotFoundException ex) { throw new RuntimeException(ex); } AEntity entity = (AEntity) beanSerializer.deserialize(in); entities.put(entity.getId(), entity); try { in.close(); } catch (IOException ex) { throw new RuntimeException(ex); } return true; } private void backup(File src, String type) { if (src.isDirectory()) throw new RuntimeException("sorry, backing up directories is not implemented yet."); String destinationPath = backupDir + "/" + Date.today() + "/" + type + "/"; File dst = new File(destinationPath + src.getName()); for (int i = 2; dst.exists(); i++) { dst = new File(destinationPath + i + "_" + src.getName()); } // LOG.debug("Backing up", src.getPath(), "to", dst.getPath()); IO.copyFile(src.getPath(), dst.getPath()); } }