This is an automated email from the ASF dual-hosted git repository. ntimofeev pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 63f738d220c9c81649002eaeeef1869dc7d82277 Merge: 4198c1bcd 5f9ab4f8b Author: Nikita Timofeev <stari...@gmail.com> AuthorDate: Thu Nov 10 11:15:59 2022 +0300 Merge remote-tracking branch 'parent/pr/463' into asf-master # Conflicts: # cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java .../cayenne/project/ConfigurationSourceSetter.java | 23 +- .../apache/cayenne/project/FileProjectSaver.java | 637 +++++++++++---------- .../org/apache/cayenne/project/ProjectModule.java | 3 + .../xml/XMLDataChannelDescriptorLoader.java | 1 - .../apache/cayenne/modeler/ProjectController.java | 65 +-- .../apache/cayenne/modeler/action/SaveAction.java | 2 + .../apache/cayenne/modeler/editor/DataMapView.java | 30 +- .../cayenne/modeler/event/ProjectSavedEvent.java | 37 ++ .../modeler/event/ProjectSavedListener.java | 29 + 9 files changed, 443 insertions(+), 384 deletions(-) diff --cc cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java index a83cf7693,11325634c..cd76e0a64 --- a/cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java +++ b/cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java @@@ -50,321 -50,322 +50,322 @@@ import java.util.List */ public class FileProjectSaver implements ProjectSaver { - @Inject - protected ConfigurationNameMapper nameMapper; - - protected ConfigurationNodeVisitor<Resource> resourceGetter; - protected ConfigurationNodeVisitor<Collection<ConfigurationNode>> saveableNodesGetter; - protected String fileEncoding; - - protected Collection<ProjectExtension> extensions; - protected SaverDelegate delegate; - - public FileProjectSaver(@Inject List<ProjectExtension> extensions) { - resourceGetter = new ConfigurationSourceGetter(); - saveableNodesGetter = new SaveableNodesGetter(); - - // this is not configurable yet... probably doesn't have to be - fileEncoding = "UTF-8"; - - this.extensions = extensions; - Collection<SaverDelegate> delegates = new ArrayList<>(extensions.size()); - for(ProjectExtension extension : extensions) { - delegates.add(extension.createSaverDelegate()); - } - delegate = new CompoundSaverDelegate(delegates); - } - - @Override - public String getSupportedVersion() { - return String.valueOf(Project.VERSION); - } - - @Override - public void save(Project project) { - save(project, project.getConfigurationResource(), true); - } - - @Override - public void saveAs(Project project, Resource baseDirectory) { - if (baseDirectory == null) { - throw new NullPointerException("Null 'baseDirectory'"); - } - save(project, baseDirectory, false); - } - - void save(Project project, Resource baseResource, boolean deleteOldResources) { - Collection<ConfigurationNode> nodes = project.getRootNode().acceptVisitor(saveableNodesGetter); - Collection<SaveUnit> units = new ArrayList<>(nodes.size()); - - delegate.setBaseDirectory(baseResource); - - for(ConfigurationNode node : nodes) { - String targetLocation = nameMapper.configurationLocation(node); - Resource targetResource = baseResource.getRelativeResource(targetLocation); - units.add(createSaveUnit(node, targetResource, null)); - - for(ProjectExtension extension : extensions) { - ConfigurationNodeVisitor<String> namingDelegate = extension.createNamingDelegate(); - SaverDelegate unitSaverDelegate = extension.createSaverDelegate(); - String fileName = node.acceptVisitor(namingDelegate); - if(fileName != null) { - // not null means that this should go to a separate file - targetResource = baseResource.getRelativeResource(fileName); - units.add(createSaveUnit(node, targetResource, unitSaverDelegate)); - } - } - } - - checkAccess(units); - - try { - saveToTempFiles(units); - saveCommit(units); - } finally { - clearTempFiles(units); - } - - try { - if (deleteOldResources) { - clearRenamedFiles(units); - - Collection<URL> unusedResources = project.getUnusedResources(); - for (SaveUnit unit : units) { - unusedResources.remove(unit.sourceConfiguration.getURL()); - } - deleteUnusedFiles(unusedResources); - } - } catch (IOException ex) { - throw new CayenneRuntimeException(ex); - } - - // I guess we should reset projects state regardless of the value of - // 'deleteOldResources' - project.getUnusedResources().clear(); - } - - SaveUnit createSaveUnit(ConfigurationNode node, Resource targetResource, SaverDelegate delegate) { - - SaveUnit unit = new SaveUnit(); - unit.node = node; - unit.delegate = delegate; - unit.sourceConfiguration = node.acceptVisitor(resourceGetter); - - if (unit.sourceConfiguration == null) { - unit.sourceConfiguration = targetResource; - } - - // attempt to convert targetResource to a File... if that fails, - // FileProjectSaver is not appropriate for handling a given project.. - - URL targetUrl = targetResource.getURL(); - - try { - unit.targetFile = Util.toFile(targetUrl); - } catch (IllegalArgumentException e) { - throw new CayenneRuntimeException("Can't save configuration to the following location: '%s'. " - + "Is this a valid file location?. (%s)", e, targetUrl, e.getMessage()); - } - - return unit; - } - - void checkAccess(Collection<SaveUnit> units) { - for (SaveUnit unit : units) { - - File targetFile = unit.targetFile; - - File parent = targetFile.getParentFile(); - if (!parent.exists()) { - if (!parent.mkdirs()) { - throw new CayenneRuntimeException("Error creating directory tree for '%s'", - parent.getAbsolutePath()); - } - } - - if (targetFile.isDirectory()) { - throw new CayenneRuntimeException("Target file '%s' is a directory", targetFile.getAbsolutePath()); - } - - if (targetFile.exists() && !targetFile.canWrite()) { - throw new CayenneRuntimeException("Can't write to file '%s'", targetFile.getAbsolutePath()); - } - - } - } - - void saveToTempFiles(Collection<SaveUnit> units) { - - for (SaveUnit unit : units) { - - String name = unit.targetFile.getName(); - if (name.length() < 3) { - name = "cayenne-project"; - } - - File parent = unit.targetFile.getParentFile(); - - try { - unit.targetTempFile = File.createTempFile(name, null, parent); - } catch (IOException e) { - throw new CayenneRuntimeException("Error creating temp file (%s)", e, e.getMessage()); - } - - if (unit.targetTempFile.exists()) { - unit.targetTempFile.delete(); - } - - try (PrintWriter printWriter = new PrintWriter(new OutputStreamWriter( - new FileOutputStream(unit.targetTempFile), fileEncoding))) { - saveToTempFile(unit, printWriter); - } catch (UnsupportedEncodingException e) { - throw new CayenneRuntimeException("Unsupported encoding '%s' (%s)", e, fileEncoding, e.getMessage()); - } catch (FileNotFoundException e) { - throw new CayenneRuntimeException("File not found '%s' (%s)", e, unit.targetTempFile.getAbsolutePath(), - e.getMessage()); - } - } - } - - void saveToTempFile(SaveUnit unit, PrintWriter printWriter) { - ConfigurationNodeVisitor<?> visitor; - if(unit.delegate == null) { - visitor = new ConfigurationSaver(printWriter, getSupportedVersion(), delegate); - } else { - XMLEncoder encoder = new XMLEncoder(printWriter, "\t", getSupportedVersion()); - encoder.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); - unit.delegate.setXMLEncoder(encoder); - visitor = unit.delegate; - } - - unit.node.acceptVisitor(visitor); - } - - void saveCommit(Collection<SaveUnit> units) { - - for (SaveUnit unit : units) { - - File targetFile = unit.targetFile; - - // Per CAY-2119, this is an ugly hack to force Windows to unlock the file that was previously locked by - // our process. Without it, the delete operation downstream would fail - System.gc(); - - if (targetFile.exists()) { - if (!targetFile.delete()) { - throw new CayenneRuntimeException("Unable to remove old master file '%s'", - targetFile.getAbsolutePath()); - } - } - - File tempFile = unit.targetTempFile; - if (!tempFile.renameTo(targetFile)) { - throw new CayenneRuntimeException("Unable to move '%s' to '%s'", tempFile.getAbsolutePath(), - targetFile.getAbsolutePath()); - } - - unit.targetTempFile = null; - try { - if(unit.delegate == null) { - unit.node.acceptVisitor(new ConfigurationSourceSetter(new URLResource(targetFile.toURI().toURL()))); - } - } catch (MalformedURLException e) { - throw new CayenneRuntimeException("Malformed URL for file '%s'", e, targetFile.getAbsolutePath()); - } - } - } - - private void clearTempFiles(Collection<SaveUnit> units) { - for (SaveUnit unit : units) { - - if (unit.targetTempFile != null && unit.targetTempFile.exists()) { - unit.targetTempFile.delete(); - unit.targetTempFile = null; - } - } - } - - private void clearRenamedFiles(Collection<SaveUnit> units) throws IOException { - for (SaveUnit unit : units) { - - if (unit.sourceConfiguration == null) { - continue; - } - - URL sourceUrl = unit.sourceConfiguration.getURL(); - File sourceFile; - try { - sourceFile = Util.toFile(sourceUrl); - } catch (IllegalArgumentException e) { - // ignore non-file configurations... - continue; - } - - if (!sourceFile.exists()) { - continue; - } - - // compare against ALL unit target files, not just the current - // unit... if the - // target matches, skip this file - boolean isTarget = false; - for (SaveUnit xunit : units) { - if (isFilesEquals(sourceFile, xunit.targetFile)) { - isTarget = true; - break; - } - } - - if (!isTarget) { - if (!sourceFile.delete()) { - throw new CayenneRuntimeException("Could not delete file '%s'", sourceFile.getCanonicalPath()); - } - } - } - } - - private boolean isFilesEquals(File firstFile, File secondFile) throws IOException { - boolean isFirstFileExists = firstFile.exists(); - boolean isSecondFileExists = secondFile.exists(); - - String firstFilePath = firstFile.getCanonicalPath(); - String secondFilePath = secondFile.getCanonicalPath(); - - return isFirstFileExists && isSecondFileExists && firstFilePath.equals(secondFilePath); - } - - private void deleteUnusedFiles(Collection<URL> unusedResources) throws IOException { - for (URL unusedResource : unusedResources) { - - File unusedFile; - try { - unusedFile = Util.toFile(unusedResource); - } catch (IllegalArgumentException e) { - // ignore non-file configurations... - continue; - } - - if (!unusedFile.exists()) { - continue; - } - - if (!unusedFile.delete()) { - throw new CayenneRuntimeException("Could not delete file '%s'", unusedFile.getCanonicalPath()); - } - - } - } - - static class SaveUnit { - - private ConfigurationNode node; - private SaverDelegate delegate; - - // source can be an abstract resource, but target is always a file... - private Resource sourceConfiguration; - private File targetFile; - private File targetTempFile; - - } + @Inject + protected ConfigurationNameMapper nameMapper; + + protected ConfigurationNodeVisitor<Resource> resourceGetter; + protected ConfigurationNodeVisitor<Collection<ConfigurationNode>> saveableNodesGetter; + protected String fileEncoding; + + protected Collection<ProjectExtension> extensions; + protected SaverDelegate delegate; + + public FileProjectSaver(@Inject List<ProjectExtension> extensions) { + resourceGetter = new ConfigurationSourceGetter(); + saveableNodesGetter = new SaveableNodesGetter(); + + // this is not configurable yet... probably doesn't have to be + fileEncoding = "UTF-8"; + + this.extensions = extensions; + Collection<SaverDelegate> delegates = new ArrayList<>(extensions.size()); + for (ProjectExtension extension : extensions) { + delegates.add(extension.createSaverDelegate()); + } + delegate = new CompoundSaverDelegate(delegates); + } + + @Override + public String getSupportedVersion() { + return String.valueOf(Project.VERSION); + } + + @Override + public void save(Project project) { + save(project, project.getConfigurationResource(), true); + } + + @Override + public void saveAs(Project project, Resource baseDirectory) { + if (baseDirectory == null) { + throw new NullPointerException("Null 'baseDirectory'"); + } + save(project, baseDirectory, false); + } + + void save(Project project, Resource baseResource, boolean deleteOldResources) { + Collection<ConfigurationNode> nodes = project.getRootNode().acceptVisitor(saveableNodesGetter); + Collection<SaveUnit> units = new ArrayList<>(nodes.size()); + + delegate.setBaseDirectory(baseResource); + + for (ConfigurationNode node : nodes) { + String targetLocation = nameMapper.configurationLocation(node); + Resource targetResource = baseResource.getRelativeResource(targetLocation); + units.add(createSaveUnit(node, targetResource, null)); + + for (ProjectExtension extension : extensions) { + ConfigurationNodeVisitor<String> namingDelegate = extension.createNamingDelegate(); + SaverDelegate unitSaverDelegate = extension.createSaverDelegate(); + String fileName = node.acceptVisitor(namingDelegate); + if (fileName != null) { + // not null means that this should go to a separate file + targetResource = baseResource.getRelativeResource(fileName); + units.add(createSaveUnit(node, targetResource, unitSaverDelegate)); + } + } + } + + checkAccess(units); + + try { + saveToTempFiles(units); + saveCommit(units); + } finally { + clearTempFiles(units); + } + + try { + if (deleteOldResources) { + clearRenamedFiles(units); + + Collection<URL> unusedResources = project.getUnusedResources(); + for (SaveUnit unit : units) { + unusedResources.remove(unit.sourceConfiguration.getURL()); + } + deleteUnusedFiles(unusedResources); + } + } catch (IOException ex) { + throw new CayenneRuntimeException(ex); + } + + // I guess we should reset projects state regardless of the value of + // 'deleteOldResources' + project.getUnusedResources().clear(); + } + + SaveUnit createSaveUnit(ConfigurationNode node, Resource targetResource, SaverDelegate delegate) { + + SaveUnit unit = new SaveUnit(); + unit.node = node; + unit.delegate = delegate; + unit.sourceConfiguration = node.acceptVisitor(resourceGetter); + + if (unit.sourceConfiguration == null) { + unit.sourceConfiguration = targetResource; + } + + // attempt to convert targetResource to a File... if that fails, + // FileProjectSaver is not appropriate for handling a given project.. + + URL targetUrl = targetResource.getURL(); + + try { + unit.targetFile = Util.toFile(targetUrl); + } catch (IllegalArgumentException e) { + throw new CayenneRuntimeException("Can't save configuration to the following location: '%s'. " + + "Is this a valid file location?. (%s)", e, targetUrl, e.getMessage()); + } + + return unit; + } + + void checkAccess(Collection<SaveUnit> units) { + for (SaveUnit unit : units) { + + File targetFile = unit.targetFile; + + File parent = targetFile.getParentFile(); + if (!parent.exists()) { + if (!parent.mkdirs()) { + throw new CayenneRuntimeException("Error creating directory tree for '%s'", + parent.getAbsolutePath()); + } + } + + if (targetFile.isDirectory()) { + throw new CayenneRuntimeException("Target file '%s' is a directory", targetFile.getAbsolutePath()); + } + + if (targetFile.exists() && !targetFile.canWrite()) { + throw new CayenneRuntimeException("Can't write to file '%s'", targetFile.getAbsolutePath()); + } + + } + } + + void saveToTempFiles(Collection<SaveUnit> units) { + + for (SaveUnit unit : units) { + + String name = unit.targetFile.getName(); + if (name.length() < 3) { + name = "cayenne-project"; + } + + File parent = unit.targetFile.getParentFile(); + + try { + unit.targetTempFile = File.createTempFile(name, null, parent); + } catch (IOException e) { + throw new CayenneRuntimeException("Error creating temp file (%s)", e, e.getMessage()); + } + + if (unit.targetTempFile.exists()) { + unit.targetTempFile.delete(); + } + + try (PrintWriter printWriter = new PrintWriter(new OutputStreamWriter( + new FileOutputStream(unit.targetTempFile), fileEncoding))) { + saveToTempFile(unit, printWriter); + } catch (UnsupportedEncodingException e) { + throw new CayenneRuntimeException("Unsupported encoding '%s' (%s)", e, fileEncoding, e.getMessage()); + } catch (FileNotFoundException e) { + throw new CayenneRuntimeException("File not found '%s' (%s)", e, unit.targetTempFile.getAbsolutePath(), + e.getMessage()); + } + } + } + + void saveToTempFile(SaveUnit unit, PrintWriter printWriter) { + ConfigurationNodeVisitor<?> visitor; + if (unit.delegate == null) { + visitor = new ConfigurationSaver(printWriter, getSupportedVersion(), delegate); + } else { + XMLEncoder encoder = new XMLEncoder(printWriter, "\t", getSupportedVersion()); + encoder.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); + unit.delegate.setXMLEncoder(encoder); + visitor = unit.delegate; + } + + unit.node.acceptVisitor(visitor); + } + + void saveCommit(Collection<SaveUnit> units) { + + for (SaveUnit unit : units) { + + File targetFile = unit.targetFile; + + // Per CAY-2119, this is an ugly hack to force Windows to unlock the file that was previously locked by + // our process. Without it, the delete operation downstream would fail + System.gc(); + + if (targetFile.exists()) { + if (!targetFile.delete()) { + throw new CayenneRuntimeException("Unable to remove old master file '%s'", + targetFile.getAbsolutePath()); + } + } + + File tempFile = unit.targetTempFile; + if (!tempFile.renameTo(targetFile)) { + throw new CayenneRuntimeException("Unable to move '%s' to '%s'", tempFile.getAbsolutePath(), + targetFile.getAbsolutePath()); + } + + unit.targetTempFile = null; + try { + if (unit.delegate == null) { + URLResource targetUrlResource = new URLResource(targetFile.toURI().toURL()); + unit.node.acceptVisitor(new ConfigurationSourceSetter(targetUrlResource, nameMapper)); + } + } catch (MalformedURLException e) { + throw new CayenneRuntimeException("Malformed URL for file '%s'", e, targetFile.getAbsolutePath()); + } + } + } + + private void clearTempFiles(Collection<SaveUnit> units) { + for (SaveUnit unit : units) { + + if (unit.targetTempFile != null && unit.targetTempFile.exists()) { + unit.targetTempFile.delete(); + unit.targetTempFile = null; + } + } + } + + private void clearRenamedFiles(Collection<SaveUnit> units) throws IOException { + for (SaveUnit unit : units) { + + if (unit.sourceConfiguration == null) { + continue; + } + + URL sourceUrl = unit.sourceConfiguration.getURL(); + File sourceFile; + try { + sourceFile = Util.toFile(sourceUrl); + } catch (IllegalArgumentException e) { + // ignore non-file configurations... + continue; + } + + if (!sourceFile.exists()) { + continue; + } + + // compare against ALL unit target files, not just the current + // unit... if the + // target matches, skip this file + boolean isTarget = false; + for (SaveUnit xunit : units) { + if (isFilesEquals(sourceFile, xunit.targetFile)) { + isTarget = true; + break; + } + } + + if (!isTarget) { + if (!sourceFile.delete()) { + throw new CayenneRuntimeException("Could not delete file '%s'", sourceFile.getCanonicalPath()); + } + } + } + } + + private boolean isFilesEquals(File firstFile, File secondFile) throws IOException { + boolean isFirstFileExists = firstFile.exists(); + boolean isSecondFileExists = secondFile.exists(); + + String firstFilePath = firstFile.getCanonicalPath(); + String secondFilePath = secondFile.getCanonicalPath(); + + return isFirstFileExists && isSecondFileExists && firstFilePath.equals(secondFilePath); + } + + private void deleteUnusedFiles(Collection<URL> unusedResources) throws IOException { + for (URL unusedResource : unusedResources) { + + File unusedFile; + try { + unusedFile = Util.toFile(unusedResource); + } catch (IllegalArgumentException e) { + // ignore non-file configurations... + continue; + } + + if (!unusedFile.exists()) { + continue; + } + + if (!unusedFile.delete()) { + throw new CayenneRuntimeException("Could not delete file '%s'", unusedFile.getCanonicalPath()); + } + + } + } + - class SaveUnit { ++ static class SaveUnit { + + private ConfigurationNode node; + private SaverDelegate delegate; + + // source can be an abstract resource, but target is always a file... + private Resource sourceConfiguration; + private File targetFile; + private File targetTempFile; + + } }