diff --git a/stub-server/src/main/java/org/zanata/rest/service/MockFileResource.java b/stub-server/src/main/java/org/zanata/rest/service/MockFileResource.java index f4c262a1..1327513c 100644 --- a/stub-server/src/main/java/org/zanata/rest/service/MockFileResource.java +++ b/stub-server/src/main/java/org/zanata/rest/service/MockFileResource.java @@ -23,12 +23,8 @@ import java.io.IOException; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.stream.Collectors; import javax.ws.rs.Path; import javax.ws.rs.WebApplicationException; @@ -40,19 +36,19 @@ import org.jboss.resteasy.annotations.providers.multipart.MultipartForm; import org.zanata.adapter.po.PoWriter2; import org.zanata.common.ContentState; +import org.zanata.common.FileTypeInfo; import org.zanata.common.DocumentType; import org.zanata.common.LocaleId; import org.zanata.common.ProjectType; import org.zanata.rest.DocumentFileUploadForm; import org.zanata.rest.StringSet; import org.zanata.rest.dto.ChunkUploadResponse; -import org.zanata.rest.dto.Project; import org.zanata.rest.dto.resource.Resource; import org.zanata.rest.dto.resource.TextFlow; import org.zanata.rest.dto.resource.TextFlowTarget; import org.zanata.rest.dto.resource.TranslationsResource; -import static org.zanata.common.ProjectType.Podir; +import static org.zanata.common.ProjectType.fileProjectSourceDocTypes; /** * @author Patrick Huang > genericEntity = - new GenericEntity>(ProjectType.fileProjectSourceDocTypes()) {}; + new GenericEntity>(fileProjectSourceDocTypes()) {}; + return Response.ok(genericEntity).build(); + } + + @Override + public Response fileTypeInfoList() { + List fileTypeInfoList = (List) fileProjectSourceDocTypes().stream().map( + DocumentType::toFileTypeInfo).collect(Collectors.toList()); + GenericEntity> genericEntity = + new GenericEntity>(fileTypeInfoList) {}; return Response.ok(genericEntity).build(); } diff --git a/zanata-client-commands/src/main/java/org/zanata/client/commands/FileMappingRuleHandler.java b/zanata-client-commands/src/main/java/org/zanata/client/commands/FileMappingRuleHandler.java index 994cbd91..a07f9664 100644 --- a/zanata-client-commands/src/main/java/org/zanata/client/commands/FileMappingRuleHandler.java +++ b/zanata-client-commands/src/main/java/org/zanata/client/commands/FileMappingRuleHandler.java @@ -37,7 +37,6 @@ import org.zanata.client.config.FileMappingRule; import org.zanata.client.config.LocaleMapping; import org.zanata.client.util.FileUtil; -import org.zanata.common.DocumentType; import org.zanata.common.ProjectType; import com.google.common.annotations.VisibleForTesting; @@ -81,7 +80,7 @@ public static boolean isRuleValid(String rule) { */ public boolean isApplicable(DocNameWithExt docNameWithExt) { if (Strings.isNullOrEmpty(mappingRule.getPattern())) { - return matchFileExtensionWithProjectType(docNameWithExt); + return true; } PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + mappingRule.getPattern()); @@ -95,18 +94,6 @@ public boolean isApplicable(DocNameWithExt docNameWithExt) { return matcher.matches(Paths.get(srcFile.getPath())); } - private boolean matchFileExtensionWithProjectType( - DocNameWithExt docNameWithExt) { - List documentTypes = projectType.getSourceFileTypes(); - for (DocumentType docType: documentTypes) { - if (docType.getSourceExtensions().contains( - docNameWithExt.getExtension())) { - return true; - } - } - return false; - } - /** * Apply the rule and return relative path of the translation file. * diff --git a/zanata-client-commands/src/main/java/org/zanata/client/commands/StringUtil.java b/zanata-client-commands/src/main/java/org/zanata/client/commands/StringUtil.java index 025c94be..2cad29cc 100644 --- a/zanata-client-commands/src/main/java/org/zanata/client/commands/StringUtil.java +++ b/zanata-client-commands/src/main/java/org/zanata/client/commands/StringUtil.java @@ -1,6 +1,7 @@ package org.zanata.client.commands; import com.google.common.base.Strings; +import org.apache.commons.lang3.StringUtils; /** * @@ -9,6 +10,8 @@ * */ public class StringUtil { + private static final String newline = System.getProperty("line.separator"); + public static String removeFileExtension(String filename, String extension) { if (!filename.endsWith(extension)) throw new IllegalArgumentException("Filename '" + filename @@ -21,4 +24,13 @@ public static String removeFileExtension(String filename, String extension) { public static String indent(int numOfSpaces) { return Strings.repeat(" ", numOfSpaces); } + + /** + * Converts an array of strings into a single string, delimited by newlines + * @param lines + * @return + */ + public static String multiline(String... lines) { + return StringUtils.join(lines, newline); + } } diff --git a/zanata-client-commands/src/main/java/org/zanata/client/commands/TransFileResolver.java b/zanata-client-commands/src/main/java/org/zanata/client/commands/TransFileResolver.java index 207abf80..a0946530 100644 --- a/zanata-client-commands/src/main/java/org/zanata/client/commands/TransFileResolver.java +++ b/zanata-client-commands/src/main/java/org/zanata/client/commands/TransFileResolver.java @@ -139,7 +139,8 @@ private Optional tryGetTransFileFromProjectMappingRules( DocNameWithExt docNameWithExt, LocaleMapping localeMapping, Optional translationFileExtension) { List fileMappingRules = opts.getFileMappingRules(); - // TODO may need to sort the rules. put rules without pattern to last + // Rules are applied in order. A rule with no pattern always matches, + // so such a rule should be the last. for (FileMappingRule rule : fileMappingRules) { FileMappingRuleHandler handler = new FileMappingRuleHandler(rule, getProjectType(), opts); diff --git a/zanata-client-commands/src/main/java/org/zanata/client/commands/init/SourceConfigPrompt.java b/zanata-client-commands/src/main/java/org/zanata/client/commands/init/SourceConfigPrompt.java index b063fa83..911d0d42 100644 --- a/zanata-client-commands/src/main/java/org/zanata/client/commands/init/SourceConfigPrompt.java +++ b/zanata-client-commands/src/main/java/org/zanata/client/commands/init/SourceConfigPrompt.java @@ -24,7 +24,6 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.List; -import java.util.Map; import java.util.Set; import org.kohsuke.args4j.Option; @@ -40,7 +39,7 @@ import org.zanata.client.commands.push.PushOptionsImpl; import org.zanata.client.commands.push.RawPushCommand; import org.zanata.client.commands.push.RawPushStrategy; -import org.zanata.common.DocumentType; +import org.zanata.common.FileTypeInfo; import org.zanata.rest.client.FileResourceClient; import org.zanata.rest.client.RestClientFactory; @@ -132,10 +131,11 @@ SourceConfigPrompt promptUser() throws Exception { console.blankLine(); console.printfln(Question, get("project.file.type.question")); - // this answer is not persisted in zanata.xml so user will still need to type it when they do the actual push + // FIXME invoke listFileTypes console.printfln(Hint, PushOptionsImpl.fileTypeHelp); console.printf(Question, get("file.type.prompt")); String answer = console.expectAnyNotBlankAnswer(); + // TODO this answer is not persisted in zanata.xml so user will still need to type it when they do the actual push ((PushOptionsImpl) pushOptions).setFileTypes(answer); } @@ -255,9 +255,9 @@ private ImmutableList filteredFileExtensions(PushOptions opts) { new RawPushCommand(opts, clientFactory, console); FileResourceClient client = clientFactory.getFileResourceClient(); - List rawDocumentTypes = client.acceptedFileTypes(); - Map> filteredDocTypes = - rawPushCommand.validateFileTypes(rawDocumentTypes, + List serverFileTypes = rawPushCommand.fileTypeInfoList(client); + ImmutableList filteredDocTypes = + rawPushCommand.getActualFileTypes(serverFileTypes, opts.getFileTypes()); if (filteredDocTypes.isEmpty()) { @@ -265,13 +265,11 @@ private ImmutableList filteredFileExtensions(PushOptions opts) { return ImmutableList.of(); } - ImmutableList.Builder sourceFileExtensionsBuilder = + ImmutableList.Builder sourceFileExtensions = ImmutableList.builder(); - for (Set filteredSourceExtensions : filteredDocTypes - .values()) { - sourceFileExtensionsBuilder.addAll(filteredSourceExtensions); - } - return sourceFileExtensionsBuilder.build(); + filteredDocTypes.forEach(type -> + sourceFileExtensions.addAll(type.getSourceExtensions())); + return sourceFileExtensions.build(); } } diff --git a/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushCommand.java b/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushCommand.java index f77a331b..163337c2 100644 --- a/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushCommand.java +++ b/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushCommand.java @@ -111,6 +111,12 @@ public AbstractPushStrategy getStrategy(PushOptions pushOptions) { return strat; } + private void checkOptions() { + if (getOpts().getListFileTypes()) { + throw new RuntimeException("\"List file types\" is only supported for project type FILE"); + } + } + public static void logOptions(Logger logger, PushOptions opts) { if (!logger.isInfoEnabled()) { return; @@ -208,6 +214,7 @@ private boolean pushTrans() { @Override public void run() throws Exception { + checkOptions(); logOptions(log, getOpts()); pushCurrentModule(); diff --git a/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushOptions.java b/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushOptions.java index b2630b57..5b5d7474 100644 --- a/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushOptions.java +++ b/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushOptions.java @@ -52,6 +52,8 @@ public interface PushOptions extends PushPullOptions { // raw file push public int getChunkSize(); + boolean getListFileTypes(); + public ImmutableList getFileTypes(); @Nonnull diff --git a/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushOptionsImpl.java b/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushOptionsImpl.java index 43fedddf..80c32540 100644 --- a/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushOptionsImpl.java +++ b/zanata-client-commands/src/main/java/org/zanata/client/commands/push/PushOptionsImpl.java @@ -53,6 +53,7 @@ public class PushOptionsImpl extends AbstractPushPullOptionsImpl private ImmutableList includes = ImmutableList.of(); private ImmutableList excludes = ImmutableList.of(); private ImmutableList fileTypes = ImmutableList.of(); + private boolean listFileTypes = false; private boolean defaultExcludes = DEF_EXCLUDES; private String mergeType = DEF_MERGE_TYPE; private boolean caseSensitive = DEF_CASE_SENSITIVE; @@ -194,25 +195,17 @@ public ImmutableList getFileTypes() { return fileTypes; } - public static final String fileTypeHelp = "File types to locate and transmit to the server. \n" + - "Default file extension will be used unless it is being specified. \n" + - "Pattern: TYPE[extension;extension],TYPE[extension] \n" + - "Supported types: \n" + - "\t XML_DOCUMENT_TYPE_DEFINITION[xml] \n" + - "\t PLAIN_TEXT[txt] \n" + - "\t IDML[idml] \n" + - "\t HTML[html;htm] \n" + - "\t OPEN_DOCUMENT_TEXT[odt] \n" + - "\t OPEN_DOCUMENT_PRESENTATION[odp] \n" + - "\t OPEN_DOCUMENT_GRAPHICS[odg] \n" + - "\t OPEN_DOCUMENT_SPREADSHEET[ods] \n" + - "\t SUBTITLE[srt;sbt;sub;vtt] \n" + - "\t TS[ts] \n" + - "\t GETTEXT[pot] \n" + - "\t PROPERTIES[properties] \n" + - "\t PROPERTIES_UTF8[properties] \n" + - "\t XLIFF[xml] \n" + - "Usage --file-types \"XML_DOCUMENT_TYPE_DEFINITION,PLAIN_TEXT[md;txt]\""; + public static final String fileTypeHelp = "File types to locate and transmit to the server\n" + + "when using project type \"file\".\n" + + "NOTE: No file types will be pushed unless listed here.\n" + + "Pattern: TYPE[extension;extension],TYPE[extension]\n" + + "The default file extension(s) for each TYPE will be used unless\n" + + "'extension' is specified in square brackets. If overriding extensions,\n" + + "please note that most shells require quotes around square brackets and \n" + + "semicolons unless they are escaped.\n" + + "Example: --file-types \"PROPERTIES,PLAIN_TEXT[md;txt]\"\n" + + "Use push with the option --list-file-types to see the server's\n" + + "supported types and their default extensions."; @Option(name = "--file-types", metaVar = "TYPES", usage = fileTypeHelp) @@ -220,6 +213,18 @@ public void setFileTypes(String fileTypes) { this.fileTypes = ImmutableList.copyOf(StringUtil.split(fileTypes, ",")); } + @Override + public boolean getListFileTypes() { + return this.listFileTypes; + } + + @Option(name = "--list-file-types", + usage = "List file types supported by the configured server, " + + "instead of pushing files.") + public void setListFileTypes(boolean listFileTypes) { + this.listFileTypes = listFileTypes; + } + @Override public boolean getCaseSensitive() { return caseSensitive; diff --git a/zanata-client-commands/src/main/java/org/zanata/client/commands/push/RawPushCommand.java b/zanata-client-commands/src/main/java/org/zanata/client/commands/push/RawPushCommand.java index d36be8e1..660596fb 100644 --- a/zanata-client-commands/src/main/java/org/zanata/client/commands/push/RawPushCommand.java +++ b/zanata-client-commands/src/main/java/org/zanata/client/commands/push/RawPushCommand.java @@ -30,9 +30,10 @@ import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -43,9 +44,12 @@ import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import javax.annotation.Nullable; +import com.google.common.collect.ImmutableMap; +import com.sun.jersey.api.client.UniformInterfaceException; import org.apache.commons.codec.binary.Hex; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; @@ -62,6 +66,9 @@ import org.zanata.client.exceptions.InvalidUserInputException; import org.zanata.client.util.ConsoleUtils; import org.zanata.common.DocumentType; +import org.zanata.common.FileTypeInfo; +import org.zanata.common.FileTypeName; +import org.zanata.common.ProjectType; import org.zanata.rest.DocumentFileUploadForm; import org.zanata.rest.client.FileResourceClient; import org.zanata.rest.client.RestClientFactory; @@ -72,6 +79,7 @@ import com.google.common.collect.ImmutableList; import static org.zanata.client.commands.ConsoleInteractor.DisplayMode; +import static org.zanata.client.commands.StringUtil.multiline; /** * Command to send files directly to the server without parsing on the client. @@ -84,12 +92,18 @@ public class RawPushCommand extends PushPullCommand { private static final Logger log = LoggerFactory .getLogger(PushCommand.class); - private static final Pattern fileNameExtensionsPattern = Pattern.compile( - "(?:([^\\[]*)?(?:\\[(.*?)\\])?)"); + private static final Pattern fileTypeSpecPattern = Pattern.compile(multiline( + "(?x) # enable regex comments", + "([^\\[]*) # capture (1) everything before EOL or brackets (DocumentType)", + "(?: # optional non-capture group for any bracketed text", + "\\[(.*)\\] # capture (2) any filename extensions inside brackets (semicolon-separated)", + ")?" + )); private final ConsoleInteractor consoleInteractor; - private FileResourceClient client; + // TODO rename to fileResource or similar + private final FileResourceClient client; public RawPushCommand(PushOptions opts) { super(opts); @@ -97,6 +111,7 @@ public RawPushCommand(PushOptions opts) { consoleInteractor = new ConsoleInteractorImpl(opts); } + @VisibleForTesting public RawPushCommand(PushOptions opts, RestClientFactory clientFactory) { super(opts, clientFactory); client = getClientFactory().getFileResourceClient(); @@ -111,137 +126,164 @@ public RawPushCommand(PushOptions opts, RestClientFactory clientFactory, } /** - * Extract extensions from input string + * Extract extensions from input string as a Map (source, target). Note + * that source and target will be the same in the current implementation, since + * the command-line option doesn't yet allow target extensions to be specified. */ - public List extractExtensions(String typeWithExtension) { - Matcher matcher = fileNameExtensionsPattern.matcher(typeWithExtension); + @VisibleForTesting + ImmutableMap extractExtensions(String fileTypeSpec) { + Matcher matcher = fileTypeSpecPattern.matcher(fileTypeSpec); if (matcher.find()) { - String rawExtensions = matcher.group(2); - if (!StringUtils.isEmpty(rawExtensions)) { - return Arrays.asList(rawExtensions.split(";")); + String extensions = matcher.group(2); + if (!StringUtils.isEmpty(extensions)) { + ImmutableMap.Builder builder = + ImmutableMap.builder(); + Arrays.asList(extensions.split(";")).forEach( + ext -> builder.put(ext, ext)); + return builder.build(); } } - return Collections.emptyList(); + return ImmutableMap.of(); } /** * Extract fileType from input string - * input pattern: fileType[extension;extension], [extension;extension] + * input pattern: fileType[extension;extension],fileType[extension;extension] * - * @param typeWithExtension - * @return result[0] - type, result[1] - extensions. e.g [txt,html] + * @param fileTypeMappingSpec + * @return an unvalidated FileTypeName if one was given, otherwise null */ - public @Nullable String extractType(String typeWithExtension) { - Matcher matcher = fileNameExtensionsPattern.matcher(typeWithExtension); + @VisibleForTesting + @Nullable FileTypeName extractFileTypeName(String fileTypeMappingSpec) { + Matcher matcher = fileTypeSpecPattern.matcher(fileTypeMappingSpec); if (matcher.find()) { - return matcher.group(1); + String name = matcher.group(1); + if (StringUtils.isNotBlank(name)) { + return new FileTypeName(name.toUpperCase()); + } } return null; } /** - * Validate inputFileType to make sure it's not empty - * - * @param inputFileType - * @param inputExtensions + * Validate file extensions + * @param inputFileType + * @param userExtensions * @param acceptedTypes */ - private void validateInputFileType(String inputFileType, - List inputExtensions, List acceptedTypes) { - if (StringUtils.isNotBlank(inputFileType)) { + private void validateFileExtensions(@Nullable FileTypeName inputFileType, + ImmutableMap userExtensions, List acceptedTypes) { + if (inputFileType != null) { return; } - if (inputExtensions.isEmpty()) { + // if file type is missing but extensions are present, try to provide + // a helpful error message + if (userExtensions.isEmpty()) { //throw error if inputFileType and inputExtensions is empty throw new InvalidUserInputException( "Invalid expression for '--file-types' option"); } else { //suggest --file-types options for this extension - for (DocumentType docType: acceptedTypes) { - for (String extension: docType.getSourceExtensions()) { - if (inputExtensions.contains(extension)) { + for (FileTypeInfo docType: acceptedTypes) { + for (String srcExt: docType.getSourceExtensions()) { + if (userExtensions.containsKey(srcExt)) { String msg = Messages.format( "file.type.suggestFromExtension", docType, - extension, docType); + srcExt, docType); throw new InvalidUserInputException(msg); } } } + throw new InvalidUserInputException( + "Invalid expression for '--file-types' option"); } } /** - * Return map of validated type with set of extensions + * Return map of validated DocumentType to set of corresponding extensions, by applying the user's + * file type options to the server's accepted file types. * * Validate user input file types against server accepted file types * * Valid input - properties_utf8,properties[txt],plain_text[md;markdown] * - * - Each file type is only input once - e.g. + * - Each file type must appear only once - e.g. * - valid: "html,properties,txt" * - invalid: "html,properties,html" - * - Same file extension cannot be in multiple file type - e.g. plain_text[txt],properties[txt] - * - * @param acceptedTypes - * @param inputFileTypes + * - Same file extension must not appear in multiple file types - e.g. plain_text[txt],properties[txt] + * @param serverFileTypes + * @param fileTypesSpec */ - public Map> validateFileTypes( - List acceptedTypes, List inputFileTypes) { - - Map> filteredFileTypes = new HashMap<>(); - - for (String typeWithExtension : inputFileTypes) { - String fileType = extractType(typeWithExtension); - List extensions = extractExtensions(typeWithExtension); - - validateInputFileType(fileType, extensions, acceptedTypes); + public ImmutableList getActualFileTypes( + List serverFileTypes, List fileTypesSpec) { + + // cumulative list of activated types + ImmutableList.Builder docTypeMappings = + new ImmutableList.Builder<>(); + // types which have been specified by the user so far + Set seenUserDocTypes = new HashSet<>(); + // extensions which have been specified by the user so far + Set seenUserExtensions = new HashSet<>(); + + if (fileTypesSpec.isEmpty()) { + return ImmutableList.of(); + } - DocumentType inputFileType = DocumentType.getByName(fileType); + for (String fileTypeSpec : fileTypesSpec) { + @Nullable FileTypeName userType = extractFileTypeName(fileTypeSpec); + ImmutableMap userExtensions; + if (userType == null) { + // try parameter as a list of file extensions: ZNTA-1248 + String[] exts = fileTypeSpec.split(","); + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + for (String ext : exts) { + builder.put(ext, ext); + } + userExtensions = builder.build(); + } else { + userExtensions = extractExtensions(fileTypeSpec); + } - //skip file type if its not supported - if (inputFileType == null) { - String msg = Messages.format("file.type.typeNotSupported", fileType); - consoleInteractor.printfln(DisplayMode.Warning, msg); - continue; + validateFileExtensions(userType, userExtensions, serverFileTypes); + assert userType != null; + @Nullable FileTypeInfo fileTypeInfo = serverFileTypes.stream() + .filter((FileTypeInfo info) -> info.getType().equals(userType)) + .findAny().orElse(null); + + // throw error if file type is not supported by server + if (fileTypeInfo == null) { + String msg = Messages.format( + "file.type.typeNotSupported", userType); + throw new InvalidUserInputException(msg); } - //throw error if file type is input more than once - if (filteredFileTypes.containsKey(inputFileType)) { - String msg = - Messages.format("file.type.duplicateFileType", fileType); + if (!seenUserDocTypes.add(userType)) { + //throw error if file type is listed more than once + String msg = Messages.format( + "file.type.duplicateFileType", userType); log.error(msg); throw new RuntimeException(msg); } - - Set filteredExtensions = new HashSet<>(extensions); - - /** - * Use the extensions from typeWithExtension input if exists, - * otherwise, use the extensions from server. - */ - filteredExtensions = - filteredExtensions.isEmpty() ? inputFileType.getSourceExtensions() - : filteredExtensions; - - //throw error if same file extension found in multiple file type - for (Map.Entry> entry: filteredFileTypes.entrySet()) { - for (String filteredExtension: entry.getValue()) { - if (filteredExtensions.contains(filteredExtension)) { - String msg = - Messages.format( - "file.type.conflictExtension", - fileType, entry.getKey().name(), - filteredExtension); - log.error(msg); - throw new RuntimeException(msg); - } + for (String srcExt : userExtensions.keySet()) { + //throw error if same file extension found in multiple file types + if (!seenUserExtensions.add(srcExt)) { + String msg = Messages.format( + "file.type.conflictExtension", srcExt, userType); + log.error(msg); + throw new RuntimeException(msg); } } - filteredFileTypes.put(inputFileType, filteredExtensions); + + // Use the extensions from docTypeMappingSpec if specified, + // otherwise use the extensions from server. + Map filteredExtensions = + userExtensions.isEmpty() ? fileTypeInfo.getExtensions() + : userExtensions; + docTypeMappings.add(new FileTypeInfo(userType, filteredExtensions)); } - return filteredFileTypes; + return docTypeMappings.build(); } @Override @@ -251,12 +293,19 @@ public void run() throws IOException { consoleInteractor.printfln(DisplayMode.Warning, "Using EXPERIMENTAL project type 'file'."); + List serverAcceptedTypes = fileTypeInfoList(client); + + if (getOpts().getListFileTypes()) { + printFileTypes(serverAcceptedTypes); + return; + } + // only supporting single module for now File sourceDir = getOpts().getSrcDir(); if (!sourceDir.exists()) { boolean enableModules = getOpts().getEnableModules(); - // TODO remove when modules implemented + // TODO(files) remove warning when modules supported if (enableModules) { consoleInteractor .printfln(DisplayMode.Warning, @@ -271,31 +320,28 @@ public void run() throws IOException { RawPushStrategy strat = new RawPushStrategy(); strat.setPushOptions(getOpts()); - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - List serverAcceptedTypes = client.acceptedFileTypes(); + ImmutableList actualFileTypes = + getActualFileTypes(serverAcceptedTypes, getOpts().getFileTypes()); - Map> filteredDocTypes = - validateFileTypes(serverAcceptedTypes, getOpts().getFileTypes()); - - if (filteredDocTypes.isEmpty()) { + if (actualFileTypes.isEmpty()) { log.info("no valid types specified; nothing to do"); return; } ImmutableList.Builder sourceFileExtensionsBuilder = ImmutableList.builder(); - for (Set filteredSourceExtensions : filteredDocTypes.values()) { - sourceFileExtensionsBuilder.addAll(filteredSourceExtensions); - } + actualFileTypes.forEach(fileTypeInfo -> + sourceFileExtensionsBuilder.addAll(fileTypeInfo.getSourceExtensions())); + ImmutableList sourceFileExtensions = sourceFileExtensionsBuilder.build(); String[] srcFiles = strat.getSrcFiles(sourceDir, getOpts().getIncludes(), getOpts() - .getExcludes(), sourceFileExtensionsBuilder.build(), true, getOpts() + .getExcludes(), sourceFileExtensions, true, getOpts() .getCaseSensitive()); SortedSet localDocNames = new TreeSet(Arrays.asList(srcFiles)); - // TODO handle obsolete document deletion + // TODO(files) handle obsolete document deletion consoleInteractor .printfln(DisplayMode.Warning, "Obsolete document removal is not yet implemented, no documents will be removed from the server."); @@ -324,11 +370,11 @@ public void run() throws IOException { consoleInteractor.printfln("Found source documents:"); for (String docName : localDocNames) { if (docsToPush.contains(docName)) { - DocumentType fileType = getFileType(filteredDocTypes, + FileTypeName fileType = getFileTypeNameBySourceExtension(actualFileTypes, FilenameUtils.getExtension(docName)); consoleInteractor.printfln(" " + Messages.format("push.info.documentToPush", - docName, fileType.name())); + docName, fileType.getName())); } else { consoleInteractor.printfln( Messages.format("push.info.skipDocument", docName)); @@ -361,7 +407,7 @@ public void run() throws IOException { for (final String localDocName : docsToPush) { try { final String srcExtension = FilenameUtils.getExtension(localDocName); - final DocumentType fileType = getFileType(filteredDocTypes, + final FileTypeInfo fileType = getFileType(actualFileTypes, srcExtension); final String qualifiedDocName = qualifiedDocName(localDocName); if (getOpts().getPushType() == PushPullType.Source @@ -370,7 +416,7 @@ public void run() throws IOException { boolean sourcePushed = pushSourceDocumentToServer(sourceDir, localDocName, qualifiedDocName, - fileType.name()); + fileType.getType().getName()); // ClientUtility.checkResult(putResponse, uri); if (!sourcePushed) { hasErrors = true; @@ -385,7 +431,7 @@ public void run() throws IOException { if (getOpts().getPushType() == PushPullType.Trans || getOpts().getPushType() == PushPullType.Both) { - Optional translationFileExtension = + Optional translationFileExtension = getTranslationFileExtension(fileType, srcExtension); strat.visitTranslationFiles(localDocName, @@ -398,7 +444,7 @@ public void visit(LocaleMapping locale, locale.getLocale(), qualifiedDocName); pushDocumentToServer(qualifiedDocName, - fileType.name(), locale.getLocale(), + fileType.getType().getName(), locale.getLocale(), translatedDoc); } }, translationFileExtension); @@ -419,6 +465,20 @@ public void visit(LocaleMapping locale, } + private void printFileTypes(List serverAcceptedTypes) { + consoleInteractor.printfln(DisplayMode.Information, "Listing supported file types [with default source extensions]:"); + List types = new ArrayList<>(serverAcceptedTypes); + Collections.sort(types, (a, b) -> 0); + for (FileTypeInfo docType : types) { + String exts = docType.getSourceExtensions() + .stream() + .sorted() + .collect(Collectors.joining(";")); + consoleInteractor.printfln(DisplayMode.Information, " %s[%s]", docType.getType(), exts); + } + log.info("Listed file types: no files were pushed"); + } + /** * Get translation file extension in given docType with the srcExtension. * If no extension found, return source file extension. @@ -426,7 +486,7 @@ public void visit(LocaleMapping locale, * @param docType * @param srcExtension */ - private Optional getTranslationFileExtension(DocumentType docType, + private Optional getTranslationFileExtension(FileTypeInfo docType, String srcExtension) { String transFileExtension = docType.getExtensions().get(srcExtension); @@ -440,12 +500,23 @@ private Optional getTranslationFileExtension(DocumentType docType, * @param fileTypes * @param srcExtension */ - private @Nullable DocumentType getFileType(Map> fileTypes, - String srcExtension) { - for (Map.Entry> entry : fileTypes.entrySet()) { + private @Nullable FileTypeInfo getFileType( + ImmutableList fileTypes, String srcExtension) { + for (FileTypeInfo entry : fileTypes) { + Collection extensions = entry.getSourceExtensions(); + if (extensions.contains(srcExtension)) { + return entry; + } + } + return null; + } - if (entry.getValue().contains(srcExtension)) { - return entry.getKey(); + private @Nullable FileTypeName getFileTypeNameBySourceExtension( + ImmutableList fileTypes, String srcExtension) { + for (FileTypeInfo entry : fileTypes) { + Collection extensions = entry.getSourceExtensions(); + if (extensions.contains(srcExtension)) { + return entry.getType(); } } return null; @@ -465,11 +536,8 @@ private boolean pushSourceDocumentToServer(File sourceDir, throws IOException { log.info("pushing source document [{}] to server", qualifiedDocName); - String locale = null; - File srcFile = new File(sourceDir, localDocName); - - pushDocumentToServer(qualifiedDocName, fileType, locale, srcFile); + pushDocumentToServer(qualifiedDocName, fileType, null /*locale*/, srcFile); return true; } @@ -480,7 +548,7 @@ private boolean pushSourceDocumentToServer(File sourceDir, * @param docFile */ private void pushDocumentToServer(String docId, String fileType, - String locale, File docFile) { + @Nullable String locale, File docFile) { try { String md5hash = calculateFileHash(docFile); if (docFile.length() <= getOpts().getChunkSize()) { @@ -546,7 +614,7 @@ private DocumentFileUploadForm generateUploadForm(boolean isFirst, } private ChunkUploadResponse uploadDocumentPart( - String docName, String locale, DocumentFileUploadForm uploadForm) { + String docName, @Nullable String locale, DocumentFileUploadForm uploadForm) { ConsoleUtils.startProgressFeedback(); ChunkUploadResponse response; if (locale == null) { @@ -571,12 +639,14 @@ private String calculateFileHash(File srcFile) { try { fileStream = new DigestInputStream(fileStream, md); byte[] buffer = new byte[256]; + //noinspection StatementWithEmptyBody while (fileStream.read(buffer) > 0) { - // continue + // just keep digesting the input } } finally { fileStream.close(); } + @SuppressWarnings("UnnecessaryLocalVariable") String md5hash = new String(Hex.encodeHex(md.digest())); return md5hash; } catch (NoSuchAlgorithmException | IOException e) { @@ -584,13 +654,40 @@ private String calculateFileHash(File srcFile) { } } + /** + * Try to get the file types from the server (if server is new enough), otherwise use + * compiled-in values (as older versions of the client did). + * @param client + * @return + */ + public List fileTypeInfoList(FileResourceClient client) { + try { + return client.fileTypeInfoList(); + } catch (UniformInterfaceException e) { + if (e.getResponse().getStatus() == 404) { + log.info("Detected old Zanata Server; using hard-coded file types."); + // probably running against an old Zanata Server + return fileTypeInfoListWorkaround(); + } else { + throw e; + } + } + } + + private List fileTypeInfoListWorkaround() { + return ProjectType.fileProjectSourceDocTypes().stream() + .sorted((a, b) -> a.toString().compareTo(b.toString())) + .map(DocumentType::toFileTypeInfo) + .collect(Collectors.toList()); + } + private static class StreamChunker implements Iterable, Closeable { - private int totalChunkCount; + private final int totalChunkCount; private int chunksRetrieved; - private File file; - private byte[] buffer; + private final File file; + private final byte[] buffer; private InputStream fileStream; private int actualChunkSize; diff --git a/zanata-client-commands/src/main/resources/prompts.properties b/zanata-client-commands/src/main/resources/prompts.properties index 7ce94ff0..2b9ad2ed 100644 --- a/zanata-client-commands/src/main/resources/prompts.properties +++ b/zanata-client-commands/src/main/resources/prompts.properties @@ -100,12 +100,13 @@ file.type.suggestFromExtension=Please use file type "{0}" for extension "{1}", e file.type.duplicateFileType=File type "{0}" cannot be used more than once in option --file-types. # {0} - fileType file.type.typeNotSupported=Requested file type "{0}" is not supported by the target server and will be ignored. -# {0} - fileType1, {1} - fileType2, {2} - extension -file.type.conflictExtension=You have used extension "{2}" in both "{0}" and "{1}" for --file-types option. Same extension can only be used once. +# {0} - extension, {1} - fileType +file.type.conflictExtension=You have used extension "{0}" in multiple types, including "{1}", for \ +the --file-types option. Each extension can only be used once. # {0} - pushType push.warn.overrideTranslations=pushType set to "{0}". Existing translations on server may be overwritten/deleted. # {0} - document name push.info.skipDocument=(to skip) {0} # {0} - document name, {1} - fileType -push.info.documentToPush={0} [{1}] \ No newline at end of file +push.info.documentToPush={0} [{1}] diff --git a/zanata-client-commands/src/test/java/org/zanata/client/MockServerRule.java b/zanata-client-commands/src/test/java/org/zanata/client/MockServerRule.java index efed9c39..dcaeb58a 100644 --- a/zanata-client-commands/src/test/java/org/zanata/client/MockServerRule.java +++ b/zanata-client-commands/src/test/java/org/zanata/client/MockServerRule.java @@ -31,9 +31,11 @@ import java.io.InputStream; import java.net.URI; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.junit.rules.ExternalResource; import org.mockito.ArgumentCaptor; @@ -48,9 +50,9 @@ import org.zanata.client.commands.push.PushOptionsImpl; import org.zanata.client.commands.push.RawPushCommand; import org.zanata.client.config.LocaleList; +import org.zanata.common.FileTypeInfo; import org.zanata.common.DocumentType; import org.zanata.common.LocaleId; -import org.zanata.common.ProjectType; import org.zanata.rest.DocumentFileUploadForm; import org.zanata.rest.client.AsyncProcessClient; import org.zanata.rest.client.CopyTransClient; @@ -70,7 +72,7 @@ import com.sun.jersey.core.util.MultivaluedMapImpl; /** - * Test rule to set up push and/or pull command(s) which will interact with a + * Test rule to set up push and/or pull commands which will interact with * mockito mocked REST clients. * * @author Patrick Huang documentTypes = - ProjectType.fileProjectSourceDocTypes(); - - when(fileResourceClient.acceptedFileTypes()).thenReturn( - documentTypes); + // Use the compiled-in DocumentTypes for the mock FileTypeInfo list: + List docTypeList = Arrays.stream(DocumentType.values()) + .map(DocumentType::toFileTypeInfo).collect(Collectors.toList()); + when(fileResourceClient.fileTypeInfoList()).thenReturn( + docTypeList); ChunkUploadResponse uploadResponse = new ChunkUploadResponse(1L, 1, false, "Upload successful"); @@ -362,6 +364,7 @@ public RawPullCommand createRawPullCommand( eq(pullOpts.getProjectVersion()), anyString(), anyString(), anyString())).thenReturn(downloadTransResponse); when(downloadTransResponse.getStatus()).thenReturn(200); + when(downloadTransResponse.getHeaders()).thenReturn(new MultivaluedMapImpl()); when(downloadTransResponse.getClientResponseStatus()).thenReturn( ClientResponse.Status.OK); when(downloadTransResponse.getEntity(InputStream.class)) diff --git a/zanata-client-commands/src/test/java/org/zanata/client/commands/init/SourceConfigPromptTest.java b/zanata-client-commands/src/test/java/org/zanata/client/commands/init/SourceConfigPromptTest.java index 48bf9b17..c45e2141 100644 --- a/zanata-client-commands/src/test/java/org/zanata/client/commands/init/SourceConfigPromptTest.java +++ b/zanata-client-commands/src/test/java/org/zanata/client/commands/init/SourceConfigPromptTest.java @@ -5,6 +5,7 @@ import java.io.File; import java.net.URI; +import java.util.List; import org.hamcrest.Matchers; import org.junit.Before; @@ -13,14 +14,12 @@ import org.junit.rules.TemporaryFolder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.zanata.client.commands.ConfigurableProjectOptions; -import org.zanata.client.commands.ConfigurableProjectOptionsImpl; import org.zanata.client.commands.ConsoleInteractor; import org.zanata.client.commands.MockConsoleInteractor; -import org.zanata.client.commands.ZanataCommand; import org.zanata.client.commands.push.PushOptions; import org.zanata.client.commands.push.PushOptionsImpl; import org.zanata.client.config.LocaleList; +import org.zanata.common.FileTypeInfo; import org.zanata.common.DocumentType; import org.zanata.rest.client.FileResourceClient; import org.zanata.rest.client.RestClientFactory; @@ -112,7 +111,8 @@ public void allowUserToRetry() throws Exception { @Test public void canHandleFileProjectType() throws Exception { - when(fileClient.acceptedFileTypes()).thenReturn(ImmutableList.copyOf(DocumentType.values())); + List docTypeList = ImmutableList.of(DocumentType.PLAIN_TEXT.toFileTypeInfo(), DocumentType.HTML.toFileTypeInfo()); + when(fileClient.fileTypeInfoList()).thenReturn(docTypeList); // here we use absolute path because we create temp files in there String expectedSrcDir = tempFolder.getRoot().getAbsolutePath() + "/resources"; ConsoleInteractor console = diff --git a/zanata-client-commands/src/test/java/org/zanata/client/commands/push/RawPushCommandTest.java b/zanata-client-commands/src/test/java/org/zanata/client/commands/push/RawPushCommandTest.java index c7ba4e86..74c76ecd 100644 --- a/zanata-client-commands/src/test/java/org/zanata/client/commands/push/RawPushCommandTest.java +++ b/zanata-client-commands/src/test/java/org/zanata/client/commands/push/RawPushCommandTest.java @@ -1,17 +1,20 @@ package org.zanata.client.commands.push; import java.util.List; +import java.util.Set; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.zanata.client.commands.ConsoleInteractor; +import org.zanata.common.FileTypeName; import org.zanata.rest.client.RestClientFactory; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.when; @@ -39,58 +42,63 @@ public void setup() { } @Test - public void extractFileTypeWithExtensionTest() { + public void extractDocTypeWithExtensionTest() { String fileNameAndExtension = "properties[xml]"; - String type = command.extractType(fileNameAndExtension); - assertThat(type, equalTo("properties")); + String type = command.extractFileTypeName(fileNameAndExtension).getName(); + assertThat(type, equalTo("PROPERTIES")); } @Test - public void extractFileTypeWithoutExtensionTest() { + public void extractDocTypeWithoutExtensionTest() { String fileNameAndExtension = "properties"; - String type = command.extractType(fileNameAndExtension); - assertThat(type, equalTo("properties")); + String type = command.extractFileTypeName(fileNameAndExtension).getName(); + assertThat(type, equalTo("PROPERTIES")); } @Test - public void extractFileTypeOnlyExtensionTest() { + public void extractDocTypeOnlyExtensionTest() { String fileNameAndExtension = "[xml]"; - String type = command.extractType(fileNameAndExtension); - assertThat(type, equalTo("")); + FileTypeName type = command.extractFileTypeName(fileNameAndExtension); + assertThat(type, nullValue()); } @Test public void extractExtensionWithTypeTest() { String fileNameAndExtension = "properties[xml]"; - List extensions = command.extractExtensions(fileNameAndExtension); + Set extensions = + command.extractExtensions(fileNameAndExtension).keySet(); assertThat(extensions, contains("xml")); } @Test public void extractExtensionWithTypeTest2() { String fileNameAndExtension = "properties[xml;html]"; - List extensions = command.extractExtensions(fileNameAndExtension); + Set extensions = + command.extractExtensions(fileNameAndExtension).keySet(); assertThat(extensions, containsInAnyOrder("xml", "html")); } @Test public void extractExtensionWithoutTypeTest() { String fileNameAndExtension = "[xml]"; - List extensions = command.extractExtensions(fileNameAndExtension); + Set extensions = + command.extractExtensions(fileNameAndExtension).keySet(); assertThat(extensions, contains("xml")); } @Test public void extractExtensionWithoutTypeTest2() { String fileNameAndExtension = "[xml;html]"; - List extensions = command.extractExtensions(fileNameAndExtension); + Set extensions = + command.extractExtensions(fileNameAndExtension).keySet(); assertThat(extensions, containsInAnyOrder("xml", "html")); } @Test public void extractExtensionOnlyTypeTest() { String fileNameAndExtension = "properties"; - List extensions = command.extractExtensions(fileNameAndExtension); + Set extensions = + command.extractExtensions(fileNameAndExtension).keySet(); assertThat(extensions.size(), equalTo(0)); } } diff --git a/zanata-maven-plugin/src/main/java/org/zanata/maven/AbstractPushMojo.java b/zanata-maven-plugin/src/main/java/org/zanata/maven/AbstractPushMojo.java index c9cf679a..1fc9249a 100644 --- a/zanata-maven-plugin/src/main/java/org/zanata/maven/AbstractPushMojo.java +++ b/zanata-maven-plugin/src/main/java/org/zanata/maven/AbstractPushMojo.java @@ -108,14 +108,36 @@ public PushPullCommand initCommand() { private int maxChunkSize = 1024 * 1024; /** - * File types to locate and transmit to the server when using project type - * "file". - * + * File types to locate and transmit to the server + * when using project type "file". + * NOTE: No file types will be pushed unless they are listed here. + *

+ * Pattern: TYPE[extension;extension],TYPE[extension],TYPE + *

+ *

+ * The default file extension(s) for each TYPE will be used unless + * 'extension' is specified in square brackets. If overriding extensions + * from the command line, please note that most shells require quotes + * around square brackets and semicolons unless they are escaped. + *

+ *

+ * Example: -Dzanata.fileTypes="PROPERTIES,PLAIN_TEXT[md;txt]" + *

+ *

+ * Use push with the option -Dzanata.listFileTypes to see the server's + * supported types and their default extensions. + *

* @parameter expression="${zanata.fileTypes}" - * default-value="txt,dtd,odt,fodt,odp,fodp,ods,fods,odg,fodg,odf,odb" */ - private String[] fileTypes = - "txt,dtd,odt,fodt,odp,fodp,ods,fods,odg,fodg,odf,odb".split(","); + private String[] fileTypes = new String[0]; + + /** + * List file types supported by the configured server, instead + * of pushing files. + * + * @parameter expression="${zanata.listFileTypes}" + */ + private boolean listFileTypes = false; /** * Case sensitive for includes and excludes options. @@ -197,6 +219,11 @@ public ImmutableList getFileTypes() { return ImmutableList.copyOf(fileTypes); } + @Override + public boolean getListFileTypes() { + return listFileTypes; + } + @Override public String getCommandName() { return "push"; diff --git a/zanata-rest-client/src/main/java/org/zanata/rest/client/FileResourceClient.java b/zanata-rest-client/src/main/java/org/zanata/rest/client/FileResourceClient.java index 4ffb7ab3..411b916f 100644 --- a/zanata-rest-client/src/main/java/org/zanata/rest/client/FileResourceClient.java +++ b/zanata-rest-client/src/main/java/org/zanata/rest/client/FileResourceClient.java @@ -29,6 +29,7 @@ import javax.ws.rs.core.MediaType; import com.sun.jersey.api.client.GenericType; +import org.zanata.common.FileTypeInfo; import org.zanata.common.DocumentType; import org.zanata.rest.DocumentFileUploadForm; import org.zanata.rest.StringSet; @@ -55,6 +56,7 @@ public class FileResourceClient { } + @Deprecated public List acceptedFileTypes() { List types = factory.getClient() .resource(baseUri) @@ -65,6 +67,16 @@ public List acceptedFileTypes() { return types; } + public List fileTypeInfoList() { + List types = factory.getClient() + .resource(baseUri) + .path(FileResource.SERVICE_PATH + + FileResource.FILE_TYPE_INFO_RESOURCE) + .get(new GenericType>() { + }); + return types; + } + public ChunkUploadResponse uploadSourceFile( String projectSlug, String iterationSlug, String docId, diff --git a/zanata-rest-client/src/test/java/org/zanata/rest/client/FileResourceClientTest.java b/zanata-rest-client/src/test/java/org/zanata/rest/client/FileResourceClientTest.java index ecabcf77..caa0d687 100644 --- a/zanata-rest-client/src/test/java/org/zanata/rest/client/FileResourceClientTest.java +++ b/zanata-rest-client/src/test/java/org/zanata/rest/client/FileResourceClientTest.java @@ -29,10 +29,8 @@ import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import org.apache.commons.codec.binary.Hex; @@ -44,10 +42,10 @@ import org.slf4j.LoggerFactory; import org.xml.sax.InputSource; import org.zanata.adapter.po.PoReader2; +import org.zanata.common.FileTypeInfo; import org.zanata.common.DocumentType; import org.zanata.common.LocaleId; import org.zanata.rest.DocumentFileUploadForm; -import org.zanata.rest.StringSet; import org.zanata.rest.dto.ChunkUploadResponse; import org.zanata.rest.dto.resource.Resource; import org.zanata.rest.dto.resource.TranslationsResource; @@ -83,7 +81,21 @@ public void testServerAcceptedType() { } assertThat(allExtension, Matchers.containsInAnyOrder("dtd", "pot", "txt", "idml", "html", "htm", "odt", "odp", "odg", "ods", - "srt", "sbt", "sub", "vtt", "properties", "xml", "ts")); + "srt", "sbt", "sub", "vtt", "properties", "xlf", "ts")); + } + + @Test + public void testFileTypeInfoList() { + List serverAcceptedTypes = client + .fileTypeInfoList(); + + Set allExtension = new HashSet(); + for (FileTypeInfo docType : serverAcceptedTypes) { + allExtension.addAll(docType.getSourceExtensions()); + } + assertThat(allExtension, Matchers.containsInAnyOrder("dtd", "pot", + "txt", "idml", "html", "htm", "odt", "odp", "odg", "ods", + "srt", "sbt", "sub", "vtt", "properties", "xlf", "ts")); } @Test diff --git a/zanata-rest-client/src/test/java/org/zanata/rest/client/MockServerTestUtil.java b/zanata-rest-client/src/test/java/org/zanata/rest/client/MockServerTestUtil.java index fc8d9cbd..e4b23e3c 100644 --- a/zanata-rest-client/src/test/java/org/zanata/rest/client/MockServerTestUtil.java +++ b/zanata-rest-client/src/test/java/org/zanata/rest/client/MockServerTestUtil.java @@ -57,8 +57,10 @@ public static RestClientFactory clientTalkingToRealServer() true); } + // FIXME this method doesn't verify anything! + @Deprecated static void verifyServerRespondSuccessStatus() { - assertThat("server return successfuly status code", true, Matchers + assertThat("server returns successful status code", true, Matchers .is(true)); } }