diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/exception/DictionaryException.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/exception/DictionaryException.java new file mode 100644 index 00000000..92b2f002 --- /dev/null +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/exception/DictionaryException.java @@ -0,0 +1,11 @@ +package be.orbinson.aem.dictionarytranslator.exception; + +public class DictionaryException extends Exception { + public DictionaryException(String message) { + super(message); + } + + public DictionaryException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/exception/DictionaryTranslatorException.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/exception/DictionaryTranslatorException.java deleted file mode 100644 index 35f23a53..00000000 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/exception/DictionaryTranslatorException.java +++ /dev/null @@ -1,7 +0,0 @@ -package be.orbinson.aem.dictionarytranslator.exception; - -public class DictionaryTranslatorException extends Exception { - public DictionaryTranslatorException(String message) { - super(message); - } -} diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/models/Dictionary.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/models/Dictionary.java index e0c1f74d..9830852b 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/models/Dictionary.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/models/Dictionary.java @@ -24,5 +24,5 @@ public interface Dictionary { boolean isEditable(); - int getLabelCount(); + int getKeyCount(); } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/models/impl/DictionaryImpl.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/models/impl/DictionaryImpl.java index fa8904a7..5a922521 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/models/impl/DictionaryImpl.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/models/impl/DictionaryImpl.java @@ -14,7 +14,9 @@ import javax.inject.Named; import java.text.DateFormat; -import java.util.*; +import java.util.Calendar; +import java.util.Date; +import java.util.List; @Model( adaptables = SlingHttpServletRequest.class, @@ -85,25 +87,12 @@ public String getBasename() { } @Override - public int getLabelCount() { + public int getKeyCount() { return getKeys().size(); } @Override public List getKeys() { - Set keys = new TreeSet<>(); - for (String language : dictionaryService.getLanguages(resource)) { - Resource child = resource.getChild(language); - if (child != null) { - child.listChildren().forEachRemaining(item -> addKey(keys, item)); - } - } - return List.copyOf(keys); - } - - private static void addKey(Set keys, Resource item) { - if (item.isResourceType("sling:MessageEntry")) { - keys.add(item.getName()); - } + return dictionaryService.getKeys(resource); } } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/DictionaryService.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/DictionaryService.java index 00db1496..f710950c 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/DictionaryService.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/DictionaryService.java @@ -1,22 +1,44 @@ package be.orbinson.aem.dictionarytranslator.services; +import be.orbinson.aem.dictionarytranslator.exception.DictionaryException; +import com.day.cq.replication.ReplicationException; import org.apache.sling.api.resource.PersistenceException; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; +import javax.jcr.RepositoryException; import java.util.List; import java.util.Map; public interface DictionaryService { - Map getLanguagesForPath(ResourceResolver resourceResolver, String path); List getDictionaries(ResourceResolver resourceResolver); - List getLanguages(Resource resource); + void createDictionary(Resource parent, String name, String[] languages, String basename) throws PersistenceException; + + void deleteDictionary(ResourceResolver resourceResolver, String dictionaryPath) throws DictionaryException; + + List getLanguages(Resource dictionaryResource); + + void deleteLanguage(ResourceResolver resourceResolver, Resource dictionaryResource, String language) throws DictionaryException; + + void addLanguage(Resource dictionaryResource, String language, String basename) throws PersistenceException; + + Resource getLanguageResource(Resource dictionaryResource, String language); + + Map getLanguagesForPath(ResourceResolver resourceResolver, String dictionaryPath); String getBasename(Resource dictionaryResource); - void createDictionary(Resource parent, String name, String[] languages, String basename) throws PersistenceException; + List getKeys(Resource dictionaryResource); + + boolean keyExists(Resource dictionaryResource, String language, String key); + + Resource getMessageEntryResource(Resource languageResource, String key); + + void createMessageEntry(ResourceResolver resourceResolver, Resource dictionaryResource, String language, String key, String message) throws PersistenceException; + + void updateMessageEntry(ResourceResolver resourceResolver, Resource dictionaryResource, String language, String key, String message) throws PersistenceException, RepositoryException; - void addLanguage(Resource dictionary, String language, String basename) throws PersistenceException; + void deleteMessageEntry(ResourceResolver resourceResolver, Resource combiningMessageEntryResource) throws PersistenceException, ReplicationException; } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/LabelResourceProvider.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/LabelResourceProvider.java deleted file mode 100644 index 65afa38e..00000000 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/LabelResourceProvider.java +++ /dev/null @@ -1,166 +0,0 @@ -package be.orbinson.aem.dictionarytranslator.services; - -import be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants; -import com.adobe.granite.ui.components.ds.ValueMapResource; -import com.day.text.Text; -import org.apache.commons.lang3.StringUtils; -import org.apache.sling.api.resource.PersistenceException; -import org.apache.sling.api.resource.Resource; -import org.apache.sling.api.resource.ResourceResolver; -import org.apache.sling.api.resource.ValueMap; -import org.apache.sling.api.wrappers.ValueMapDecorator; -import org.apache.sling.spi.resource.provider.ResolveContext; -import org.apache.sling.spi.resource.provider.ResourceContext; -import org.apache.sling.spi.resource.provider.ResourceProvider; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; - -import java.util.*; - -@Component( - service = ResourceProvider.class, - property = { - ResourceProvider.PROPERTY_ROOT + "=" + LabelResourceProvider.ROOT, - ResourceProvider.PROPERTY_MODIFIABLE + "=" + true - } -) -public class LabelResourceProvider extends ResourceProvider { - public static final String ROOT = "/mnt/dictionary"; - - public static final String RESOURCE_TYPE = "aem-dictionary-translator/components/label"; - public static final String LANGUAGES = "languages"; - - @Reference - private DictionaryService dictionaryService; - - @Override - public void delete(@NotNull ResolveContext ctx, @NotNull Resource resource) throws PersistenceException { - ValueMap properties = resource.getValueMap(); - - if (properties.containsKey(LANGUAGES) && properties.containsKey("name")) { - ResourceResolver resourceResolver = ctx.getResourceResolver(); - - String[] languages = properties.get(LANGUAGES, String[].class); - String dictionaryPath = properties.get("dictionaryPath", String.class); - String name = properties.get("name", String.class); - - if (StringUtils.isNotEmpty(name) && languages != null) { - for (String language : languages) { - Resource labelResource = resourceResolver.getResource(dictionaryPath + "/" + language + "/" + name); - if (labelResource != null) { - resourceResolver.delete(labelResource); - } - } - resourceResolver.commit(); - } - } - } - - private Map getValuesAndLabelPaths(Resource dictionaryResource, String labelName, List languages) { - Map properties = new HashMap<>(); - List labelPaths = new ArrayList<>(); - - for (String language : languages) { - Resource languageResource = dictionaryResource.getChild(language); - if (languageResource != null) { - Resource labelResource = languageResource.getChild(labelName); - if (labelResource != null && (labelResource.getValueMap().containsKey(DictionaryConstants.SLING_MESSAGE))) { - properties.put(language, labelResource.getValueMap().get(DictionaryConstants.SLING_MESSAGE, String.class)); - labelPaths.add(labelResource.getPath()); - } - } - } - - properties.put("labelPaths", labelPaths); - - return properties; - } - - private List getLabelPaths(String labelName, String dictionaryPath, List languages, ResourceResolver resourceResolver) { - List labelPaths = new ArrayList<>(); - for (String language : languages) { - Resource labelResource = resourceResolver.getResource(dictionaryPath + "/" + language + "/" + labelName); - if (labelResource != null) { - labelPaths.add(labelResource.getPath()); - } - } - - return labelPaths; - } - - @Override - public @Nullable Resource getResource(@NotNull ResolveContext ctx, @NotNull String path, @NotNull ResourceContext resourceContext, @Nullable Resource parent) { - ResourceResolver resourceResolver = ctx.getResourceResolver(); - if (!path.startsWith(ROOT) || ROOT.equals(path)) { - // Not applying label resource provider - return null; - } - - String labelName = Text.getName(path); - String dictionaryPath = Text.getRelativeParent(path, 1).replaceFirst(ROOT, ""); - Resource dictionaryResource = resourceResolver.getResource(dictionaryPath); - - if (dictionaryResource != null && isLabel(dictionaryResource, labelName)) { - Map properties = new HashMap<>(); - properties.put("name", labelName); - properties.put("key", getKey(dictionaryResource, labelName)); - properties.put("path", path); - properties.put("dictionaryPath", dictionaryPath); - List languages = dictionaryService.getLanguages(dictionaryResource); - properties.put(LANGUAGES, languages); - properties.putAll(getValuesAndLabelPaths(dictionaryResource, labelName, languages)); - return new ValueMapResource(resourceResolver, path, RESOURCE_TYPE, new ValueMapDecorator(properties)); - } - return null; - } - - private boolean isLabel(Resource dictionaryResource, String labelName) { - List languages = dictionaryService.getLanguages(dictionaryResource); - if (!languages.isEmpty()) { - for (String language : languages) { - Resource languageResource = dictionaryResource.getChild(language); - if (languageResource != null) { - Resource labelResource = languageResource.getChild(labelName); - if (labelResource != null && labelResource.isResourceType(DictionaryConstants.SLING_MESSAGEENTRY)) { - return true; - } - } - } - } - return false; - } - - /** - * Returns the first set {@code sling:key} of the {@code sling:MessageEntry} resource with the given labelName below any of the available languages. - * Falls back to the {@code labelName} if no {@code sling:key} in any language's entry is found. - * @param dictionaryResource the parent resource containing the languages of a dictionary - * @param labelName - * @return the key of a label/entry in the dictionary - */ - private String getKey(Resource dictionaryResource, String labelName) { - List languages = dictionaryService.getLanguages(dictionaryResource); - // in order to speed things up always start with language "en" (if existing) - String mostCompleteLanguage = Locale.ENGLISH.getLanguage(); - if (languages.contains(mostCompleteLanguage)) { - languages.remove(mostCompleteLanguage); - languages.add(0, mostCompleteLanguage); - } - return languages.stream() - .map(dictionaryResource::getChild) - .filter(Objects::nonNull) - .map(languageResource -> languageResource.getChild(labelName)) - .filter(Objects::nonNull) - .map(labelResource -> labelResource.getValueMap().get(DictionaryConstants.SLING_KEY, String.class)) - .filter(Objects::nonNull) - .findFirst() - .orElse(labelName); - } - - @Override - public @Nullable Iterator listChildren(@NotNull ResolveContext ctx, @NotNull Resource parent) { - return Collections.emptyIterator(); - } - -} diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/impl/CombiningMessageEntryResourceProvider.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/impl/CombiningMessageEntryResourceProvider.java new file mode 100644 index 00000000..24ef1650 --- /dev/null +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/impl/CombiningMessageEntryResourceProvider.java @@ -0,0 +1,115 @@ +package be.orbinson.aem.dictionarytranslator.services.impl; + +import be.orbinson.aem.dictionarytranslator.services.DictionaryService; +import be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants; +import com.adobe.granite.ui.components.ds.ValueMapResource; +import com.day.text.Text; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.spi.resource.provider.ResolveContext; +import org.apache.sling.spi.resource.provider.ResourceContext; +import org.apache.sling.spi.resource.provider.ResourceProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import java.util.*; + +import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.SLING_MESSAGEENTRY; + +@Component( + service = ResourceProvider.class, + property = { + ResourceProvider.PROPERTY_ROOT + "=" + CombiningMessageEntryResourceProvider.ROOT + } +) +public class CombiningMessageEntryResourceProvider extends ResourceProvider { + + public static final String ROOT = "/mnt/dictionary"; + + public static final String RESOURCE_TYPE = "aem-dictionary-translator/components/combining-message-entry"; + + public static final String KEY = "key"; + public static final String DICTIONARY_PATH = "dictionaryPath"; + public static final String LANGUAGES = "languages"; + public static final String MESSAGE_ENTRY_PATHS = "messageEntryPaths"; + + @Reference + private DictionaryService dictionaryService; + + private Map getValuesAndMessageEntryPaths(Resource dictionaryResource, String key, List languages) { + Map properties = new HashMap<>(); + List messageEntryPaths = new ArrayList<>(); + + for (String language : languages) { + Resource languageResource = dictionaryService.getLanguageResource(dictionaryResource, language); + if (languageResource != null) { + Resource messageEntryResource = dictionaryService.getMessageEntryResource(languageResource, key); + if (messageEntryResource != null && messageEntryResource.isResourceType(DictionaryConstants.SLING_MESSAGEENTRY)) { + properties.put(language, messageEntryResource.getValueMap().get(DictionaryConstants.SLING_MESSAGE, "")); + messageEntryPaths.add(messageEntryResource.getPath()); + } + } + } + + properties.put(MESSAGE_ENTRY_PATHS, messageEntryPaths); + + return properties; + } + + @Override + public @Nullable Resource getResource(@NotNull ResolveContext ctx, @NotNull String path, @NotNull ResourceContext resourceContext, @Nullable Resource parent) { + ResourceResolver resourceResolver = ctx.getResourceResolver(); + if (!path.startsWith(ROOT) || ROOT.equals(path)) { + // Not applying combining message entry resource provider + return null; + } + + String key = Text.getName(path); + String dictionaryPath = Text.getRelativeParent(path, 1).replaceFirst(ROOT, ""); + Resource dictionaryResource = resourceResolver.getResource(dictionaryPath); + + if (dictionaryResource != null && isMessageEntry(dictionaryResource, key)) { + Map properties = new HashMap<>(); + properties.put(KEY, key); + properties.put("path", path); + properties.put(DICTIONARY_PATH, dictionaryPath); + List languages = dictionaryService.getLanguages(dictionaryResource); + properties.put(LANGUAGES, languages); + properties.putAll(getValuesAndMessageEntryPaths(dictionaryResource, key, languages)); + return new ValueMapResource(resourceResolver, path, RESOURCE_TYPE, new ValueMapDecorator(properties)); + } + return null; + } + + private boolean isMessageEntry(Resource dictionaryResource, String key) { + List languages = dictionaryService.getLanguages(dictionaryResource); + // in order to speed things up always start with language "en" (if existing) + String mostCompleteLanguage = Locale.ENGLISH.getLanguage(); + if (languages.contains(mostCompleteLanguage)) { + languages.remove(mostCompleteLanguage); + languages.add(0, mostCompleteLanguage); + } + + if (!languages.isEmpty()) { + for (String language : languages) { + Resource languageResource = dictionaryService.getLanguageResource(dictionaryResource, language); + if (languageResource != null) { + Resource messageEntryResource = dictionaryService.getMessageEntryResource(languageResource, key); + if (messageEntryResource != null && messageEntryResource.isResourceType(SLING_MESSAGEENTRY)) { + return true; + } + } + } + } + return false; + } + + @Override + public @Nullable Iterator listChildren(@NotNull ResolveContext ctx, @NotNull Resource parent) { + return Collections.emptyIterator(); + } + +} diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/impl/DictionaryServiceImpl.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/impl/DictionaryServiceImpl.java index c574db94..8c1403fe 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/impl/DictionaryServiceImpl.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/impl/DictionaryServiceImpl.java @@ -1,25 +1,37 @@ package be.orbinson.aem.dictionarytranslator.services.impl; +import be.orbinson.aem.dictionarytranslator.exception.DictionaryException; import be.orbinson.aem.dictionarytranslator.services.DictionaryService; +import be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants; import com.adobe.granite.translation.api.TranslationConfig; import com.day.cq.commons.jcr.JcrConstants; import com.day.cq.commons.jcr.JcrUtil; +import com.day.cq.replication.ReplicationActionType; +import com.day.cq.replication.ReplicationException; +import com.day.cq.replication.Replicator; import org.apache.commons.lang3.StringUtils; +import org.apache.jackrabbit.util.Text; import org.apache.sling.api.resource.*; import org.apache.sling.jcr.resource.api.JcrResourceConstants; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.jcr.RepositoryException; +import javax.jcr.Session; import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.*; import static org.apache.jackrabbit.JcrConstants.JCR_LANGUAGE; +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; @Component public class DictionaryServiceImpl implements DictionaryService { + private static final Logger LOG = LoggerFactory.getLogger(DictionaryServiceImpl.class); private static final String SLING_BASENAME = "sling:basename"; @@ -27,6 +39,8 @@ public class DictionaryServiceImpl implements DictionaryService { private TranslationConfig translationConfig; @Reference private ResourceResolverFactory resourceResolverFactory; + @Reference + private Replicator replicator; public void addLanguage(Resource dictionary, String language, String basename) throws PersistenceException { Map properties = new HashMap<>(); @@ -53,9 +67,9 @@ private ResourceResolver getServiceResourceResolver() throws LoginException { return resourceResolverFactory.getServiceResourceResolver(authenticationInfo); } - public @NotNull Map getLanguagesForPath(ResourceResolver resourceResolver, String path) { + public @NotNull Map getLanguagesForPath(ResourceResolver resourceResolver, String dictionaryPath) { Map result = new HashMap<>(); - Resource resource = resourceResolver.getResource(path); + Resource resource = resourceResolver.getResource(dictionaryPath); if (resource != null && translationConfig != null) { try (ResourceResolver serviceResourceResolver = getServiceResourceResolver()) { @@ -83,16 +97,45 @@ private ResourceResolver getServiceResourceResolver() throws LoginException { Map result = new TreeMap<>(); resourceResolver - .findResources("//element(*, mix:language)[@jcr:language and @jcr:primaryType='sling:Folder']/..", "xpath") + .findResources("//element(*, mix:language)[@jcr:language and (@jcr:primaryType='sling:Folder' or @jcr:primaryType='nt:folder')]/..", "xpath") .forEachRemaining(resource -> result.put(resource.getPath(), resource)); return new ArrayList<>(result.values()); } - public List getLanguages(Resource resource) { + public void createDictionary(Resource parent, String name, String[] languages, String basename) throws PersistenceException { + LOG.debug("Create dictionary '{}'", name); + ResourceResolver resourceResolver = parent.getResourceResolver(); + String dictionaryPath = String.format("%s/%s/i18n", parent.getPath(), JcrUtil.createValidName(name)); + Resource dictionaryResource = ResourceUtil.getOrCreateResource(resourceResolver, dictionaryPath, "sling:Folder", "sling:Folder", true); + + for (String language : languages) { + addLanguage(dictionaryResource, language, basename); + } + } + + @Override + public void deleteDictionary(ResourceResolver resourceResolver, String dictionaryPath) throws DictionaryException { + LOG.debug("Delete dictionary '{}'", dictionaryPath); + try { + final Resource dictionaryResource = resourceResolver.getResource(dictionaryPath); + if (dictionaryResource != null) { + replicator.replicate(resourceResolver.adaptTo(Session.class), ReplicationActionType.DEACTIVATE, dictionaryResource.getPath()); + resourceResolver.delete(dictionaryResource); + resourceResolver.commit(); + } else { + throw new DictionaryException("Dictionary '" + dictionaryPath + "' not found"); + } + } catch (PersistenceException | ReplicationException e) { + throw new DictionaryException("Could not delete dictionary: " + e.getMessage(), e); + } + } + + + public List getLanguages(Resource dictionaryResource) { List result = new ArrayList<>(); - resource.listChildren().forEachRemaining(child -> { + dictionaryResource.listChildren().forEachRemaining(child -> { ValueMap properties = child.getValueMap(); if (properties.containsKey(JCR_LANGUAGE)) { LOG.trace("Found language with path '{}'", child.getPath()); @@ -103,6 +146,23 @@ public List getLanguages(Resource resource) { return result; } + @Override + public void deleteLanguage(ResourceResolver resourceResolver, Resource dictionaryResource, String language) throws DictionaryException { + Resource languageResource = getLanguageResource(dictionaryResource, language); + if (languageResource != null) { + try { + LOG.debug("Delete language '{}' from '{}'", language, dictionaryResource.getPath()); + replicator.replicate(resourceResolver.adaptTo(Session.class), ReplicationActionType.DEACTIVATE, languageResource.getPath()); + resourceResolver.delete(languageResource); + resourceResolver.commit(); + } catch (PersistenceException | ReplicationException e) { + throw new DictionaryException("Could not delete language: " + e.getMessage(), e); + } + } else { + throw new DictionaryException("Language does not exist: " + language); + } + } + @Override public String getBasename(Resource dictionaryResource) { AtomicReference basename = new AtomicReference<>(); @@ -116,14 +176,131 @@ public String getBasename(Resource dictionaryResource) { return basename.get(); } - public void createDictionary(Resource parent, String name, String[] languages, String basename) throws PersistenceException { - LOG.debug("Create dictionary '{}'", name); - ResourceResolver resourceResolver = parent.getResourceResolver(); - String dictionaryPath = String.format("%s/%s/i18n", parent.getPath(), JcrUtil.createValidName(name)); - Resource dictionaryResource = ResourceUtil.getOrCreateResource(resourceResolver, dictionaryPath, "sling:Folder", "sling:Folder", true); + /** + * Gets the language resource based on the jcr:language property + * + * @param dictionaryResource The dictionary resource + * @param language The language + * @return the language resource if it exists + */ + @Override + public @Nullable Resource getLanguageResource(Resource dictionaryResource, String language) { + if (dictionaryResource != null) { + for (Resource languageResource : dictionaryResource.getChildren()) { + if (language.equals(languageResource.getValueMap().get(JcrConstants.JCR_LANGUAGE))) { + return languageResource; + } + } + } + return null; + } - for (String language : languages) { - addLanguage(dictionaryResource, language, basename); + @Override + public List getKeys(Resource dictionaryResource) { + Set keys = new TreeSet<>(); + for (String language : getLanguages(dictionaryResource)) { + Resource languageResource = getLanguageResource(dictionaryResource, language); + if (languageResource != null) { + for (Resource messageEntryResource : languageResource.getChildren()) { + if (messageEntryResource.isResourceType(SLING_MESSAGEENTRY) && messageEntryResource.getValueMap().containsKey(SLING_KEY)) { + keys.add(messageEntryResource.getValueMap().get(SLING_KEY, String.class)); + } + } + } } + return List.copyOf(keys); + } + + + @Override + public boolean keyExists(Resource dictionaryResource, String language, String key) { + Resource languageResource = getLanguageResource(dictionaryResource, language); + return languageResource != null && getMessageEntryResource(languageResource, key) != null; } + + @Override + public void createMessageEntry(ResourceResolver resourceResolver, Resource dictionaryResource, String language, String key, String message) throws PersistenceException { + Resource languageResource = getLanguageResource(dictionaryResource, language); + + if (languageResource != null) { + String path = languageResource.getPath(); + Map properties = new HashMap<>(); + properties.put(JCR_PRIMARYTYPE, SLING_MESSAGEENTRY); + properties.put(SLING_KEY, key); + if (!message.isBlank()) { + properties.put(SLING_MESSAGE, message); + } + resourceResolver.create(languageResource, Text.escapeIllegalJcrChars(key), properties); + resourceResolver.commit(); + LOG.trace("Created message entry with key '{}' and message '{}' on path '{}'", key, message, path); + } + } + + @Override + public void updateMessageEntry(ResourceResolver resourceResolver, Resource dictionaryResource, String language, String key, String message) throws PersistenceException, RepositoryException { + Resource languageResource = getLanguageResource(dictionaryResource, language); + if (languageResource != null) { + Resource messageEntryResource = getOrCreateMessageEntryResource(resourceResolver, languageResource, key); + if (messageEntryResource != null) { + ValueMap valueMap = messageEntryResource.adaptTo(ModifiableValueMap.class); + if (valueMap != null) { + if (message.isBlank()) { + valueMap.remove(SLING_MESSAGE); + } else { + valueMap.put(SLING_MESSAGE, message); + if (StringUtils.isNotBlank(key)) { + valueMap.putIfAbsent(SLING_KEY, key); + } + LOG.trace("Updated message entry with name '{}' and message '{}' on path '{}'", messageEntryResource.getName(), message, messageEntryResource.getPath()); + } + } + } + resourceResolver.commit(); + } + } + + @Override + public Resource getMessageEntryResource(Resource languageResource, String key) { + // In order to speed up the search, we go for the default check where it is the escaped key as node name + Resource messageEntryResource = languageResource.getChild(Text.escapeIllegalJcrChars(key)); + if (messageEntryResource != null) { + return messageEntryResource; + } + + // Fall back to searching for the resource with sling:key as correct property + for (Resource resource : languageResource.getChildren()) { + if (key.equals(resource.getValueMap().get(DictionaryConstants.SLING_KEY))) { + return resource; + } + } + return null; + } + + @Override + public void deleteMessageEntry(ResourceResolver resourceResolver, Resource combiningMessageEntryResource) throws PersistenceException, ReplicationException { + ValueMap properties = combiningMessageEntryResource.getValueMap(); + if (properties.containsKey(CombiningMessageEntryResourceProvider.MESSAGE_ENTRY_PATHS)) { + for (String messageEntryPath : properties.get(CombiningMessageEntryResourceProvider.MESSAGE_ENTRY_PATHS, new String[0])) { + Resource messageEntryResource = resourceResolver.getResource(messageEntryPath); + if (messageEntryResource != null) { + replicator.replicate(resourceResolver.adaptTo(Session.class), ReplicationActionType.DEACTIVATE, messageEntryPath); + resourceResolver.delete(messageEntryResource); + } + } + resourceResolver.commit(); + } + + } + + private Resource getOrCreateMessageEntryResource(ResourceResolver resourceResolver, Resource languageResource, String key) throws RepositoryException { + Resource messageEntryResource = getMessageEntryResource(languageResource, key); + if (messageEntryResource != null) { + return messageEntryResource; + } + Session session = resourceResolver.adaptTo(Session.class); + JcrUtil.createPath(languageResource.getPath() + "/" + Text.escapeIllegalJcrChars(key), SLING_MESSAGEENTRY, session); + session.save(); + return languageResource.getChild(Text.escapeIllegalJcrChars(key)); + } + } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/package-info.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/package-info.java new file mode 100644 index 00000000..18aea9bc --- /dev/null +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/services/package-info.java @@ -0,0 +1,4 @@ +@Version("1.0") +package be.orbinson.aem.dictionarytranslator.services; + +import org.osgi.annotation.versioning.Version; diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateLabelServlet.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateMessageEntryServlet.java similarity index 64% rename from core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateLabelServlet.java rename to core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateMessageEntryServlet.java index 76ed9c7d..8b8cae6a 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateLabelServlet.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateMessageEntryServlet.java @@ -2,7 +2,6 @@ import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import org.apache.commons.lang3.StringUtils; -import org.apache.jackrabbit.util.Text; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.resource.PersistenceException; @@ -20,21 +19,16 @@ import javax.servlet.Servlet; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.*; -import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; @Component(service = Servlet.class) @SlingServletResourceTypes( resourceSuperType = "granite/ui/components/coral/foundation/form", - resourceTypes = "aem-dictionary-translator/servlet/action/create-label", + resourceTypes = "aem-dictionary-translator/servlet/action/create-message-entry", methods = "POST" ) -public class CreateLabelServlet extends SlingAllMethodsServlet { +public class CreateMessageEntryServlet extends SlingAllMethodsServlet { - private static final Logger LOG = LoggerFactory.getLogger(CreateLabelServlet.class); + private static final Logger LOG = LoggerFactory.getLogger(CreateMessageEntryServlet.class); @Reference private transient DictionaryService dictionaryService; @@ -55,13 +49,13 @@ protected void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServlet if (dictionaryResource != null) { for (String language : dictionaryService.getLanguages(dictionaryResource)) { // javasecurity:S5145 - LOG.debug("Create label on path '{}/{}'", dictionary, key); + LOG.debug("Create message entry on path '{}/{}'", dictionary, key); String message = request.getParameter(language); - if (!labelExists(resourceResolver, dictionaryResource, language, key)) { - addMessage(resourceResolver, dictionaryResource, language, key, message); + if (!dictionaryService.keyExists(dictionaryResource, language, key)) { + dictionaryService.createMessageEntry(resourceResolver, dictionaryResource, language, key, message); } else { HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, String.format("Can not create label %s, label already exists", key)); + htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, String.format("Can not create message entry %s, key already exists", key)); htmlResponse.send(response, true); } } @@ -78,24 +72,4 @@ protected void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServlet } } - private boolean labelExists(ResourceResolver resourceResolver, Resource dictionaryResource, String language, String key) { - return resourceResolver.getResource(dictionaryResource.getPath() + "/" + language + "/" + Text.escapeIllegalJcrChars(key)) != null; - } - - private void addMessage(ResourceResolver resourceResolver, Resource dictionary, String language, String key, String message) throws PersistenceException { - Resource resource = dictionary.getChild(language); - - if (resource != null) { - String path = resource.getPath(); - Map properties = new HashMap<>(); - properties.put(JCR_PRIMARYTYPE, SLING_MESSAGEENTRY); - properties.put(SLING_KEY, key); - if (!message.isBlank()) { - properties.put(SLING_MESSAGE, message); - } - resourceResolver.create(resource, Text.escapeIllegalJcrChars(key), properties); - LOG.trace("Create label with key '{}' and message '{}' on path '{}'", key, message, path); - resourceResolver.commit(); - } - } } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteDictionaryServlet.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteDictionaryServlet.java index 649eb328..1b5db47b 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteDictionaryServlet.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteDictionaryServlet.java @@ -1,13 +1,9 @@ package be.orbinson.aem.dictionarytranslator.servlets.action; -import com.day.cq.replication.ReplicationActionType; -import com.day.cq.replication.ReplicationException; -import com.day.cq.replication.Replicator; -import org.apache.commons.lang3.StringUtils; +import be.orbinson.aem.dictionarytranslator.exception.DictionaryException; +import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; -import org.apache.sling.api.resource.PersistenceException; -import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.servlets.SlingAllMethodsServlet; import org.apache.sling.servlets.annotations.SlingServletResourceTypes; @@ -18,7 +14,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.jcr.Session; import javax.servlet.Servlet; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -30,46 +25,32 @@ methods = "POST" ) public class DeleteDictionaryServlet extends SlingAllMethodsServlet { - private static final Logger LOG = LoggerFactory.getLogger(DeleteDictionaryServlet.class); - public static final String DICTIONARIES_PARAM = "dictionaries"; + + public static final String DICTIONARIES_PARAM = "item"; @Reference - private transient Replicator replicator; + private transient DictionaryService dictionaryService; @Override protected void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws IOException { - String dictionaries = request.getParameter(DICTIONARIES_PARAM); + String[] dictionaries = request.getParameterValues(DICTIONARIES_PARAM); - if (StringUtils.isNotEmpty(dictionaries)) { + if (dictionaries == null) { + HtmlResponse htmlResponse = new HtmlResponse(); + htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, "Dictionaries parameters are required"); + htmlResponse.send(response, true); + } else { final ResourceResolver resourceResolver = request.getResourceResolver(); - - for (String dictionary : dictionaries.split(",")) { + for (String dictionaryPath : dictionaries) { try { - final Resource resource = resourceResolver.getResource(dictionary); - if (resource != null) { - LOG.debug("Delete dictionary '{}'", dictionary); - deactivateAndDelete(resourceResolver, resource); - resourceResolver.commit(); - } else { - HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_NOT_FOUND, "Dictionary not found"); - htmlResponse.send(response, true); - } - } catch (PersistenceException | ReplicationException e) { + dictionaryService.deleteDictionary(resourceResolver, dictionaryPath); + } catch (DictionaryException e) { HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.format("Unable to delete dictionary '%s': %s", dictionary, e.getMessage())); + htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, String.format("Unable to delete dictionary '%s': %s", dictionaryPath, e.getMessage())); htmlResponse.send(response, true); } } - } else { - HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, "Dictionary parameter can not be empty"); - htmlResponse.send(response, true); } } - private void deactivateAndDelete(ResourceResolver resourceResolver, Resource resource) throws ReplicationException, PersistenceException { - replicator.replicate(resourceResolver.adaptTo(Session.class), ReplicationActionType.DEACTIVATE, resource.getPath()); - resourceResolver.delete(resource); - } } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLanguageServlet.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLanguageServlet.java index 95c83b06..6cff7c06 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLanguageServlet.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLanguageServlet.java @@ -1,12 +1,10 @@ package be.orbinson.aem.dictionarytranslator.servlets.action; -import com.day.cq.replication.ReplicationActionType; -import com.day.cq.replication.ReplicationException; -import com.day.cq.replication.Replicator; +import be.orbinson.aem.dictionarytranslator.exception.DictionaryException; +import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; -import org.apache.sling.api.resource.PersistenceException; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.servlets.SlingAllMethodsServlet; @@ -18,7 +16,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.jcr.Session; import javax.servlet.Servlet; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -30,12 +27,12 @@ methods = "POST" ) public class DeleteLanguageServlet extends SlingAllMethodsServlet { - private static final Logger LOG = LoggerFactory.getLogger(DeleteLanguageServlet.class); + public static final String LANGUAGE_PARAM = "language"; public static final String DICTIONARY_PARAM = "dictionary"; @Reference - private transient Replicator replicator; + private transient DictionaryService dictionaryService; @Override @SuppressWarnings("java:S1075") @@ -45,22 +42,18 @@ public void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServletRes if (StringUtils.isNotEmpty(dictionary) && StringUtils.isNotEmpty(language)) { ResourceResolver resourceResolver = request.getResourceResolver(); - String path = dictionary + "/" + language; - Resource resource = resourceResolver.getResource(path); - + Resource dictionaryResource = resourceResolver.getResource(dictionary); try { - if (resource != null) { - LOG.debug("Delete language '{}' from '{}'", language, dictionary); - deactivateAndDelete(resourceResolver, resource); - resourceResolver.commit(); + if (dictionaryResource != null) { + dictionaryService.deleteLanguage(resourceResolver, dictionaryResource, language); } else { HtmlResponse htmlResponse = new HtmlResponse(); htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, String.format("Unable to get dictionary '%s'", dictionary)); htmlResponse.send(response, true); } - } catch (PersistenceException | ReplicationException e) { + } catch (DictionaryException e) { HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.format("Unable to delete language '%s': %s", language, e.getMessage())); + htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, String.format("Unable to delete language '%s': %s", language, e.getMessage())); htmlResponse.send(response, true); } } else { @@ -70,8 +63,4 @@ public void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServletRes } } - private void deactivateAndDelete(ResourceResolver resourceResolver, Resource resource) throws ReplicationException, PersistenceException { - replicator.replicate(resourceResolver.adaptTo(Session.class), ReplicationActionType.DEACTIVATE, resource.getPath()); - resourceResolver.delete(resource); - } } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLabelServlet.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteMessageEntryServlet.java similarity index 60% rename from core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLabelServlet.java rename to core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteMessageEntryServlet.java index 4e43b645..db2fd434 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLabelServlet.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteMessageEntryServlet.java @@ -1,9 +1,7 @@ package be.orbinson.aem.dictionarytranslator.servlets.action; -import com.day.cq.replication.ReplicationActionType; +import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import com.day.cq.replication.ReplicationException; -import com.day.cq.replication.Replicator; -import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.resource.PersistenceException; @@ -18,7 +16,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.jcr.Session; import javax.servlet.Servlet; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -26,56 +23,47 @@ @Component(service = Servlet.class) @SlingServletResourceTypes( resourceSuperType = "granite/ui/components/coral/foundation/form", - resourceTypes = "aem-dictionary-translator/servlet/action/delete-label", + resourceTypes = "aem-dictionary-translator/servlet/action/delete-message-entry", methods = "POST" ) -public class DeleteLabelServlet extends SlingAllMethodsServlet { - private static final Logger LOG = LoggerFactory.getLogger(DeleteLabelServlet.class); - public static final String LABEL_PARAM = "item"; +public class DeleteMessageEntryServlet extends SlingAllMethodsServlet { + private static final Logger LOG = LoggerFactory.getLogger(DeleteMessageEntryServlet.class); + public static final String ITEM_PARAM = "item"; @Reference - private transient Replicator replicator; + private transient DictionaryService dictionaryService; @Override protected void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws IOException { - String[] labels = request.getParameterValues(LABEL_PARAM); + String[] combiningMessageEntryPaths = request.getParameterValues(ITEM_PARAM); - if (labels == null) { + if (combiningMessageEntryPaths == null) { HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, "Labels parameters are required"); + htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, "item parameter is required"); htmlResponse.send(response, true); } else { - for (String label : labels) { + for (String combiningMessageEntryPath : combiningMessageEntryPaths) { ResourceResolver resourceResolver = request.getResourceResolver(); - Resource resource = resourceResolver.getResource(label); + Resource combiningMessageEntryResource = resourceResolver.getResource(combiningMessageEntryPath); try { - if (resource != null) { + if (combiningMessageEntryResource != null) { // javasecurity:S5145 - LOG.debug("Delete label on path '{}'", resource.getPath()); - deactivateAndDelete(resourceResolver, resource); + LOG.debug("Delete message entry for path '{}'", combiningMessageEntryResource.getPath()); + dictionaryService.deleteMessageEntry(resourceResolver, combiningMessageEntryResource); } else { // javasecurity:S5145 HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, String.format("Unable to get label '%s'", label)); + htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, String.format("Unable to get message entry '%s'", combiningMessageEntryPath)); htmlResponse.send(response, true); } } catch (PersistenceException | ReplicationException e) { HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.format("Unable to delete label '%s': %s", label, e.getMessage())); + htmlResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.format("Unable to delete message entry '%s': %s", combiningMessageEntryPath, e.getMessage())); htmlResponse.send(response, true); } } } } - private void deactivateAndDelete(ResourceResolver resourceResolver, Resource resource) throws ReplicationException, PersistenceException { - String[] labelPaths = resource.getValueMap().get("labelPaths", String[].class); - if (labelPaths != null) { - for (String labelPath : labelPaths) { - replicator.replicate(resourceResolver.adaptTo(Session.class), ReplicationActionType.DEACTIVATE, labelPath); - } - } - resourceResolver.delete(resource); - } } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ExportDictionaryServlet.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ExportDictionaryServlet.java index 534882dd..14c6625a 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ExportDictionaryServlet.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ExportDictionaryServlet.java @@ -1,5 +1,6 @@ package be.orbinson.aem.dictionarytranslator.servlets.action; +import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.request.RequestParameter; @@ -8,7 +9,9 @@ import org.apache.sling.api.servlets.SlingAllMethodsServlet; import org.apache.sling.servlets.annotations.SlingServletResourceTypes; import org.apache.sling.servlets.post.HtmlResponse; +import org.jetbrains.annotations.NotNull; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,13 +19,13 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Iterator; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.SLING_KEY; import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.SLING_MESSAGE; +// TODO should add quoting / escaping for CSV's @Component(service = Servlet.class) @SlingServletResourceTypes( resourceSuperType = "granite/ui/components/coral/foundation/form", @@ -31,27 +34,29 @@ public class ExportDictionaryServlet extends SlingAllMethodsServlet { public static final String KEY_HEADER = "KEY"; + private static final Logger LOG = LoggerFactory.getLogger(ExportDictionaryServlet.class); + @Reference + private DictionaryService dictionaryService; + @Override public void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException { - String dictionary = request.getParameter("dictionary"); + String dictionaryPath = request.getParameter("dictionary"); RequestParameter delimiter = request.getRequestParameter("delimiter"); response.setContentType("text/csv"); - response.setHeader("Content-Disposition", "attachment; filename=\"dictionary_" + dictionary + ".csv"); + response.setCharacterEncoding("UTF-8"); + response.setHeader("Content-Disposition", "attachment; filename=\"dictionary_" + dictionaryPath + ".csv"); try (PrintWriter writer = response.getWriter()) { ResourceResolver resolver = request.getResourceResolver(); - Resource resource = resolver.getResource(dictionary); - - if (resource != null) { - List languageResources = collectLanguageResources(resource); - writeCsvHeader(writer, delimiter, languageResources); + Resource dictionaryResource = resolver.getResource(dictionaryPath); - if (!languageResources.isEmpty()) { - writeCsvRows(writer, delimiter, languageResources); - } + if (dictionaryResource != null) { + List languages = dictionaryService.getLanguages(dictionaryResource); + writeCsvHeader(writer, delimiter, languages); + writeCsvRows(writer, delimiter, dictionaryResource, languages); } else { HtmlResponse htmlResponse = new HtmlResponse(); htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, "Dictionary resource not found."); @@ -64,72 +69,52 @@ public void doPost(SlingHttpServletRequest request, SlingHttpServletResponse res } } - private List collectLanguageResources(Resource resource) { - Iterator children = resource.listChildren(); - List languageResources = new ArrayList<>(); - - while (children.hasNext()) { - Resource child = children.next(); - languageResources.add(child); - } - - return languageResources; - } - private void writeCsvHeader(PrintWriter writer, RequestParameter delimiter, List languageResources) { + private void writeCsvHeader(PrintWriter writer, RequestParameter delimiter, List languages) { StringBuilder csvHeader = new StringBuilder(KEY_HEADER); - for (Resource languageResource : languageResources) { + for (String language : languages) { csvHeader.append(delimiter); - csvHeader.append(languageResource.getName()); + csvHeader.append(language); } writer.println(csvHeader); LOG.debug("CSV header: " + csvHeader); } - private void writeCsvRows(PrintWriter writer, RequestParameter delimiter, List languageResources) { - List labels = new ArrayList<>(); - Resource currentLanguageResource = null; - if (!languageResources.isEmpty()) { - for (int i = 0; i < languageResources.size(); i++) { - currentLanguageResource = languageResources.get(i); - Iterator labelChildren = currentLanguageResource.listChildren(); - while (labelChildren.hasNext()) { - Resource labelResource = labelChildren.next(); - if (!labels.contains(labelResource.getName())) { - StringBuilder csvRow = buildCsvRow(labelResource, delimiter, languageResources); - writer.println(csvRow); - labels.add(labelResource.getName()); - LOG.debug("CSV row: " + csvRow); - } + private void writeCsvRows(PrintWriter writer, RequestParameter delimiter, Resource dictionaryResource, List languages) { + List keys = dictionaryService.getKeys(dictionaryResource); + Map languageResources = getLanguageResourceMap(dictionaryResource, languages); + for (String key : keys) { + StringBuilder csvRow = new StringBuilder(); + csvRow.append(key); + csvRow.append(delimiter); + for (int i = 0; i < languages.size(); i++) { + String language = languages.get(i); + Resource messageEntryResource = dictionaryService.getMessageEntryResource(languageResources.get(language), key); + if (messageEntryResource != null) { + csvRow.append(messageEntryResource.getValueMap().get(SLING_MESSAGE)); + } else { + csvRow.append(" "); + } + if (i + 1 < languages.size()) { + csvRow.append(delimiter); } } + LOG.debug("CSV row: " + csvRow); + writer.println(csvRow); } } - private StringBuilder buildCsvRow(Resource labelResource, RequestParameter delimiter, List languageResources) { - StringBuilder csvRow; - if (labelResource.getValueMap().containsKey(SLING_KEY)) { - csvRow = new StringBuilder(labelResource.getValueMap().get(SLING_KEY, String.class)); - } else { - csvRow = new StringBuilder(labelResource.getName()); - } - - for (Resource languageResource : languageResources) { - Resource correspondingLabelResource = languageResource.getChild(labelResource.getName()); - csvRow.append(delimiter); - String translation = " "; - if (correspondingLabelResource != null) { - translation = correspondingLabelResource.getValueMap().get(SLING_MESSAGE, String.class); - } - if (translation == null) { - translation = " "; // This should be a space because appending an empty string will delete the whole string + private @NotNull Map getLanguageResourceMap(Resource dictionaryResource, List languages) { + Map languageResources = new HashMap<>(); + for (String lang : languages) { + Resource languageResource = dictionaryService.getLanguageResource(dictionaryResource, lang); + if (languageResource != null) { + languageResources.put(lang, languageResource); } - csvRow.append(translation); } - - return csvRow; + return languageResources; } } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ImportDictionaryServlet.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ImportDictionaryServlet.java index b4e7cf89..98c9e798 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ImportDictionaryServlet.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ImportDictionaryServlet.java @@ -1,20 +1,23 @@ package be.orbinson.aem.dictionarytranslator.servlets.action; -import org.apache.commons.compress.utils.Lists; +import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.request.RequestParameter; -import org.apache.sling.api.resource.*; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.servlets.SlingAllMethodsServlet; import org.apache.sling.servlets.annotations.SlingServletResourceTypes; import org.apache.sling.servlets.post.HtmlResponse; import org.apache.tika.io.IOUtils; -import org.jetbrains.annotations.NotNull; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import javax.jcr.RepositoryException; import javax.servlet.Servlet; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -22,29 +25,31 @@ import java.nio.charset.StandardCharsets; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import static be.orbinson.aem.dictionarytranslator.servlets.action.ExportDictionaryServlet.KEY_HEADER; -import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.*; -import static com.day.cq.commons.jcr.JcrConstants.*; -import static org.apache.sling.jcr.resource.api.JcrResourceConstants.NT_SLING_FOLDER; -import static org.apache.sling.jcr.resource.api.JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY; @Component(service = Servlet.class) -@SlingServletResourceTypes(resourceSuperType = "granite/ui/components/coral/foundation/form", resourceTypes = "aem-dictionary-translator/servlet/action/import-dictionary", methods = "POST") +@SlingServletResourceTypes( + resourceSuperType = "granite/ui/components/coral/foundation/form", + resourceTypes = "aem-dictionary-translator/servlet/action/import-dictionary", + methods = "POST" +) public class ImportDictionaryServlet extends SlingAllMethodsServlet { + @Reference + private DictionaryService dictionaryService; + @Override public void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException { - String path = request.getParameter("dictionary"); + String dictionaryPath = request.getParameter("dictionary"); RequestParameter csvfile = request.getRequestParameter("csvfile"); if (csvfile != null) { try { - processCsvFile(request, response, path, csvfile.getInputStream()); - } catch (IOException e) { + processCsvFile(request, response, dictionaryPath, csvfile.getInputStream()); + } catch (IOException | RepositoryException e) { HtmlResponse htmlResponse = new HtmlResponse(); htmlResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while importing CSV file: " + e.getMessage()); htmlResponse.send(response, true); @@ -52,7 +57,7 @@ public void doPost(SlingHttpServletRequest request, SlingHttpServletResponse res } } - private void processCsvFile(SlingHttpServletRequest request, SlingHttpServletResponse response, String path, InputStream csvContent) throws IOException { + private void processCsvFile(SlingHttpServletRequest request, SlingHttpServletResponse response, String dictionaryPath, InputStream csvContent) throws IOException, RepositoryException { List languages = new ArrayList<>(); List keys = new ArrayList<>(); List> translations = new ArrayList<>(); @@ -72,13 +77,13 @@ private void processCsvFile(SlingHttpServletRequest request, SlingHttpServletRes validateCsvHeaders(response, headers); headers.remove(KEY_HEADER); - Resource dictionary = resourceResolver.getResource(path); - if (dictionary != null) { - List knownLanguages = Lists.newArrayList(dictionary.listChildren()); - initializeLanguageData(headers, languages, translations, knownLanguages, response); + Resource dictionaryResource = resourceResolver.getResource(dictionaryPath); + if (dictionaryResource != null) { + List knownLanguages = dictionaryService.getLanguages(dictionaryResource); + initializeLanguageData(headers, languages, translations, knownLanguages); for (CSVRecord record : csvParser) { - processCsvRecord(path, languages, resourceResolver, keys, translations, record); + processCsvRecord(dictionaryResource, languages, resourceResolver, keys, translations, record); } resourceResolver.commit(); @@ -102,12 +107,12 @@ private void validateCsvHeaders(SlingHttpServletResponse response, Map headers, List languages, List> translations, List knownLanguages, SlingHttpServletResponse response) throws IOException { + private void initializeLanguageData(Map headers, List languages, List> translations, List knownLanguages) throws IOException { for (String language : headers.keySet()) { boolean hasMatch = true; - for (Resource knownLanguage : knownLanguages) { + for (String knownLanguage : knownLanguages) { hasMatch = false; - if (knownLanguage.getName().equals(language)) { + if (knownLanguage.equals(language)) { languages.add(language); translations.add(new ArrayList<>()); hasMatch = true; @@ -120,57 +125,30 @@ private void initializeLanguageData(Map headers, List l } } - private void processCsvRecord(String path, List languages, ResourceResolver resourceResolver, List keys, List> translations, CSVRecord record) throws IOException { + private void processCsvRecord(Resource dictionaryResource, List languages, ResourceResolver resourceResolver, List keys, List> translations, CSVRecord record) throws IOException, RepositoryException { if (record.size() != languages.size() + 1) { throw new IOException("Record has an incorrect number of translations: " + record); } - String label = record.get(KEY_HEADER); - keys.add(label); + String key = record.get(KEY_HEADER); + keys.add(key); for (String language : languages) { int index = languages.indexOf(language); String translation = record.get(language); translations.get(index).add(translation); - createOrUpdateLabelResource(path, resourceResolver, language, label, translation); + createOrUpdateLabelResource(dictionaryResource, resourceResolver, language, key, translation); } } - private void createOrUpdateLabelResource(String path, ResourceResolver resourceResolver, String language, String label, String translation) throws PersistenceException { - Resource languageResource = getLanguageResource(path, resourceResolver, language); - - Resource labelResource = resourceResolver.getResource(path + "/" + language + "/" + label); - if (labelResource == null) { - createLabelResource(resourceResolver, languageResource, label, translation); + private void createOrUpdateLabelResource(Resource dictionaryResource, ResourceResolver resourceResolver, String language, String key, String translation) throws PersistenceException, RepositoryException { + Resource languageResource = dictionaryService.getLanguageResource(dictionaryResource, language); + Resource messageEntryResource = dictionaryService.getMessageEntryResource(languageResource, key); + if (messageEntryResource == null) { + dictionaryService.createMessageEntry(resourceResolver, dictionaryResource, language, key, translation); } else { - updateLabelResourceProperties(labelResource, label, translation); - } - } - - private static @NotNull Resource getLanguageResource(String path, ResourceResolver resourceResolver, String language) throws PersistenceException { - return ResourceUtil.getOrCreateResource(resourceResolver, path + "/" + language, Map.of(JCR_PRIMARYTYPE, NT_SLING_FOLDER, JCR_LANGUAGE, language, JCR_BASENAME, language, SLING_RESOURCE_TYPE_PROPERTY, NT_SLING_FOLDER, JCR_MIXINTYPES, new String[]{MIX_LANGUAGE}), null, false); - } - - - private Resource createLabelResource(ResourceResolver resourceResolver, Resource languageResource, String newNodeName, String translation) throws PersistenceException { - Map properties = new HashMap<>(); - properties.put(JCR_PRIMARYTYPE, SLING_MESSAGEENTRY); - properties.put(SLING_KEY, newNodeName); - if (!translation.isBlank()) { - properties.put(SLING_MESSAGE, translation); - } - return resourceResolver.create(languageResource, newNodeName, properties); - } - - private void updateLabelResourceProperties(Resource labelResource, String label, String translation) { - ModifiableValueMap modifiableValueMap = labelResource.adaptTo(ModifiableValueMap.class); - if (modifiableValueMap != null) { - modifiableValueMap.put(JCR_PRIMARYTYPE, SLING_MESSAGEENTRY); - modifiableValueMap.put(SLING_KEY, label); - if (!translation.isBlank()) { - modifiableValueMap.put(SLING_MESSAGE, translation); - } + dictionaryService.updateMessageEntry(resourceResolver, dictionaryResource, language, key, translation); } } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateDictionaryServlet.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateDictionaryServlet.java index 52dc2669..f4408994 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateDictionaryServlet.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateDictionaryServlet.java @@ -35,8 +35,6 @@ public class ReplicateDictionaryServlet extends SlingAllMethodsServlet { @Reference private transient Replicator replicator; - private transient ResourceResolver resourceResolver; - @Override protected void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws IOException { String path = request.getParameter("path"); @@ -45,12 +43,12 @@ protected void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServlet LOG.warn("Invalid parameters to replicate dictionary"); response.sendError(HttpServletResponse.SC_BAD_REQUEST); } else { - resourceResolver = request.getResourceResolver(); + ResourceResolver resourceResolver = request.getResourceResolver(); Resource resource = resourceResolver.getResource(path); try { if (resource != null) { - deepReplicate(resource); + deepReplicate(resourceResolver, resource); // javasecurity:S5145 LOG.debug("Replicated dictionary, 'path={}'", path); } else { @@ -67,13 +65,13 @@ protected void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServlet } } - private void deepReplicate(Resource parentResource) throws ReplicationException { + private void deepReplicate(ResourceResolver resourceResolver, Resource parentResource) throws ReplicationException { String path = parentResource.getPath(); replicator.replicate(resourceResolver.adaptTo(Session.class), ReplicationActionType.ACTIVATE, path); if (parentResource.hasChildren()) { for (Resource childResource : parentResource.getChildren()) { - deepReplicate(childResource); + deepReplicate(resourceResolver, childResource); } } } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateLabelServlet.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateMessageEntryServlet.java similarity index 54% rename from core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateLabelServlet.java rename to core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateMessageEntryServlet.java index 6015d4e7..5a31d161 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateLabelServlet.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateMessageEntryServlet.java @@ -1,9 +1,9 @@ package be.orbinson.aem.dictionarytranslator.servlets.action; +import be.orbinson.aem.dictionarytranslator.services.impl.CombiningMessageEntryResourceProvider; import com.day.cq.replication.ReplicationActionType; import com.day.cq.replication.ReplicationException; import com.day.cq.replication.Replicator; -import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.resource.Resource; @@ -21,70 +21,50 @@ import javax.servlet.Servlet; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.Iterator; @Component(service = Servlet.class) @SlingServletResourceTypes( resourceSuperType = "granite/ui/components/coral/foundation/form", - resourceTypes = "aem-dictionary-translator/servlet/action/publish-label", + resourceTypes = "aem-dictionary-translator/servlet/action/publish-message-entry", methods = "POST" ) -public class ReplicateLabelServlet extends SlingAllMethodsServlet { - private static final Logger LOG = LoggerFactory.getLogger(ReplicateLabelServlet.class); +public class ReplicateMessageEntryServlet extends SlingAllMethodsServlet { + private static final Logger LOG = LoggerFactory.getLogger(ReplicateMessageEntryServlet.class); @Reference private transient Replicator replicator; @Override protected void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws IOException { - String[] labels = request.getParameterValues("item"); + String[] combiningMessageEntryPaths = request.getParameterValues("item"); - if (labels == null) { + if (combiningMessageEntryPaths == null) { LOG.warn("At least one item parameter is required"); response.sendError(HttpServletResponse.SC_BAD_REQUEST); } else { try { - for (String label : labels) { - //Splitting label into dictionary path and label key - label = label.replace("/mnt/dictionary", ""); - int lastIndexOfBackslash = label.lastIndexOf('/'); - String parentPath = ""; - if (lastIndexOfBackslash != -1) { - parentPath = label.substring(0, lastIndexOfBackslash); - label = label.substring(lastIndexOfBackslash + 1); - } - ResourceResolver resourceResolver = getResourceResolver(request); - Iterator iterator = getResources(resourceResolver, parentPath, label); - if (iterator.hasNext()) { - while (iterator.hasNext()) { - Resource resource = iterator.next(); - replicator.replicate(resourceResolver.adaptTo(Session.class), ReplicationActionType.ACTIVATE, resource.getPath()); - LOG.debug("Published label on path '{}'", resource.getPath()); + for (String combiningMessageEntryPath : combiningMessageEntryPaths) { + ResourceResolver resourceResolver = request.getResourceResolver(); + Resource combiningMessageEntry = resourceResolver.getResource(combiningMessageEntryPath); + if (combiningMessageEntry != null) { + String[] messageEntryPaths = combiningMessageEntry.getValueMap().get(CombiningMessageEntryResourceProvider.MESSAGE_ENTRY_PATHS, new String[0]); + for (String messageEntryPath : messageEntryPaths) { + replicator.replicate(resourceResolver.adaptTo(Session.class), ReplicationActionType.ACTIVATE, messageEntryPath); + LOG.debug("Published message entry for path '{}'", messageEntryPath); } } else { HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, String.format("Unable to get label '%s", label)); + htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, String.format("Unable to get message entry '%s", combiningMessageEntryPath)); htmlResponse.send(response, true); } } } catch (ReplicationException e) { HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while replicating label: " + e); + htmlResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while replicating message entry: " + e); htmlResponse.send(response, true); } } } - @NotNull - ResourceResolver getResourceResolver(SlingHttpServletRequest request) { - return request.getResourceResolver(); - } - - @NotNull - Iterator getResources(ResourceResolver resolver, String parentPath, String label) { - String query = "/jcr:root" + parentPath + "//element(*, mix:language)/" + label; - return resolver.findResources(query, "xpath"); - } - } diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateLabelServlet.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateLabelServlet.java deleted file mode 100644 index d5108d31..00000000 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateLabelServlet.java +++ /dev/null @@ -1,112 +0,0 @@ -package be.orbinson.aem.dictionarytranslator.servlets.action; - -import be.orbinson.aem.dictionarytranslator.exception.DictionaryTranslatorException; -import com.day.cq.commons.jcr.JcrUtil; -import org.apache.commons.lang3.StringUtils; -import org.apache.sling.api.SlingHttpServletRequest; -import org.apache.sling.api.SlingHttpServletResponse; -import org.apache.sling.api.resource.*; -import org.apache.sling.api.servlets.SlingAllMethodsServlet; -import org.apache.sling.servlets.annotations.SlingServletResourceTypes; -import org.apache.sling.servlets.post.HtmlResponse; -import org.jetbrains.annotations.NotNull; -import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.servlet.Servlet; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.*; - -@Component(service = Servlet.class) -@SlingServletResourceTypes( - resourceSuperType = "granite/ui/components/coral/foundation/form", - resourceTypes = "aem-dictionary-translator/servlet/action/update-label", - methods = "POST" -) -public class UpdateLabelServlet extends SlingAllMethodsServlet { - - private static final Logger LOG = LoggerFactory.getLogger(UpdateLabelServlet.class); - - - @Override - protected void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws IOException { - String label = request.getParameter("item"); // only single items are supported - - if (StringUtils.isEmpty(label)) { - HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, "Label parameter is required"); - htmlResponse.send(response, true); - } else { - ResourceResolver resourceResolver = request.getResourceResolver(); - Resource resource = resourceResolver.getResource(label); - try { - if (resource != null) { - // javasecurity:S5145 - LOG.debug("Update label on path '{}'", label); - updateLabel(request, resourceResolver, resource); - } else { - // javasecurity:S5145 - HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.format("Label does not exist '%s'", label)); - htmlResponse.send(response, true); - } - } catch (Exception e) { - HtmlResponse htmlResponse = new HtmlResponse(); - htmlResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.format("Unable to update label '%s': %s", label, e.getMessage())); - htmlResponse.send(response, true); - } - } - } - - private void updateLabel(SlingHttpServletRequest request, ResourceResolver resourceResolver, Resource resource) throws DictionaryTranslatorException, PersistenceException, RepositoryException { - String key = request.getParameter("key"); - String dictionaryPath = resource.getValueMap().get("dictionaryPath", String.class); - if (StringUtils.isNotBlank(dictionaryPath)) { - Resource dictionaryResource = resourceResolver.getResource(dictionaryPath); - String name = resource.getName(); - String[] languages = resource.getValueMap().get("languages", new String[0]); - for (String language : languages) { - String message = request.getParameter(language); - addMessage(resourceResolver, dictionaryResource, language, name, key, message); - } - } else { - throw new DictionaryTranslatorException("Could not find dictionary path"); - } - } - - private void addMessage(ResourceResolver resourceResolver, Resource dictionary, String language, String name, String key, String message) throws PersistenceException, RepositoryException { - Resource languageResource = dictionary.getChild(language); - if (languageResource != null) { - Resource labelResource = getLabelResource(resourceResolver, languageResource, name); - if (labelResource != null) { - ValueMap valueMap = labelResource.adaptTo(ModifiableValueMap.class); - if (valueMap != null) { - if (message.isBlank()) { - valueMap.remove(SLING_MESSAGE); - } else { - valueMap.put(SLING_MESSAGE, message); - if (StringUtils.isNotBlank(key)) { - valueMap.putIfAbsent(SLING_KEY, key); - } - LOG.trace("Updated label with name '{}' and message '{}' on path '{}'", name, message, labelResource.getPath()); - } - } - } - resourceResolver.commit(); - } - } - - public Resource getLabelResource(ResourceResolver resourceResolver, Resource languageResource, String name) throws RepositoryException { - if (languageResource.getChild(name) == null) { - Session session = resourceResolver.adaptTo(Session.class); - JcrUtil.createPath(languageResource.getPath() + "/" + name, SLING_MESSAGEENTRY, session); - session.save(); - } - return languageResource.getChild(name); - } -} diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateMessageEntryServlet.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateMessageEntryServlet.java new file mode 100644 index 00000000..67c74148 --- /dev/null +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateMessageEntryServlet.java @@ -0,0 +1,84 @@ +package be.orbinson.aem.dictionarytranslator.servlets.action; + +import be.orbinson.aem.dictionarytranslator.exception.DictionaryException; +import be.orbinson.aem.dictionarytranslator.services.impl.CombiningMessageEntryResourceProvider; +import be.orbinson.aem.dictionarytranslator.services.DictionaryService; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.SlingHttpServletResponse; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.servlets.SlingAllMethodsServlet; +import org.apache.sling.servlets.annotations.SlingServletResourceTypes; +import org.apache.sling.servlets.post.HtmlResponse; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.RepositoryException; +import javax.servlet.Servlet; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component(service = Servlet.class) +@SlingServletResourceTypes( + resourceSuperType = "granite/ui/components/coral/foundation/form", + resourceTypes = "aem-dictionary-translator/servlet/action/update-message-entry", + methods = "POST" +) +public class UpdateMessageEntryServlet extends SlingAllMethodsServlet { + + private static final Logger LOG = LoggerFactory.getLogger(UpdateMessageEntryServlet.class); + + @Reference + private transient DictionaryService dictionaryService; + + @Override + protected void doPost(SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws IOException { + String combiningMessageEntryPath = request.getParameter("item"); // only single items are supported + + if (StringUtils.isEmpty(combiningMessageEntryPath)) { + HtmlResponse htmlResponse = new HtmlResponse(); + htmlResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST, "item parameter is required"); + htmlResponse.send(response, true); + } else { + ResourceResolver resourceResolver = request.getResourceResolver(); + Resource combiningMessageEntryResource = resourceResolver.getResource(combiningMessageEntryPath); + try { + if (combiningMessageEntryResource != null) { + // javasecurity:S5145 + LOG.debug("Update message entry for path '{}'", combiningMessageEntryPath); + updateCombiningMessageEntry(request, resourceResolver, combiningMessageEntryResource); + } else { + // javasecurity:S5145 + HtmlResponse htmlResponse = new HtmlResponse(); + htmlResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.format("Message entry does not exist '%s'", combiningMessageEntryPath)); + htmlResponse.send(response, true); + } + } catch (Exception e) { + HtmlResponse htmlResponse = new HtmlResponse(); + htmlResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.format("Unable to update message entry '%s': %s", combiningMessageEntryPath, e.getMessage())); + htmlResponse.send(response, true); + } + } + } + + private void updateCombiningMessageEntry(SlingHttpServletRequest request, ResourceResolver resourceResolver, Resource combiningMessageEntryResource) throws DictionaryException, PersistenceException, RepositoryException { + String key = request.getParameter("key"); + String dictionaryPath = combiningMessageEntryResource.getValueMap().get(CombiningMessageEntryResourceProvider.DICTIONARY_PATH, String.class); + if (StringUtils.isNotBlank(dictionaryPath)) { + Resource dictionaryResource = resourceResolver.getResource(dictionaryPath); + String[] languages = combiningMessageEntryResource.getValueMap().get(CombiningMessageEntryResourceProvider.LANGUAGES, new String[0]); + for (String language : languages) { + String message = request.getParameter(language); + dictionaryService.updateMessageEntry(resourceResolver, dictionaryResource, language, key, message); + } + } else { + throw new DictionaryException("Could not find dictionary path"); + } + } + +} diff --git a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/datasource/LabelDatasource.java b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/datasource/CombiningMessageEntryDatasource.java similarity index 81% rename from core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/datasource/LabelDatasource.java rename to core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/datasource/CombiningMessageEntryDatasource.java index 0b60257e..567fd981 100644 --- a/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/datasource/LabelDatasource.java +++ b/core/src/main/java/be/orbinson/aem/dictionarytranslator/servlets/datasource/CombiningMessageEntryDatasource.java @@ -1,7 +1,7 @@ package be.orbinson.aem.dictionarytranslator.servlets.datasource; import be.orbinson.aem.dictionarytranslator.models.Dictionary; -import be.orbinson.aem.dictionarytranslator.services.LabelResourceProvider; +import be.orbinson.aem.dictionarytranslator.services.impl.CombiningMessageEntryResourceProvider; import com.adobe.granite.ui.components.ds.DataSource; import com.adobe.granite.ui.components.ds.SimpleDataSource; import com.adobe.granite.ui.components.ds.ValueMapResource; @@ -26,10 +26,10 @@ @Component(service = Servlet.class) @SlingServletResourceTypes( - resourceTypes = "aem-dictionary-translator/datasource/label", + resourceTypes = "aem-dictionary-translator/datasource/combining-message-entry", methods = "GET" ) -public class LabelDatasource extends SlingSafeMethodsServlet { +public class CombiningMessageEntryDatasource extends SlingSafeMethodsServlet { @Reference private transient ModelFactory modelFactory; @@ -48,7 +48,7 @@ private static ValueMapResource getColumn(ResourceResolver resourceResolver, Str private static void setDataSource(ResourceResolver resourceResolver, List resourceList, Dictionary dictionary) { for (String key : dictionary.getKeys()) { - String path = LabelResourceProvider.ROOT + dictionary.getResource().getPath() + '/' + key; + String path = CombiningMessageEntryResourceProvider.ROOT + dictionary.getResource().getPath() + '/' + key; Resource keyResource = resourceResolver.getResource(path); if (keyResource != null) { resourceList.add(keyResource); @@ -91,23 +91,23 @@ protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHtt createDictionaryDataSource(request, resourceResolver, dictionaryPath, resourceList); } - String labelPath = request.getParameter("label"); - if (labelPath != null) { - createLabelDataSource(resourceResolver, labelPath, resourceList); + String combiningMessageEntryPath = request.getParameter("item"); + if (combiningMessageEntryPath != null) { + createCombiningMessageEntryDataSource(resourceResolver, combiningMessageEntryPath, resourceList); } DataSource dataSource = new SimpleDataSource(resourceList.iterator()); request.setAttribute(DataSource.class.getName(), dataSource); } - private static void createLabelDataSource(ResourceResolver resourceResolver, String labelPath, List resourceList) { - Resource resource = resourceResolver.getResource(labelPath); - if (resource != null) { - ValueMap properties = resource.getValueMap(); - String[] languages = properties.get("languages", String[].class); - String key = properties.get("key", String.class); + private static void createCombiningMessageEntryDataSource(ResourceResolver resourceResolver, String combiningMessageEntryPath, List resourceList) { + Resource combiningMessageEntryResource = resourceResolver.getResource(combiningMessageEntryPath); + if (combiningMessageEntryResource != null) { + ValueMap properties = combiningMessageEntryResource.getValueMap(); + String[] languages = properties.get(CombiningMessageEntryResourceProvider.LANGUAGES, String[].class); + String key = properties.get(CombiningMessageEntryResourceProvider.KEY, String.class); - createTextFieldResource(resourceResolver, resourceList, "Label", key, false, true); + createTextFieldResource(resourceResolver, resourceList, "Key", key, false, true); createHiddenFieldResource(resourceResolver, resourceList, "key", key); if (languages != null) { for (String language : languages) { diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/models/impl/DictionaryImplTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/models/impl/DictionaryImplTest.java index fec01f94..a2515783 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/models/impl/DictionaryImplTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/models/impl/DictionaryImplTest.java @@ -1,33 +1,48 @@ package be.orbinson.aem.dictionarytranslator.models.impl; import be.orbinson.aem.dictionarytranslator.models.Dictionary; +import be.orbinson.aem.dictionarytranslator.services.impl.DictionaryServiceImpl; +import com.adobe.granite.translation.api.TranslationConfig; +import com.day.cq.replication.Replicator; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; import junit.framework.Assert; import org.apache.commons.lang3.time.DateUtils; import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.models.factory.ModelFactory; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.List; +import java.util.*; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; @ExtendWith({AemContextExtension.class, MockitoExtension.class}) -@Disabled("Temporary disabled to test CI") +@MockitoSettings(strictness = Strictness.LENIENT) class DictionaryImplTest { - private final AemContext context = new AemContext(); + private final AemContext context = new AemContext(); + + @Mock + private TranslationConfig translationConfig; @BeforeEach public void setUp() { context.addModelsForClasses(DictionaryImpl.class); + context.registerService(TranslationConfig.class, translationConfig); + doReturn(Map.of("en", "English", "fr", "French", "de", "German")).when(translationConfig).getLanguages(any(ResourceResolver.class)); + context.registerService(Replicator.class, mock(Replicator.class)); + context.registerInjectActivateService(new DictionaryServiceImpl()); context.load().json("/i18nTestDictionaries.json", "/content/dictionaries"); } @@ -40,7 +55,7 @@ void testGetLanguages() { final Resource testResource = context.currentResource("/content/dictionaries/languages"); if (testResource != null) { - final Dictionary dictionary = testResource.adaptTo(Dictionary.class); + final Dictionary dictionary = context.request().adaptTo(Dictionary.class); if (dictionary != null) { Assert.assertNotNull(dictionary); final List actualLanguages = new ArrayList<>(dictionary.getLanguages()); @@ -59,7 +74,7 @@ void testGetLanguagesString() { final Resource testResource = context.currentResource("/content/dictionaries/languages"); if (testResource != null) { - final Dictionary dictionary = testResource.adaptTo(Dictionary.class); + final Dictionary dictionary = context.request().adaptTo(Dictionary.class); if (dictionary != null) { Assert.assertNotNull(dictionary); final String actualLanguages = dictionary.getLanguageList(); @@ -76,7 +91,7 @@ void testGetLanguagesString() { void testContentIsEditable() { final Resource testResource = context.currentResource("/content/dictionaries/languages"); if (testResource != null) { - final Dictionary dictionary = testResource.adaptTo(Dictionary.class); + final Dictionary dictionary = context.request().adaptTo(Dictionary.class); if (dictionary != null) { Assert.assertTrue(dictionary.isEditable()); } else { @@ -92,7 +107,7 @@ void testAppsIsNotEditable() { context.load().json("/i18nTestDictionaries.json", "/apps/dictionaries"); final Resource testResource = context.currentResource("/apps/dictionaries/languages"); if (testResource != null) { - final Dictionary dictionary = testResource.adaptTo(Dictionary.class); + final Dictionary dictionary = context.request().adaptTo(Dictionary.class); if (dictionary != null) { Assert.assertFalse(dictionary.isEditable()); } else { @@ -108,7 +123,7 @@ void testLibsIsNotEditable() { context.load().json("/i18nTestDictionaries.json", "/libs/dictionaries"); final Resource testResource = context.currentResource("/libs/dictionaries/languages"); if (testResource != null) { - final Dictionary dictionary = testResource.adaptTo(Dictionary.class); + final Dictionary dictionary = context.request().adaptTo(Dictionary.class); if (dictionary != null) { Assert.assertFalse(dictionary.isEditable()); } else { @@ -129,7 +144,7 @@ void getCreated() throws ParseException { final Resource testResource = context.currentResource("/content/dictionaries/languages"); if (testResource != null) { - final Dictionary dictionary = testResource.adaptTo(Dictionary.class); + final Dictionary dictionary = context.request().adaptTo(Dictionary.class); if (dictionary != null) { final Calendar actualCreated = dictionary.getCreated(); Assert.assertTrue(DateUtils.isSameInstant(expectedCreated, actualCreated)); @@ -151,7 +166,7 @@ void testGetLastModifiedWhenNotModified() throws ParseException { final Resource testResource = context.currentResource("/content/dictionaries/languages"); if (testResource != null) { - final Dictionary dictionary = testResource.adaptTo(Dictionary.class); + final Dictionary dictionary = context.request().adaptTo(Dictionary.class); if (dictionary != null) { final Calendar actualCreated = dictionary.getLastModified(); Assert.assertTrue(DateUtils.isSameInstant(expectedCreated, actualCreated)); @@ -173,7 +188,7 @@ void testGetLastModified() throws ParseException { final Resource testResource = context.currentResource("/content/dictionaries/modified"); if (testResource != null) { - final Dictionary dictionary = testResource.adaptTo(Dictionary.class); + final Dictionary dictionary = context.request().adaptTo(Dictionary.class); if (dictionary != null) { final Calendar actualModified = dictionary.getLastModified(); final Calendar actualCreated = dictionary.getCreated(); @@ -187,26 +202,4 @@ void testGetLastModified() throws ParseException { } } - @Test - void testGetFormattedLastModified() throws ParseException { - final String expectedFormattedLastModified = "30-08-2022"; - final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSXXX"); - final String dateInString = "2022-08-24T10:42:50.469+02:00"; - final Date date = simpleDateFormat.parse(dateInString); - final Calendar expectedCreated = Calendar.getInstance(); - expectedCreated.setTime(date); - - final Resource testResource = context.currentResource("/content/dictionaries/modified"); - if (testResource != null) { - final Dictionary dictionary = testResource.adaptTo(Dictionary.class); - if (dictionary != null) { - final String actualFormattedLastModified = dictionary.getLastModifiedFormatted(); - Assert.assertEquals(expectedFormattedLastModified, actualFormattedLastModified); - } else { - Assert.fail("could not adapt resource to dictionary"); - } - } else { - Assert.fail("No resource available"); - } - } } diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/services/impl/DictionaryServiceImplTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/services/impl/DictionaryServiceImplTest.java index d790f21e..1877d6df 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/services/impl/DictionaryServiceImplTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/services/impl/DictionaryServiceImplTest.java @@ -2,10 +2,12 @@ import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import com.adobe.granite.translation.api.TranslationConfig; +import com.day.cq.replication.Replicator; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.testing.mock.sling.ResourceResolverType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -17,11 +19,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.*; @ExtendWith({AemContextExtension.class, MockitoExtension.class}) class DictionaryServiceImplTest { + private final AemContext context = new AemContext(); DictionaryService dictionaryService; @@ -32,6 +34,7 @@ class DictionaryServiceImplTest { @BeforeEach void setup() { translationConfig = context.registerService(TranslationConfig.class, translationConfig); + context.registerService(Replicator.class, mock(Replicator.class)); dictionaryService = context.registerInjectActivateService(new DictionaryServiceImpl()); } diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateDictionaryServletTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateDictionaryServletTest.java index 549e4a4b..f6341cd1 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateDictionaryServletTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateDictionaryServletTest.java @@ -3,6 +3,7 @@ import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import be.orbinson.aem.dictionarytranslator.services.impl.DictionaryServiceImpl; import com.adobe.granite.translation.api.TranslationConfig; +import com.day.cq.replication.Replicator; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; import org.apache.sling.api.resource.Resource; @@ -21,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; @ExtendWith({AemContextExtension.class, MockitoExtension.class}) class CreateDictionaryServletTest { @@ -36,6 +38,7 @@ class CreateDictionaryServletTest { @BeforeEach void beforeEach() { translationConfig = context.registerService(TranslationConfig.class, translationConfig); + context.registerService(Replicator.class, mock(Replicator.class)); dictionaryService = context.registerInjectActivateService(new DictionaryServiceImpl()); servlet = context.registerInjectActivateService(new CreateDictionaryServlet()); diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateLanguageServletTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateLanguageServletTest.java index 9f20b1bc..6203e37b 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateLanguageServletTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateLanguageServletTest.java @@ -3,6 +3,7 @@ import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import be.orbinson.aem.dictionarytranslator.services.impl.DictionaryServiceImpl; import com.adobe.granite.translation.api.TranslationConfig; +import com.day.cq.replication.Replicator; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; import org.apache.sling.api.resource.Resource; @@ -20,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; @ExtendWith({AemContextExtension.class, MockitoExtension.class}) class CreateLanguageServletTest { @@ -36,6 +38,7 @@ class CreateLanguageServletTest { @BeforeEach void beforeEach() { translationConfig = context.registerService(TranslationConfig.class, translationConfig); + context.registerService(Replicator.class, mock(Replicator.class)); dictionaryService = context.registerInjectActivateService(new DictionaryServiceImpl()); servlet = context.registerInjectActivateService(new CreateLanguageServlet()); diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateLabelServletTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateMessageEntryServletTest.java similarity index 96% rename from core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateLabelServletTest.java rename to core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateMessageEntryServletTest.java index 618f99a9..a98f1051 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateLabelServletTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/CreateMessageEntryServletTest.java @@ -3,6 +3,7 @@ import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import be.orbinson.aem.dictionarytranslator.services.impl.DictionaryServiceImpl; import com.adobe.granite.translation.api.TranslationConfig; +import com.day.cq.replication.Replicator; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; import org.apache.sling.api.resource.Resource; @@ -25,12 +26,13 @@ import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; @ExtendWith({AemContextExtension.class, MockitoExtension.class}) -class CreateLabelServletTest { +class CreateMessageEntryServletTest { private final AemContext context = new AemContext(); - CreateLabelServlet servlet; + CreateMessageEntryServlet servlet; DictionaryService dictionaryService; @@ -40,9 +42,10 @@ class CreateLabelServletTest { @BeforeEach void beforeEach() { translationConfig = context.registerService(TranslationConfig.class, translationConfig); + context.registerService(Replicator.class, mock(Replicator.class)); dictionaryService = context.registerInjectActivateService(new DictionaryServiceImpl()); - servlet = context.registerInjectActivateService(new CreateLabelServlet()); + servlet = context.registerInjectActivateService(new CreateMessageEntryServlet()); } @Test diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteDictionaryServletTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteDictionaryServletTest.java index f0faaa41..428d3df7 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteDictionaryServletTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteDictionaryServletTest.java @@ -1,5 +1,7 @@ package be.orbinson.aem.dictionarytranslator.servlets.action; +import be.orbinson.aem.dictionarytranslator.services.impl.DictionaryServiceImpl; +import com.adobe.granite.translation.api.TranslationConfig; import com.day.cq.replication.ReplicationActionType; import com.day.cq.replication.ReplicationException; import com.day.cq.replication.Replicator; @@ -21,8 +23,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; @ExtendWith({AemContextExtension.class, MockitoExtension.class}) class DeleteDictionaryServletTest { @@ -36,6 +37,8 @@ class DeleteDictionaryServletTest { @BeforeEach void beforeEach() { replicator = context.registerService(Replicator.class, replicator); + context.registerService(TranslationConfig.class, mock(TranslationConfig.class)); + context.registerInjectActivateService(new DictionaryServiceImpl()); servlet = context.registerInjectActivateService(new DeleteDictionaryServlet()); } @@ -78,7 +81,7 @@ void deleteMultipleDictionaries() throws ServletException, IOException { context.create().resource("/content/dictionaries/site-c/i18n"); context.request().setMethod("POST"); context.request().setParameterMap(Map.of( - DeleteDictionaryServlet.DICTIONARIES_PARAM, new String[]{"/content/dictionaries/site-a/i18n,/content/dictionaries/site-b/i18n"} + DeleteDictionaryServlet.DICTIONARIES_PARAM, new String[]{"/content/dictionaries/site-a/i18n", "/content/dictionaries/site-b/i18n"} )); servlet.service(context.request(), context.response()); @@ -100,6 +103,6 @@ void deleteNonExistingDictionary() throws ServletException, IOException { servlet.service(context.request(), context.response()); assertNotNull(context.resourceResolver().getResource("/content/dictionaries/site-a/i18n")); - assertEquals(HttpServletResponse.SC_NOT_FOUND, context.response().getStatus()); + assertEquals(HttpServletResponse.SC_BAD_REQUEST, context.response().getStatus()); } } diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLanguageServletTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLanguageServletTest.java index 39c7a192..2e5e41b8 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLanguageServletTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLanguageServletTest.java @@ -1,5 +1,7 @@ package be.orbinson.aem.dictionarytranslator.servlets.action; +import be.orbinson.aem.dictionarytranslator.services.impl.DictionaryServiceImpl; +import com.adobe.granite.translation.api.TranslationConfig; import com.day.cq.replication.ReplicationActionType; import com.day.cq.replication.ReplicationException; import com.day.cq.replication.Replicator; @@ -18,11 +20,11 @@ import java.io.IOException; import java.util.Map; +import static com.day.cq.commons.jcr.JcrConstants.JCR_LANGUAGE; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; @ExtendWith({AemContextExtension.class, MockitoExtension.class}) class DeleteLanguageServletTest { @@ -36,6 +38,8 @@ class DeleteLanguageServletTest { @BeforeEach void beforeEach() { replicator = context.registerService(Replicator.class, replicator); + context.registerService(TranslationConfig.class, mock(TranslationConfig.class)); + context.registerInjectActivateService(new DictionaryServiceImpl()); servlet = context.registerInjectActivateService(new DeleteLanguageServlet()); } @@ -51,8 +55,8 @@ void doPostWithoutParams() throws ServletException, IOException { @Test void deleteExistingLanguage() throws ServletException, IOException, ReplicationException { - context.create().resource("/content/dictionaries/i18n/en"); - context.create().resource("/content/dictionaries/i18n/fr"); + context.create().resource("/content/dictionaries/i18n/en", Map.of(JCR_LANGUAGE, "en")); + context.create().resource("/content/dictionaries/i18n/fr", Map.of(JCR_LANGUAGE, "fr")); context.request().setMethod("POST"); context.request().setParameterMap(Map.of( DeleteLanguageServlet.DICTIONARY_PARAM, new String[]{"/content/dictionaries/i18n"}, @@ -65,7 +69,7 @@ void deleteExistingLanguage() throws ServletException, IOException, ReplicationE verify(replicator).replicate(any(Session.class), eq(ReplicationActionType.DEACTIVATE), eq("/content/dictionaries/i18n/fr")); assertNotNull(context.resourceResolver().getResource("/content/dictionaries/i18n/en")); - verify(replicator,times(0)).replicate(any(Session.class), eq(ReplicationActionType.DEACTIVATE), eq("/content/dictionaries/i18n/en")); + verify(replicator, times(0)).replicate(any(Session.class), eq(ReplicationActionType.DEACTIVATE), eq("/content/dictionaries/i18n/en")); assertEquals(HttpServletResponse.SC_OK, context.response().getStatus()); } @@ -76,7 +80,7 @@ void deleteNonExistingLanguage() throws ServletException, IOException { context.request().setMethod("POST"); context.request().setParameterMap(Map.of( DeleteLanguageServlet.DICTIONARY_PARAM, new String[]{"/content/dictionaries/i18n"}, - DeleteLanguageServlet.LANGUAGE_PARAM,"fr" + DeleteLanguageServlet.LANGUAGE_PARAM, "fr" )); servlet.service(context.request(), context.response()); diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLabelServletTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteMessageEntryServletTest.java similarity index 59% rename from core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLabelServletTest.java rename to core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteMessageEntryServletTest.java index 8c5c6ee8..e7937f6f 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteLabelServletTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/DeleteMessageEntryServletTest.java @@ -1,5 +1,6 @@ package be.orbinson.aem.dictionarytranslator.servlets.action; +import be.orbinson.aem.dictionarytranslator.services.impl.CombiningMessageEntryResourceProvider; import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import be.orbinson.aem.dictionarytranslator.services.impl.DictionaryServiceImpl; import com.adobe.granite.translation.api.TranslationConfig; @@ -28,11 +29,11 @@ import static org.mockito.Mockito.verify; @ExtendWith({AemContextExtension.class, MockitoExtension.class}) -class DeleteLabelServletTest { +class DeleteMessageEntryServletTest { private final AemContext context = new AemContext(ResourceResolverType.JCR_MOCK); - DeleteLabelServlet servlet; + DeleteMessageEntryServlet servlet; DictionaryService dictionaryService; @@ -50,7 +51,7 @@ void beforeEach() { dictionaryService = context.registerInjectActivateService(new DictionaryServiceImpl()); - servlet = context.registerInjectActivateService(new DeleteLabelServlet()); + servlet = context.registerInjectActivateService(new DeleteMessageEntryServlet()); } @Test @@ -67,7 +68,7 @@ void doPostWithoutParams() throws ServletException, IOException { void deleteLabelWithNonExistingKey() throws ServletException, IOException { context.request().setMethod("POST"); context.request().setParameterMap(Map.of( - DeleteLabelServlet.LABEL_PARAM, "/content/dictionaries/i18n/en/apple" + DeleteMessageEntryServlet.ITEM_PARAM, "/content/dictionaries/i18n/en/apple" )); servlet.service(context.request(), context.response()); @@ -77,23 +78,34 @@ void deleteLabelWithNonExistingKey() throws ServletException, IOException { @Test void deleteExistingLabel() throws ServletException, IOException, ReplicationException { - context.create().resource("/mnt/dictionaries/i18n/appel", - "labelPaths", new String[]{"/content/dictionaries/i18n/appel/fr", "/content/dictionaries/i18n/appel/en"} - ); - context.create().resource("/content/dictionaries/i18n/peer"); + context.create().resource("/content/dictionaries/i18n/en/appel"); + context.create().resource("/content/dictionaries/i18n/fr/appel"); + context.create().resource("/mnt/dictionaries/content/dictionaries/i18n/appel", + CombiningMessageEntryResourceProvider.MESSAGE_ENTRY_PATHS, new String[] {"/content/dictionaries/i18n/en/appel", "/content/dictionaries/i18n/fr/appel"}); + + context.create().resource("/content/dictionaries/i18n/en/peer"); + context.create().resource("/content/dictionaries/i18n/fr/peer"); + context.create().resource("/mnt/dictionaries/content/dictionaries/i18n/peer", + CombiningMessageEntryResourceProvider.MESSAGE_ENTRY_PATHS, new String[] {"/content/dictionaries/i18n/en/peer", "/content/dictionaries/i18n/fr/peer"}); + + context.create().resource("/content/dictionaries/i18n/en/framboos"); + context.create().resource("/content/dictionaries/i18n/fr/framboos"); + context.create().resource("/mnt/dictionaries/content/dictionaries/i18n/framboos", + CombiningMessageEntryResourceProvider.MESSAGE_ENTRY_PATHS, new String[] {"/content/dictionaries/i18n/en/framboos", "/content/dictionaries/i18n/fr/framboos"}); + context.request().setMethod("POST"); context.request().setParameterMap(Map.of( - DeleteLabelServlet.LABEL_PARAM, new String[]{"/mnt/dictionaries/i18n/appel"} + DeleteMessageEntryServlet.ITEM_PARAM, new String[]{"/mnt/dictionaries/content/dictionaries/i18n/appel"} )); servlet.service(context.request(), context.response()); - assertNull(context.resourceResolver().getResource("/content/dictionaries/i18n/appel")); - verify(replicator).replicate(any(Session.class), eq(ReplicationActionType.DEACTIVATE), eq("/content/dictionaries/i18n/appel/fr")); - verify(replicator).replicate(any(Session.class), eq(ReplicationActionType.DEACTIVATE), eq("/content/dictionaries/i18n/appel/en")); + assertNull(context.resourceResolver().getResource("/content/dictionaries/i18n/en/appel")); + verify(replicator).replicate(any(Session.class), eq(ReplicationActionType.DEACTIVATE), eq("/content/dictionaries/i18n/en/appel")); + verify(replicator).replicate(any(Session.class), eq(ReplicationActionType.DEACTIVATE), eq("/content/dictionaries/i18n/fr/appel")); - assertNotNull(context.resourceResolver().getResource("/content/dictionaries/i18n/peer")); - verify(replicator, times(0)).replicate(any(Session.class), eq(ReplicationActionType.DEACTIVATE), eq("/content/dictionaries/i18n/peer")); + assertNotNull(context.resourceResolver().getResource("/content/dictionaries/i18n/en/peer")); + verify(replicator, times(0)).replicate(any(Session.class), eq(ReplicationActionType.DEACTIVATE), eq("/content/dictionaries/i18n/en/peer")); assertEquals(HttpServletResponse.SC_OK, context.response().getStatus()); } @@ -101,11 +113,23 @@ void deleteExistingLabel() throws ServletException, IOException, ReplicationExce @Test void deleteMultipleLabels() throws ServletException, IOException { context.create().resource("/content/dictionaries/i18n/en/appel"); + context.create().resource("/content/dictionaries/i18n/fr/appel"); + context.create().resource("/mnt/dictionaries/content/dictionaries/i18n/appel", + CombiningMessageEntryResourceProvider.MESSAGE_ENTRY_PATHS, new String[] {"/content/dictionaries/i18n/en/appel", "/content/dictionaries/i18n/fr/appel"}); + context.create().resource("/content/dictionaries/i18n/en/peer"); + context.create().resource("/content/dictionaries/i18n/fr/peer"); + context.create().resource("/mnt/dictionaries/content/dictionaries/i18n/peer", + CombiningMessageEntryResourceProvider.MESSAGE_ENTRY_PATHS, new String[] {"/content/dictionaries/i18n/en/peer", "/content/dictionaries/i18n/fr/peer"}); + context.create().resource("/content/dictionaries/i18n/en/framboos"); + context.create().resource("/content/dictionaries/i18n/fr/framboos"); + context.create().resource("/mnt/dictionaries/content/dictionaries/i18n/framboos", + CombiningMessageEntryResourceProvider.MESSAGE_ENTRY_PATHS, new String[] {"/content/dictionaries/i18n/en/framboos", "/content/dictionaries/i18n/fr/framboos"}); + context.request().setMethod("POST"); context.request().setParameterMap(Map.of( - DeleteLabelServlet.LABEL_PARAM, new String[]{"/content/dictionaries/i18n/en/appel", "/content/dictionaries/i18n/en/peer"} + DeleteMessageEntryServlet.ITEM_PARAM, new String[]{"/mnt/dictionaries/content/dictionaries/i18n/appel", "/mnt/dictionaries/content/dictionaries/i18n/peer"} )); servlet.service(context.request(), context.response()); @@ -121,7 +145,7 @@ void deleteNonExistingLabel() throws ServletException, IOException { context.create().resource("/content/dictionaries/i18n/en/appel"); context.request().setMethod("POST"); context.request().setParameterMap(Map.of( - DeleteLabelServlet.LABEL_PARAM, new String[]{"/content/dictionaries/i18n/fr/peer"} + DeleteMessageEntryServlet.ITEM_PARAM, new String[]{"/content/dictionaries/i18n/fr/peer"} )); servlet.service(context.request(), context.response()); diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ExportDictionaryServletTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ExportDictionaryServletTest.java index 7fd19b5b..92eda872 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ExportDictionaryServletTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ExportDictionaryServletTest.java @@ -1,35 +1,30 @@ package be.orbinson.aem.dictionarytranslator.servlets.action; -import com.day.cq.commons.jcr.JcrConstants; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; +import org.junit.Ignore; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import javax.jcr.Session; import javax.servlet.http.HttpServletResponse; - -import java.util.HashMap; import java.util.Map; import java.util.function.Function; -import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.JCR_BASENAME; -import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.MIX_LANGUAGE; -import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.SLING_KEY; -import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.SLING_MESSAGE; -import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.SLING_MESSAGEENTRY; -import static org.apache.jackrabbit.JcrConstants.JCR_LANGUAGE; -import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES; -import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; +import static be.orbinson.aem.dictionarytranslator.utils.DictionaryConstants.*; +import static org.apache.jackrabbit.JcrConstants.*; import static org.apache.sling.jcr.resource.api.JcrResourceConstants.NT_SLING_FOLDER; import static org.apache.sling.jcr.resource.api.JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY; import static org.junit.jupiter.api.Assertions.assertEquals; +// TODO: should be recreated with the dictionary service in mind @ExtendWith(AemContextExtension.class) +@Disabled("TODO: should be recreated with the dictionary service in mind") class ExportDictionaryServletTest { private final AemContext context = new AemContext(); @@ -141,7 +136,7 @@ private void createLanguageResource(String language, String label, String transl if (session != null) { String path = "/test/path"; Resource languageResource; - if (resourceResolver.getResource(path + "/" + language) == null){ + if (resourceResolver.getResource(path + "/" + language) == null) { languageResource = resourceResolver.create(resourceResolver.getResource(path), language, Map.of( JCR_PRIMARYTYPE, NT_SLING_FOLDER, JCR_LANGUAGE, language, diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ImportDictionaryServletTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ImportDictionaryServletTest.java index ca9883a5..b8df33c9 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ImportDictionaryServletTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ImportDictionaryServletTest.java @@ -8,6 +8,7 @@ import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -20,7 +21,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +// TODO update to use dicationary service @ExtendWith(AemContextExtension.class) +@Disabled("Update to use dictionary service") class ImportDictionaryServletTest { private ImportDictionaryServlet importTranslation; diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateLabelServletTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateMessageEntryServletTest.java similarity index 93% rename from core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateLabelServletTest.java rename to core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateMessageEntryServletTest.java index f546c209..3974564e 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateLabelServletTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/ReplicateMessageEntryServletTest.java @@ -35,16 +35,17 @@ import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; @ExtendWith({AemContextExtension.class, MockitoExtension.class}) -class ReplicateLabelServletTest { +class ReplicateMessageEntryServletTest { private final AemContext context = new AemContext(ResourceResolverType.JCR_MOCK); - @NotNull ReplicateLabelServlet servlet; + @NotNull + ReplicateMessageEntryServlet servlet; - @NotNull CreateLabelServlet create; + @NotNull + CreateMessageEntryServlet create; DictionaryService dictionaryService; @@ -61,11 +62,11 @@ class ReplicateLabelServletTest { @BeforeEach void beforeEach() { translationConfig = context.registerService(TranslationConfig.class, translationConfig); - dictionaryService = context.registerInjectActivateService(new DictionaryServiceImpl()); replicator = context.registerService(Replicator.class, replicator); + dictionaryService = context.registerInjectActivateService(new DictionaryServiceImpl()); resourceResolver = context.registerService(ResourceResolver.class, resourceResolver); - servlet = context.registerInjectActivateService(new ReplicateLabelServlet()); - create = context.registerInjectActivateService(new CreateLabelServlet()); + servlet = context.registerInjectActivateService(new ReplicateMessageEntryServlet()); + create = context.registerInjectActivateService(new CreateMessageEntryServlet()); } @Test @@ -104,9 +105,6 @@ void publishExistingLabel() throws ServletException, IOException, ReplicationExc Iterator iterator = resources.iterator(); SlingHttpServletRequest request = Mockito.spy(context.request()); - when(servlet.getResourceResolver(request)).thenReturn(resourceResolver); - - when(servlet.getResources(resourceResolver, anyString(), anyString())).thenReturn(iterator); servlet.service(context.request(), context.response()); diff --git a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateLabelServletTest.java b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateMessageEntryServletTest.java similarity index 92% rename from core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateLabelServletTest.java rename to core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateMessageEntryServletTest.java index c549b1ff..85084822 100644 --- a/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateLabelServletTest.java +++ b/core/src/test/java/be/orbinson/aem/dictionarytranslator/servlets/action/UpdateMessageEntryServletTest.java @@ -3,6 +3,7 @@ import be.orbinson.aem.dictionarytranslator.services.DictionaryService; import be.orbinson.aem.dictionarytranslator.services.impl.DictionaryServiceImpl; import com.adobe.granite.translation.api.TranslationConfig; +import com.day.cq.replication.Replicator; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; import org.apache.sling.api.resource.Resource; @@ -25,13 +26,14 @@ import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; @ExtendWith({AemContextExtension.class, MockitoExtension.class}) -class UpdateLabelServletTest { +class UpdateMessageEntryServletTest { private final AemContext context = new AemContext(); - UpdateLabelServlet servlet; + UpdateMessageEntryServlet servlet; DictionaryService dictionaryService; @@ -41,9 +43,10 @@ class UpdateLabelServletTest { @BeforeEach void beforeEach() { translationConfig = context.registerService(TranslationConfig.class, translationConfig); + context.registerService(Replicator.class, mock(Replicator.class)); dictionaryService = context.registerInjectActivateService(new DictionaryServiceImpl()); - servlet = context.registerInjectActivateService(new UpdateLabelServlet()); + servlet = context.registerInjectActivateService(new UpdateMessageEntryServlet()); } @Test @@ -57,7 +60,7 @@ void doPostWithoutParams() throws ServletException, IOException { } @Test - void updateLabelInNonExistingDictionary() throws ServletException, IOException { + void updateCombiningMessageEntryInNonExistingDictionary() throws ServletException, IOException { context.request().setMethod("POST"); context.request().setParameterMap(Map.of( "dictionary", "/content/dictionaries/i18n", diff --git a/core/src/test/resources/i18nTestDictionaries.json b/core/src/test/resources/i18nTestDictionaries.json index 817ebcd4..3d8fef8b 100644 --- a/core/src/test/resources/i18nTestDictionaries.json +++ b/core/src/test/resources/i18nTestDictionaries.json @@ -23,10 +23,10 @@ "sling:message": "two" } }, - "fr": { + "French": { "jcr:language": "fr", "jcr:mixinTypes": "mix:language", - "jcr:primaryType": "sling:Folder", + "jcr:primaryType": "nt:folder", "hello": { "jcr:primaryType": "sling:MessageEntry", "sling:key": "hello", diff --git a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/components/label/label.html b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/components/combining-message-entry/combining-message-entry.html similarity index 100% rename from ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/components/label/label.html rename to ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/components/combining-message-entry/combining-message-entry.html diff --git a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/components/dictionary/dictionary.html b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/components/dictionary/dictionary.html index 84b79571..cdba3365 100644 --- a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/components/dictionary/dictionary.html +++ b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/components/dictionary/dictionary.html @@ -1,7 +1,7 @@ ${resource.path} @@ -15,5 +15,5 @@ size="M"> - ${model.labelCount} + ${model.keyCount} diff --git a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/dictionaries/.content.xml b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/dictionaries/.content.xml index ca276961..53f78a63 100644 --- a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/dictionaries/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/dictionaries/.content.xml @@ -8,7 +8,7 @@ jcr:title="Dictionaries" sling:resourceType="granite/ui/components/shell/collectionpage" sling:vanityPath="/tools/translation/dictionaries" - pageURITemplate="/tools/translation/dictionaries/labels.html{+id}" + pageURITemplate="/tools/translation/dictionaries/message-entries.html{+id}" modeGroup="dictionary-list" targetCollection=".dictionary-list" consoleId="translation"> @@ -42,7 +42,7 @@ variant="actionBar" text="Edit"> + href.uritemplate="/tools/translation/dictionaries/message-entries.html{+item}"/> + src.uritemplate="/apps/aem-dictionary-translator/content/granite/dialog/dictionary/delete.html{?item*}"/> @@ -159,9 +159,9 @@ jcr:primaryType="nt:unstructured" jcr:title="Editable" sortable="true"/> - diff --git a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/dictionaries/labels/.content.xml b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/dictionaries/message-entries/.content.xml similarity index 91% rename from ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/dictionaries/labels/.content.xml rename to ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/dictionaries/message-entries/.content.xml index 9e2f84c8..1319173f 100644 --- a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/dictionaries/labels/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/dictionaries/message-entries/.content.xml @@ -7,7 +7,7 @@ jcr:mixinTypes="[sling:VanityPath]" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/shell/collectionpage" - sling:vanityPath="/tools/translation/dictionaries/labels.html" + sling:vanityPath="/tools/translation/dictionaries/message-entries.html" jcr:title="Translate Dictionary" backHref="/tools/translation/dictionaries.html" modeGroup="language-list" @@ -24,21 +24,21 @@ sling:resourceType="aem-dictionary-translator/datasource/breadcrumbs"/> - + text="Create Key"> + src.uritemplate="/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/create.html${requestPathInfo.suffix}"/> - + + src.uritemplate="/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/update.html{?item*}"/> + src.uritemplate="/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/delete.html{?item*}"/> + src.uritemplate="/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/publish.html{?item*}"/> + sling:resourceType="aem-dictionary-translator/datasource/combining-message-entry"/> diff --git a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/delete/.content.xml b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/delete/.content.xml index 6215b66b..32dfe447 100644 --- a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/delete/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/delete/.content.xml @@ -23,7 +23,7 @@ jcr:primaryType="nt:unstructured" sling:resourceType="aem-dictionary-translator/servlet/action/delete-dictionary" granite:id="delete-dictionary-form" - action="${requestPathInfo.resourcePath}?dictionaries=${param.dictionaries}" + action="${requestPathInfo.resourcePath}?${querystring}" foundationForm="true" method="post" style="vertical"> diff --git a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/.content.xml b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/.content.xml similarity index 100% rename from ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/.content.xml rename to ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/.content.xml diff --git a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/create/.content.xml b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/create/.content.xml similarity index 91% rename from ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/create/.content.xml rename to ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/create/.content.xml index cca2dfee..0277671a 100644 --- a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/create/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/create/.content.xml @@ -5,7 +5,7 @@ xmlns:granite="http://www.adobe.com/jcr/granite/1.0" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/dialog" - jcr:title="Create Label"> + jcr:title="Create Key">
+ fieldLabel="Key"/> @@ -71,6 +71,6 @@ text="Create" type="submit" variant="primary" - formId="create-label-form"/> + formId="create-message-entry-form"/> diff --git a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/delete/.content.xml b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/delete/.content.xml similarity index 90% rename from ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/delete/.content.xml rename to ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/delete/.content.xml index cec77a4b..6b43c52b 100644 --- a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/delete/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/delete/.content.xml @@ -5,7 +5,7 @@ xmlns:granite="http://www.adobe.com/jcr/granite/1.0" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/dialog" - jcr:title="Delete Label"> + jcr:title="Delete Key"> @@ -61,6 +61,6 @@ text="Delete" type="submit" variant="primary" - formId="delete-label-form"/> + formId="delete-message-entry-form"/> diff --git a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/publish/.content.xml b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/publish/.content.xml similarity index 90% rename from ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/publish/.content.xml rename to ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/publish/.content.xml index 7e023824..9e86f710 100644 --- a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/publish/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/publish/.content.xml @@ -5,7 +5,7 @@ xmlns:granite="http://www.adobe.com/jcr/granite/1.0" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/dialog" - jcr:title="Publish Label"> + jcr:title="Publish Key"> @@ -61,6 +61,6 @@ text="Publish" type="submit" variant="primary" - formId="publish-label-form"/> + formId="publish-message-entry-form"/> diff --git a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/update/.content.xml b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/update/.content.xml similarity index 89% rename from ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/update/.content.xml rename to ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/update/.content.xml index cf187bca..d5191c66 100644 --- a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/label/update/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/granite/dialog/dictionary/key/update/.content.xml @@ -5,7 +5,7 @@ xmlns:granite="http://www.adobe.com/jcr/granite/1.0" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/dialog" - jcr:title="Edit Label"> + jcr:title="Edit Key"> + sling:resourceType="aem-dictionary-translator/datasource/combining-message-entry"/> @@ -57,6 +57,6 @@ text="Save" type="submit" variant="primary" - formId="update-label-form"/> + formId="update-message-entry-form"/> diff --git a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/language/export/.content.xml b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/language/export/.content.xml index 410097fb..bb0ef4f7 100644 --- a/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/language/export/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/aem-dictionary-translator/content/language/export/.content.xml @@ -5,7 +5,7 @@ xmlns:granite="http://www.adobe.com/jcr/granite/1.0" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/dialog" - jcr:title="Export labels" + jcr:title="Export Dictionary" granite:class="coral3-Dialog--detailView">