This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit baee8db689e7118a0acf1c378c31b0458ac64918
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sat Aug 8 16:56:58 2026 -0400

    ClassGenerationAction cleanup
    
    caching Velocity engine and templates
---
 .../apache/cayenne/gen/ClassGenerationAction.java  | 76 ++++++++++-------
 .../apache/cayenne/gen/TemplateLocationTest.java   | 42 +++++-----
 .../ui/project/editor/datamap/cgen/CgenPanel.java  | 94 +++++++++++-----------
 .../cgen/templateeditor/TemplateEditor.java        |  2 +-
 4 files changed, 116 insertions(+), 98 deletions(-)

diff --git 
a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java 
b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
index 263e6c8c6..82a322b54 100644
--- 
a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
+++ 
b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
@@ -44,6 +44,8 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.EnumMap;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Properties;
 import java.util.stream.Collectors;
@@ -58,6 +60,8 @@ public class ClassGenerationAction {
     protected final Logger logger;
     private final ToolsUtilsFactory utilsFactory;
     private final MetadataUtils metadataUtils;
+    private final VelocityEngine velocityEngine;
+    private final Map<TemplateType, Template> templates;
 
     protected Context context;
 
@@ -71,6 +75,7 @@ public class ClassGenerationAction {
      * tools.toolbox = application
      * tools.application.myTool = com.mycompany.MyTool</pre>
      * Then the methods in the MyTool class will be available for use in the 
template like ${myTool.myMethod(arg)}
+     * <p>
      */
     public ClassGenerationAction(
             CgenConfiguration configuration,
@@ -83,6 +88,38 @@ public class ClassGenerationAction {
         this.metadataUtils = Objects.requireNonNull(metadataUtils);
         this.logger = Objects.requireNonNull(logger);
         this.context = createContext(configuration.getExternalToolConfig());
+        this.velocityEngine = createVelocityEngine(configuration);
+        this.templates = new EnumMap<>(TemplateType.class);
+    }
+
+    private static VelocityEngine createVelocityEngine(CgenConfiguration 
configuration) {
+
+        Properties props = new Properties();
+        props.put(RuntimeConstants.RESOURCE_LOADERS, "string,cayenne");
+
+        props.put("resource.loader.cayenne.class", 
ClassGeneratorResourceLoader.class.getName());
+        props.put("resource.loader.cayenne.cache", "false");
+        if (configuration.getRootPath() != null) {
+            props.put("resource.loader.cayenne.root", 
configuration.getRootPath());
+        }
+
+        props.put("resource.loader.string.class", 
StringResourceLoader.class.getName());
+        props.put("resource.loader.string.repository.name", 
CUSTOM_TEMPLATE_REPO);
+        // keep the repository local to this engine instead of sharing the 
global static registry
+        props.put("resource.loader.string.repository.static", "false");
+
+        StringResourceRepository repository = new 
StringResourceRepositoryImpl();
+        for (TemplateType type : TemplateType.values()) {
+            CgenTemplate template = configuration.getTemplateByType(type);
+            if (!template.isFile()) {
+                repository.putStringResource(template.getName(), 
template.getData());
+            }
+        }
+
+        VelocityEngine engine = new VelocityEngine();
+        engine.setApplicationAttribute(CUSTOM_TEMPLATE_REPO, repository);
+        engine.init(props);
+        return engine;
     }
 
     private static Context createContext(String toolConfigFile) {
@@ -90,7 +127,7 @@ public class ClassGenerationAction {
         if (System.getProperty("org.apache.velocity.tools") == null && 
toolConfigFile == null) {
             return new VelocityContext();
         }
-        
+
         ToolManager manager = new ToolManager(true, true);
         if (toolConfigFile != null) {
             FactoryConfiguration config = 
ConfigurationUtils.find(toolConfigFile);
@@ -148,9 +185,6 @@ public class ClassGenerationAction {
 
     /**
      * Adds entities to the internal entity list.
-     *
-     * @param entities collection
-     * @since 4.0 throws exception
      */
     public void addEntities(Collection<ObjEntity> entities) {
         if (entities != null) {
@@ -169,8 +203,7 @@ public class ClassGenerationAction {
     }
 
     /**
-     * @param dataMap to add to the list of artifacts to generate
-     * @since 5.0 replaces removed {@code addQueries()} method
+     * @since 5.0
      */
     public void addDataMap(DataMap dataMap) {
         // data map should be used only in ArtifactsGenerationMode.ALL
@@ -232,32 +265,13 @@ public class ClassGenerationAction {
         }
     }
 
+    /**
+     * Returns a parsed template for a given type. Templates are parsed on 
first use and cached, so that all the
+     * artifacts processed by this action share a single parsed copy.
+     */
     protected Template getTemplate(TemplateType type) {
-        Properties props = new Properties();
-        initVelocityProperties(props, type);
-        VelocityEngine velocityEngine = new VelocityEngine();
-        velocityEngine.init(props);
-        return 
velocityEngine.getTemplate(configuration.getTemplateByType(type).getName());
-    }
-
-    protected void initVelocityProperties(Properties props, TemplateType type) 
{
-        CgenTemplate template = configuration.getTemplateByType(type);
-        if (template.isFile()) {
-            props.put(RuntimeConstants.RESOURCE_LOADERS, "cayenne");
-            props.put("resource.loader.cayenne.class", 
ClassGeneratorResourceLoader.class.getName());
-            props.put("resource.loader.cayenne.cache", "false");
-            if (configuration.getRootPath() != null) {
-                props.put("resource.loader.cayenne.root", 
configuration.getRootPath());
-            }
-        } else {
-            props.put(RuntimeConstants.RESOURCE_LOADERS, "string");
-            props.put("resource.loader.string.class", 
StringResourceLoader.class.getName());
-            props.put("resource.loader.string.repository.name", 
CUSTOM_TEMPLATE_REPO);
-
-            StringResourceRepository repo = new StringResourceRepositoryImpl();
-            repo.putStringResource(template.getName(), template.getData());
-            StringResourceLoader.setRepository(CUSTOM_TEMPLATE_REPO, repo);
-        }
+        return templates.computeIfAbsent(type,
+                t -> 
velocityEngine.getTemplate(configuration.getTemplateByType(t).getName()));
     }
 
     /**
diff --git 
a/cayenne-cgen/src/test/java/org/apache/cayenne/gen/TemplateLocationTest.java 
b/cayenne-cgen/src/test/java/org/apache/cayenne/gen/TemplateLocationTest.java
index 8c6b28daa..f9ac5e355 100644
--- 
a/cayenne-cgen/src/test/java/org/apache/cayenne/gen/TemplateLocationTest.java
+++ 
b/cayenne-cgen/src/test/java/org/apache/cayenne/gen/TemplateLocationTest.java
@@ -34,44 +34,48 @@ public class TemplateLocationTest extends CgenCase {
     @TempDir
     public File tempFolder;
 
-    private CgenConfiguration cgenConfiguration;
-    private ClassGenerationAction action;
-    private TemplateType templateType;
+    private CgenConfiguration configuration;
 
     @BeforeEach
     public void setUp() {
-        cgenConfiguration = new CgenConfiguration();
-        action = new ClassGenerationAction(
-                cgenConfiguration,
-                getUnitTestInjector().getInstance(ToolsUtilsFactory.class),
-                getUnitTestInjector().getInstance(MetadataUtils.class),
-                NOPLogger.NOP_LOGGER);
-        templateType = TemplateType.ENTITY_SUBCLASS;
+        configuration = new CgenConfiguration();
     }
 
     @Test
     public void upperLevel() throws Exception {
         File subFolder = new File(tempFolder, "sub");
         subFolder.mkdir();
-        cgenConfiguration.setRootPath(subFolder.toPath());
+        configuration.setRootPath(subFolder.toPath());
         new File(tempFolder, "testTemplate.vm").createNewFile();
-        cgenConfiguration.setTemplate(new CgenTemplate("../testTemplate.vm", 
false, TemplateType.ENTITY_SUBCLASS));
-        assertNotNull(action.getTemplate(templateType));
+        configuration.setTemplate(new CgenTemplate("../testTemplate.vm", true, 
TemplateType.ENTITY_SUBCLASS));
+
+        
assertNotNull(createAction().getTemplate(TemplateType.ENTITY_SUBCLASS));
     }
 
     @Test
     public void sameLevel() throws Exception {
-        cgenConfiguration.setRootPath(tempFolder.toPath());
+        configuration.setRootPath(tempFolder.toPath());
         new File(tempFolder, "testTemplate2.vm").createNewFile();
-        cgenConfiguration.setTemplate(new CgenTemplate("testTemplate2.vm", 
false, TemplateType.ENTITY_SUBCLASS));
-        assertNotNull(action.getTemplate(templateType));
+        configuration.setTemplate(new CgenTemplate("testTemplate2.vm", true, 
TemplateType.ENTITY_SUBCLASS));
+
+        
assertNotNull(createAction().getTemplate(TemplateType.ENTITY_SUBCLASS));
     }
 
     @Test
     public void aboveLevel() throws Exception {
-        cgenConfiguration.setRootPath(Paths.get(tempFolder.getParent()));
+        configuration.setRootPath(Paths.get(tempFolder.getParent()));
         new File(tempFolder, "testTemplate3.vm").createNewFile();
-        cgenConfiguration.setTemplate(new CgenTemplate(tempFolder + 
"/testTemplate3.vm", false, TemplateType.ENTITY_SUBCLASS));
-        assertNotNull(action.getTemplate(templateType));
+        configuration.setTemplate(
+                new CgenTemplate(tempFolder + "/testTemplate3.vm", true, 
TemplateType.ENTITY_SUBCLASS));
+
+        
assertNotNull(createAction().getTemplate(TemplateType.ENTITY_SUBCLASS));
+    }
+
+    private ClassGenerationAction createAction() {
+        return new ClassGenerationAction(
+                configuration,
+                getUnitTestInjector().getInstance(ToolsUtilsFactory.class),
+                getUnitTestInjector().getInstance(MetadataUtils.class),
+                NOPLogger.NOP_LOGGER);
     }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenPanel.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenPanel.java
index e57bc1a40..92bddcc75 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenPanel.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenPanel.java
@@ -84,7 +84,7 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
 
     private CgenConfigList cgenConfigList;
     private Object currentClass;
-    private CgenConfiguration cgenConfiguration;
+    private CgenConfiguration configuration;
 
     private boolean initFromModel;
 
@@ -123,8 +123,8 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
         });
     }
 
-    public CgenConfiguration getCgenConfiguration() {
-        return cgenConfiguration;
+    public CgenConfiguration getConfiguration() {
+        return configuration;
     }
 
     public CgenConfigPanel getStandardModeController() {
@@ -212,14 +212,14 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
     }
 
     public void checkCgenConfigDirty() {
-        if (initFromModel || cgenConfiguration == null) {
+        if (initFromModel || configuration == null) {
             return;
         }
 
         DataMap map = session.getSelectedDataMap();
         CgenConfigList existingConfigurations = app.getMetaData().get(map, 
CgenConfigList.class);
         if (existingConfigurations == null) {
-            cgenConfigList.add(cgenConfiguration);
+            cgenConfigList.add(configuration);
             app.getMetaData().add(map, cgenConfigList);
         }
 
@@ -232,7 +232,7 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
         initCgenConfigurations(map);
         initConfigurationsComboBox();
         setConfiguration((String) configurationsComboBox.getSelectedItem());
-        cgenConfigPanel.initForm(cgenConfiguration);
+        cgenConfigPanel.initForm(configuration);
         classesSelector.startup();
         initFromModel = false;
         classesSelector.validate(classes);
@@ -242,19 +242,19 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
      * Builds a class generator for provided selections.
      */
     private void setConfiguration(String selectedConfig) {
-        cgenConfiguration = cgenConfigList.getByName(selectedConfig);
-        if (cgenConfiguration != null) {
+        configuration = cgenConfigList.getByName(selectedConfig);
+        if (configuration != null) {
             // Refresh entityArtifacts from the DataMap so any entities added 
since the config
             // was last loaded (e.g. by a dbimport run) are treated as 
included by default.
-            cgenConfiguration.resolveExcludedEntities();
-            cgenConfiguration.resolveExcludedEmbeddables();
-            addToSelectedEntities(cgenConfiguration.getEntities());
-            addToSelectedEmbeddables(cgenConfiguration.getEmbeddables());
+            configuration.resolveExcludedEntities();
+            configuration.resolveExcludedEmbeddables();
+            addToSelectedEntities(configuration.getEntities());
+            addToSelectedEmbeddables(configuration.getEmbeddables());
             return;
         }
 
         DataMap dataMap = session.getSelectedDataMap();
-        cgenConfiguration = createDefaultCgenConfiguration(dataMap);
+        configuration = createDefaultCgenConfiguration(dataMap);
         addToSelectedEntities(dataMap.getObjEntities()
                 .stream()
                 .map(Entity::getName)
@@ -341,7 +341,7 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
             }
             selectionModel.clearAll();
             setConfiguration((String) 
configurationsComboBox.getSelectedItem());
-            cgenConfigPanel.initForm(cgenConfiguration);
+            cgenConfigPanel.initForm(configuration);
             classesSelector.initBindings();
             classesSelector.validate(classes);
         });
@@ -359,7 +359,7 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
                 .addModule(binder -> 
binder.bind(DataChannelMetaData.class).toInstance(app.getMetaData()))
                 .create()
                 .getInstance(ClassGenerationActionFactory.class)
-                .createAction(cgenConfiguration, NOPLogger.NOP_LOGGER);
+                .createAction(configuration, NOPLogger.NOP_LOGGER);
 
         try {
             generator.prepareArtifacts();
@@ -400,7 +400,7 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
                 configurationsComboBox.getSelectedItem());
         if (name != null) {
             if (!cgenConfigList.isExist(name) && !name.isEmpty()) {
-                cgenConfiguration.setName(name);
+                configuration.setName(name);
                 
configurationsComboBox.removeItem(configurationsComboBox.getSelectedItem());
                 configurationsComboBox.addItem(name);
                 configurationsComboBox.setSelectedItem(name);
@@ -418,7 +418,7 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
                 JOptionPane.YES_NO_OPTION);
         if (result == JOptionPane.OK_OPTION) {
             if (configurationsComboBox.getItemCount() > 1) {
-                cgenConfigList.removeByName(cgenConfiguration.getName());
+                cgenConfigList.removeByName(configuration.getName());
                 
configurationsComboBox.removeItem(configurationsComboBox.getSelectedItem());
                 configurationsComboBox.setSelectedIndex(0);
             } else {
@@ -443,22 +443,22 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
     }
 
     private void updateArtifactGenerationMode(boolean selected) {
-        cgenConfiguration.setArtifactsGenerationMode(selected ? "all" : 
"entity");
+        configuration.setArtifactsGenerationMode(selected ? "all" : "entity");
         checkCgenConfigDirty();
     }
 
     private void updateEntities() {
-        if (cgenConfiguration != null) {
-            cgenConfiguration.getEntities().clear();
+        if (configuration != null) {
+            configuration.getEntities().clear();
             for (ObjEntity entity : 
selectionModel.getSelectedEntities(classes)) {
-                cgenConfiguration.loadEntity(entity);
+                configuration.loadEntity(entity);
             }
             // Keep excludedEntityArtifacts in sync so XML serialization stays 
correct
-            Collection<String> excluded = 
cgenConfiguration.getExcludedEntityArtifacts();
+            Collection<String> excluded = 
configuration.getExcludedEntityArtifacts();
             excluded.clear();
-            DataMap dataMap = cgenConfiguration.getDataMap();
+            DataMap dataMap = configuration.getDataMap();
             if (dataMap != null) {
-                Set<String> selected = cgenConfiguration.getEntities();
+                Set<String> selected = configuration.getEntities();
                 dataMap.getObjEntities().stream()
                         .map(ObjEntity::getName)
                         .filter(name -> !selected.contains(name))
@@ -469,17 +469,17 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
     }
 
     private void updateEmbeddables() {
-        if (cgenConfiguration != null) {
-            cgenConfiguration.getEmbeddables().clear();
+        if (configuration != null) {
+            configuration.getEmbeddables().clear();
             for (Embeddable embeddable : 
selectionModel.getSelectedEmbeddables(classes)) {
-                cgenConfiguration.loadEmbeddable(embeddable);
+                configuration.loadEmbeddable(embeddable);
             }
             // Keep excludedEmbeddableArtifacts in sync so XML serialization 
stays correct
-            Collection<String> excluded = 
cgenConfiguration.getExcludedEmbeddableArtifacts();
+            Collection<String> excluded = 
configuration.getExcludedEmbeddableArtifacts();
             excluded.clear();
-            DataMap dataMap = cgenConfiguration.getDataMap();
+            DataMap dataMap = configuration.getDataMap();
             if (dataMap != null) {
-                Set<String> selected = cgenConfiguration.getEmbeddables();
+                Set<String> selected = configuration.getEmbeddables();
                 dataMap.getEmbeddables().stream()
                         .map(Embeddable::getClassName)
                         .filter(name -> !selected.contains(name))
@@ -497,8 +497,8 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
     private void addEntity(DataMap dataMap, ObjEntity objEntity) {
         prepareClasses(dataMap);
         selectionModel.addSelectedEntity(objEntity.getName());
-        if (cgenConfiguration != null) {
-            cgenConfiguration.loadEntity(objEntity);
+        if (configuration != null) {
+            configuration.loadEntity(objEntity);
         }
         checkCgenConfigDirty();
     }
@@ -517,9 +517,9 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
             return;
         }
         selectionModel.renameSelectedEntity(entity.getDataMap(), oldName, 
newName);
-        if (cgenConfiguration != null) {
-            if (cgenConfiguration.getEntities().remove(oldName)) {
-                cgenConfiguration.getEntities().add(newName);
+        if (configuration != null) {
+            if (configuration.getEntities().remove(oldName)) {
+                configuration.getEntities().add(newName);
             }
         }
         checkCgenConfigDirty();
@@ -533,8 +533,8 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
     @Override
     public void objEntityRemoved(ObjEntityEvent e) {
         selectionModel.removeFromSelectedEntities(e.getEntity());
-        if (cgenConfiguration != null) {
-            cgenConfiguration.getEntities().remove(e.getEntity().getName());
+        if (configuration != null) {
+            configuration.getEntities().remove(e.getEntity().getName());
         }
         checkCgenConfigDirty();
     }
@@ -548,9 +548,9 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
             return;
         }
         selectionModel.renameSelectedEmbeddable(map, oldClassName, 
newClassName);
-        if (cgenConfiguration != null) {
-            if (cgenConfiguration.getEmbeddables().remove(oldClassName)) {
-                cgenConfiguration.getEmbeddables().add(newClassName);
+        if (configuration != null) {
+            if (configuration.getEmbeddables().remove(oldClassName)) {
+                configuration.getEmbeddables().add(newClassName);
             }
         }
         checkCgenConfigDirty();
@@ -561,8 +561,8 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
         prepareClasses(map);
         Embeddable embeddable = e.getEmbeddable();
         selectionModel.addSelectedEmbeddable(embeddable.getClassName());
-        if (cgenConfiguration != null) {
-            cgenConfiguration.loadEmbeddable(embeddable);
+        if (configuration != null) {
+            configuration.loadEmbeddable(embeddable);
         }
         checkCgenConfigDirty();
     }
@@ -570,8 +570,8 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
     @Override
     public void embeddableRemoved(EmbeddableEvent e, DataMap map) {
         selectionModel.removeFromSelectedEmbeddables(e.getEmbeddable());
-        if (cgenConfiguration != null) {
-            
cgenConfiguration.getEmbeddables().remove(e.getEmbeddable().getClassName());
+        if (configuration != null) {
+            
configuration.getEmbeddables().remove(e.getEmbeddable().getClassName());
         }
         checkCgenConfigDirty();
     }
@@ -585,7 +585,7 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
         // DbImportProjectSaver fires a pair of REMOVE / ADD events after 
merging new entities into a DataMap.
         // Re-initialize the panel so newly imported entities appear as 
included.
         DataMap map = e.getDataMap();
-        if (map != null && cgenConfiguration != null && map == 
cgenConfiguration.getDataMap()) {
+        if (map != null && configuration != null && map == 
configuration.getDataMap()) {
             initFromModel(map);
         }
     }
@@ -598,9 +598,9 @@ public class CgenPanel extends ProjectPanel implements 
ObjEntityListener, Embedd
      * Update cgen path on project save when no path is already set manually.
      */
     private void onProjectSaved(ProjectAfterSaveEvent e) {
-        if (cgenConfigPanel != null && cgenConfiguration != null) {
+        if (cgenConfigPanel != null && configuration != null) {
             cgenConfigPanel.getOutputFolder()
-                    .setText(cgenConfiguration.buildOutputPath().toString());
+                    .setText(configuration.buildOutputPath().toString());
         }
     }
 
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/templateeditor/TemplateEditor.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/templateeditor/TemplateEditor.java
index 910f60692..1f0acaebc 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/templateeditor/TemplateEditor.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/templateeditor/TemplateEditor.java
@@ -86,7 +86,7 @@ public class TemplateEditor extends AppFrame {
         super(app);
         this.parent = parent;
         this.templateType = templateType;
-        this.configuration = 
parent.getCodeGeneratorController().getCgenConfiguration();
+        this.configuration = 
parent.getCodeGeneratorController().getConfiguration();
         this.currentDataMap = configuration.getDataMap();
         this.artefactsConfigurator = setupArtefactConfigurator(templateType);
 

Reply via email to