Skip to content

Commit

Permalink
#26002 Handling the sync process of languages
Browse files Browse the repository at this point in the history
  • Loading branch information
jgambarios committed Sep 20, 2023
1 parent a32d925 commit f0b192b
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ public interface PushAnalysisService {
* Analyzes a local file or folder and generates a list of push analysis results.
*
* @param localFileOrFolder the local file or folder to analyze
* @param allowRemove whether to allow removals
* @param provider the content fetcher used to retrieve content
* @param comparator the content comparator used to compare content
* @return a list of push analysis results
*/
<T> List<PushAnalysisResult<T>> analyze(File localFileOrFolder,
boolean allowRemove,
ContentFetcher<T> provider,
ContentComparator<T> comparator);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,19 @@ public class PushAnalysisServiceImpl implements PushAnalysisService {
* by comparing the content of the local files with the content of the server files using the
* specified content comparator.
*
* @param localFile the local file or directory to be analyzed
* @param provider the content fetcher that provides the server files content
* @param comparator the content comparator used to compare the content of local and server
* files
* @param localFile the local file or directory to be analyzed
* @param allowRemove whether to allow removals
* @param provider the content fetcher that provides the server files content
* @param comparator the content comparator used to compare the content of local and server
* files
* @return a list of push analysis results which include updates, additions, and removals found
* during the analysis
*/
@ActivateRequestContext
public <T> List<PushAnalysisResult<T>> analyze(File localFile,
ContentFetcher<T> provider,
ContentComparator<T> comparator) {
public <T> List<PushAnalysisResult<T>> analyze(final File localFile,
final boolean allowRemove,
final ContentFetcher<T> provider,
final ContentComparator<T> comparator) {

List<File> localContents = readLocalContents(localFile);
List<T> serverContents = provider.fetch();
Expand All @@ -55,10 +57,13 @@ public <T> List<PushAnalysisResult<T>> analyze(File localFile,
checkLocal(localContents, serverContents, comparator)
);

// We don't need to check the server against local files if we are dealing with a single file
if (localFile.isDirectory()) {
// Checking server files against local files to find removals
results.addAll(checkServer(localContents, serverContents, comparator));
if (allowRemove) {

// We don't need to check the server against local files if we are dealing with a single file
if (localFile.isDirectory()) {
// Checking server files against local files to find removals
results.addAll(checkServerForRemovals(localContents, serverContents, comparator));
}
}

return results;
Expand Down Expand Up @@ -125,7 +130,7 @@ private <T> List<PushAnalysisResult<T>> checkLocal(List<File> localFiles,
* @param comparator a content comparator used to compare server content with local files
* @return a list of PushAnalysisResult objects representing the analysis results
*/
private <T> List<PushAnalysisResult<T>> checkServer(List<File> localFiles,
private <T> List<PushAnalysisResult<T>> checkServerForRemovals(List<File> localFiles,
List<T> serverContents, ContentComparator<T> comparator) {

if (serverContents.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,17 @@ public <T> void push(final File localFileOrFolder, final PushOptions options,
* Analyzes the push data for a local file or folder.
*
* @param localFileOrFolder the local file or folder to analyze
* @param options the push options
* @param output the output option mixin to use for displaying progress
* @param provider the content fetcher used to fetch content for analysis
* @param comparator the content comparator used to compare content for analysis
* @return a pair containing the list of push analysis results and the push analysis summary
* @throws PushException if an error occurs during the analysis
*/
private <T> Pair<List<PushAnalysisResult<T>>, PushAnalysisSummary<T>> analyze(
final File localFileOrFolder, final OutputOptionMixin output,
final File localFileOrFolder,
final PushOptions options,
final OutputOptionMixin output,
final ContentFetcher<T> provider,
final ContentComparator<T> comparator) {

Expand All @@ -128,6 +131,7 @@ private <T> Pair<List<PushAnalysisResult<T>>, PushAnalysisSummary<T>> analyze(
// Analyzing what push operations need to be performed
return pushAnalysisService.analyze(
localFileOrFolder,
options.allowRemove(),
provider,
comparator
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package com.dotcms.cli.command.language;

import com.dotcms.api.LanguageAPI;
import com.dotcms.api.client.push.PushService;
import com.dotcms.api.client.push.language.LanguageComparator;
import com.dotcms.api.client.push.language.LanguageFetcher;
import com.dotcms.api.client.push.language.LanguagePushHandler;
import com.dotcms.cli.command.DotCommand;
import com.dotcms.cli.command.DotPush;
import com.dotcms.cli.common.FormatOptionMixin;
import com.dotcms.cli.common.OutputOptionMixin;
import com.dotcms.cli.common.WorkspaceMixin;
import com.dotcms.cli.common.PushMixin;
import com.dotcms.common.WorkspaceManager;
import com.dotcms.model.ResponseEntityView;
import com.dotcms.model.config.Workspace;
import com.dotcms.model.language.Language;
import com.dotcms.model.push.PushOptions;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
Expand All @@ -21,117 +27,131 @@
import picocli.CommandLine;
import picocli.CommandLine.ExitCode;

/**
* The LanguagePush class represents a command that allows pushing languages to the server. It
* provides functionality to push a language file or folder path, or create a new language by
* providing a language ISO code.
*/
@ActivateRequestContext
@CommandLine.Command(
name = LanguagePush.NAME,
header = "@|bold,blue Push a language|@",
description = {
" Save or update a language given a Language object (in JSON or YML format) or iso (e.g.: en-us)",
" Push a language given a Language object (in JSON or YML format) or iso (e.g.: en-us)",
" If no file is specified, a new language will be created using the iso provided.",
"This command enables the pushing of languages to the server. It accommodates the "
+ "specification of either a language file or a folder path. In addition to "
+ "these options, it also facilitates the creation of a new language through "
+ "the provision of a language iso code (e.g.: en-us).",
"" // empty string to add a new line
}
)
/**
* Command to push a language given a Language object (in JSON or YML format) or iso code (e.g.: en-us)
* @author nollymar
*/
public class LanguagePush extends AbstractLanguageCommand implements Callable<Integer>, DotCommand {
public class LanguagePush extends AbstractLanguageCommand implements Callable<Integer>, DotCommand,
DotPush {

static final String NAME = "push";

static final String LANGUAGES_PUSH_MIXIN = "languagesPushMixin";

@CommandLine.Mixin
PushMixin pushMixin;

@CommandLine.Mixin(name = LANGUAGES_PUSH_MIXIN)
LanguagesPushMixin languagesPushMixin;

@CommandLine.Mixin(name = "format")
FormatOptionMixin formatOption;

@CommandLine.Mixin(name = "workspace")
WorkspaceMixin workspaceMixin;
@CommandLine.Option(names = {"--byIso"}, description =
"Code to be used to create a new language. "
+ "Used when no file is specified. For example: en-us")
String languageIso;

@Inject
WorkspaceManager workspaceManager;

@CommandLine.Option(names = {"--byIso"}, description = "Code to be used to create a new language. Used when no file is specified. For example: en-us")
String languageIso;
@Inject
PushService pushService;

@CommandLine.Option(names = {"-f", "--file"}, description = "The json/yml formatted content-type descriptor file to be pushed. ")
File file;
@Inject
LanguageFetcher languageProvider;

@Inject
LanguageComparator languageComparator;

@Inject
LanguagePushHandler languagePushHandler;

@CommandLine.Spec
CommandLine.Model.CommandSpec spec;

@Override
public Integer call() throws Exception {

// Checking for unmatched arguments
output.throwIfUnmatchedArguments(spec.commandLine());
// When calling from the global push we should avoid the validation of the unmatched
// arguments as we may send arguments meant for other push subcommands
if (!pushMixin.noValidateUnmatchedArguments) {
// Checking for unmatched arguments
output.throwIfUnmatchedArguments(spec.commandLine());
}

// Make sure the path is within a workspace
final Optional<Workspace> workspace = workspaceManager.findWorkspace(
this.getPushMixin().path.toPath());
if (workspace.isEmpty()) {
throw new IllegalArgumentException(
String.format("No valid workspace found at path: [%s]",
this.getPushMixin().path.toPath()));
}

final LanguageAPI languageAPI = clientFactory.getClient(LanguageAPI.class);

File inputFile = this.file;
File inputFile = this.getPushMixin().path;

if (null == inputFile && StringUtils.isEmpty(languageIso)) {
output.error("You must specify an iso code or file to create a new language.");
output.error("You must specify an iso code or file or folder to push a languages.");
return ExitCode.USAGE;
}

final ObjectMapper objectMapper = formatOption.objectMapper(inputFile);

ResponseEntityView<Language> responseEntityView;
if (null != inputFile) {
final Optional<Workspace> workspace = workspaceManager.findWorkspace(workspaceMixin.workspace());
if(workspace.isPresent() && !inputFile.isAbsolute()){

if (!inputFile.isAbsolute()) {
inputFile = Path.of(workspace.get().languages().toString(), inputFile.getName()).toFile();
}
if (!inputFile.exists() || !inputFile.canRead()) {
throw new IOException(String.format(
"Unable to read the input file [%s] check that it does exist and that you have read permissions on it.",
inputFile)
"Unable to access the path [%s] check that it does exist and that you have "
+ "read permissions on it.", inputFile)
);
}

final Language language = objectMapper.readValue(inputFile, Language.class);
responseEntityView = pushLanguageByFile(languageAPI, language);

} else {
responseEntityView = pushLanguageByIsoCode(languageAPI);
}

final Language response = responseEntityView.entity();
output.info(objectMapper.writeValueAsString(response));

return CommandLine.ExitCode.OK;

}



private ResponseEntityView<Language> pushLanguageByFile(final LanguageAPI languageAPI, final Language language) {

final String languageId = language.id().map(String::valueOf).orElse("");
final ResponseEntityView<Language> responseEntityView;
// To make sure that if the user is passing a directory we use the languages folder
if (inputFile.isDirectory()) {
inputFile = workspace.get().languages().toFile();
}

final String isoCode = language.isoCode();
language.withLanguageCode(isoCode.split("-")[0]);
// Execute the push
pushService.push(
inputFile,
PushOptions.builder().
failFast(pushMixin.failFast).
allowRemove(languagesPushMixin.removeLanguages).
maxRetryAttempts(pushMixin.retryAttempts).
dryRun(pushMixin.dryRun).
build(),
output,
languageProvider,
languageComparator,
languagePushHandler
);

if (isoCode.split("-").length > 1) {
language.withCountryCode(isoCode.split("-")[1]);
} else {
language.withCountryCode("");
}
var responseEntityView = pushLanguageByIsoCode(languageAPI);
final Language response = responseEntityView.entity();

output.info(String.format("Attempting to save language with code @|bold,green [%s]|@",language.languageCode().get()));

if (StringUtils.isNotBlank(languageId)){
output.info(String.format("The id @|bold,green [%s]|@ provided in the language file will be used for look-up.", languageId));
responseEntityView = languageAPI.update(
languageId, Language.builder().from(language).id(Optional.empty()).build());
} else {
output.info("The language file @|bold did not|@ provide a language id. ");
responseEntityView = languageAPI.create(Language.builder().from(language).id(Optional.empty()).build());
final ObjectMapper objectMapper = formatOption.objectMapper();
output.info(objectMapper.writeValueAsString(response));
}

output.info(String.format("Language with code @|bold,green [%s]|@ successfully pushed.",language.languageCode().get()));

return responseEntityView;

return CommandLine.ExitCode.OK;
}

private ResponseEntityView<Language> pushLanguageByIsoCode(final LanguageAPI languageAPI) {
Expand All @@ -155,4 +175,14 @@ public OutputOptionMixin getOutput() {
return output;
}

@Override
public PushMixin getPushMixin() {
return pushMixin;
}

@Override
public Optional<String> getCustomMixinName() {
return Optional.of(LANGUAGES_PUSH_MIXIN);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.dotcms.cli.command.language;

import picocli.CommandLine;

public class LanguagesPushMixin {

@CommandLine.Option(names = {"-rf", "--removeLanguages"}, defaultValue = "false",
description =
"When this option is enabled, the push process allows the deletion of languages in the remote server. "
+ "By default, this option is disabled, and languages will not be removed on the remote server.")
public boolean removeLanguages;

}

0 comments on commit f0b192b

Please sign in to comment.