/*************************GO-LICENSE-START********************************* * Copyright 2014 ThoughtWorks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *************************GO-LICENSE-END***********************************/ package com.thoughtworks.go.plugin.access.scm; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.thoughtworks.go.plugin.access.common.handler.JSONResultMessageHandler; import com.thoughtworks.go.plugin.access.scm.material.MaterialPollResult; import com.thoughtworks.go.plugin.access.scm.revision.ModifiedAction; import com.thoughtworks.go.plugin.access.scm.revision.ModifiedFile; import com.thoughtworks.go.plugin.access.scm.revision.SCMRevision; import com.thoughtworks.go.plugin.api.config.Property; import com.thoughtworks.go.plugin.api.response.Result; import com.thoughtworks.go.plugin.api.response.validation.ValidationResult; import org.apache.commons.lang.StringUtils; import java.text.SimpleDateFormat; import java.util.*; import static java.lang.String.format; import static org.apache.commons.lang.StringUtils.isEmpty; public class JsonMessageHandler1_0 implements JsonMessageHandler { private static final String DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; private final JSONResultMessageHandler jsonResultMessageHandler; public JsonMessageHandler1_0() { jsonResultMessageHandler = new JSONResultMessageHandler(); } @Override public SCMPropertyConfiguration responseMessageForSCMConfiguration(String responseBody) { try { SCMPropertyConfiguration scmConfiguration = new SCMPropertyConfiguration(); Map<String, Map> configurations; try { configurations = parseResponseToMap(responseBody); } catch (Exception e) { throw new RuntimeException("SCM configuration should be returned as a map"); } if (configurations == null || configurations.isEmpty()) { throw new RuntimeException("Empty response body"); } for (String key : configurations.keySet()) { if (isEmpty(key)) { throw new RuntimeException("SCM configuration key cannot be empty"); } if (!(configurations.get(key) instanceof Map)) { throw new RuntimeException(format("SCM configuration properties for key '%s' should be represented as a Map", key)); } scmConfiguration.add(toSCMProperty(key, configurations.get(key))); } return scmConfiguration; } catch (Exception e) { throw new RuntimeException(format("Unable to de-serialize json response. %s", e.getMessage())); } } @Override public SCMView responseMessageForSCMView(String responseBody) { try { final Map map = parseResponseToMap(responseBody); if (map.isEmpty()) { throw new RuntimeException("The JSON for SCM View cannot be empty"); } final String displayValue; try { displayValue = (String) map.get("displayValue"); } catch (Exception e) { throw new RuntimeException("SCM View's 'displayValue' should be of type string"); } if (isEmpty(displayValue)) { throw new RuntimeException("SCM View's 'displayValue' is a required field"); } final String template; try { template = (String) map.get("template"); } catch (Exception e) { throw new RuntimeException("SCM View's 'template' should be of type string"); } if (isEmpty(template)) { throw new RuntimeException("SCM View's 'template' is a required field"); } return new SCMView() { @Override public String displayValue() { return displayValue; } @Override public String template() { return template; } }; } catch (Exception e) { throw new RuntimeException(String.format("Unable to de-serialize json response. Error: %s.", e.getMessage())); } } @Override public String requestMessageForIsSCMConfigurationValid(SCMPropertyConfiguration scmConfiguration) { Map configuredValues = new LinkedHashMap(); configuredValues.put("scm-configuration", jsonResultMessageHandler.configurationToMap(scmConfiguration)); return toJsonString(configuredValues); } @Override public ValidationResult responseMessageForIsSCMConfigurationValid(String responseBody) { return jsonResultMessageHandler.toValidationResult(responseBody); } @Override public String requestMessageForCheckConnectionToSCM(SCMPropertyConfiguration scmConfiguration) { Map configuredValues = new LinkedHashMap(); configuredValues.put("scm-configuration", jsonResultMessageHandler.configurationToMap(scmConfiguration)); return toJsonString(configuredValues); } @Override public Result responseMessageForCheckConnectionToSCM(String responseBody) { return jsonResultMessageHandler.toResult(responseBody); } @Override public String requestMessageForLatestRevision(SCMPropertyConfiguration scmConfiguration, Map<String, String> materialData, String flyweightFolder) { Map configuredValues = new LinkedHashMap(); configuredValues.put("scm-configuration", jsonResultMessageHandler.configurationToMap(scmConfiguration)); configuredValues.put("scm-data", materialData); configuredValues.put("flyweight-folder", flyweightFolder); return toJsonString(configuredValues); } @Override public MaterialPollResult responseMessageForLatestRevision(String responseBody) { Map responseBodyMap = getResponseMap(responseBody); return new MaterialPollResult(toMaterialDataMap(responseBodyMap), toSCMRevision(responseBodyMap)); } @Override public String requestMessageForLatestRevisionsSince(SCMPropertyConfiguration scmConfiguration, Map<String, String> materialData, String flyweightFolder, SCMRevision previousRevision) { Map configuredValues = new LinkedHashMap(); configuredValues.put("scm-configuration", jsonResultMessageHandler.configurationToMap(scmConfiguration)); configuredValues.put("scm-data", materialData); configuredValues.put("flyweight-folder", flyweightFolder); configuredValues.put("previous-revision", scmRevisionToMap(previousRevision)); return toJsonString(configuredValues); } @Override public MaterialPollResult responseMessageForLatestRevisionsSince(String responseBody) { if (isEmpty(responseBody)) return new MaterialPollResult(); Map responseBodyMap = getResponseMap(responseBody); return new MaterialPollResult(toMaterialDataMap(responseBodyMap), toSCMRevisions(responseBodyMap)); } @Override public String requestMessageForCheckout(SCMPropertyConfiguration scmConfiguration, String destinationFolder, SCMRevision revision) { Map configuredValues = new LinkedHashMap(); configuredValues.put("scm-configuration", jsonResultMessageHandler.configurationToMap(scmConfiguration)); configuredValues.put("destination-folder", destinationFolder); configuredValues.put("revision", scmRevisionToMap(revision)); return toJsonString(configuredValues); } @Override public Result responseMessageForCheckout(String responseBody) { return jsonResultMessageHandler.toResult(responseBody); } private List<Map> parseResponseToList(String responseBody) { return (List<Map>) new GsonBuilder().create().fromJson(responseBody, Object.class); } private Map parseResponseToMap(String responseBody) { return (Map) new GsonBuilder().create().fromJson(responseBody, Object.class); } private static String toJsonString(Object object) { Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); return gson.toJson(object); } private SCMProperty toSCMProperty(String key, Map configuration) { List<String> errors = new ArrayList<>(); String defaultValue = null; try { defaultValue = (String) configuration.get("default-value"); } catch (Exception e) { errors.add(format("'default-value' property for key '%s' should be of type string", key)); } Boolean partOfIdentity = null; try { partOfIdentity = (Boolean) configuration.get("part-of-identity"); } catch (Exception e) { errors.add(format("'part-of-identity' property for key '%s' should be of type boolean", key)); } Boolean isSecure = null; try { isSecure = (Boolean) configuration.get("secure"); } catch (Exception e) { errors.add(format("'secure' property for key '%s' should be of type boolean", key)); } Boolean required = null; try { required = (Boolean) configuration.get("required"); } catch (Exception e) { errors.add(format("'required' property for key '%s' should be of type boolean", key)); } String displayName = null; try { displayName = (String) configuration.get("display-name"); } catch (Exception e) { errors.add(format("'display-name' property for key '%s' should be of type string", key)); } Integer displayOrder = null; try { displayOrder = configuration.get("display-order") == null ? null : Integer.parseInt((String) configuration.get("display-order")); } catch (Exception e) { errors.add(format("'display-order' property for key '%s' should be of type integer", key)); } if (!errors.isEmpty()) { throw new RuntimeException(StringUtils.join(errors, ", ")); } SCMProperty scmProperty = new SCMProperty(key); if (!isEmpty(defaultValue)) { scmProperty.withDefault(defaultValue); } if (partOfIdentity != null) { scmProperty.with(Property.PART_OF_IDENTITY, partOfIdentity); } if (isSecure != null) { scmProperty.with(Property.SECURE, isSecure); } if (required != null) { scmProperty.with(Property.REQUIRED, required); } if (!isEmpty(displayName)) { scmProperty.with(Property.DISPLAY_NAME, displayName); } if (displayOrder != null) { scmProperty.with(Property.DISPLAY_ORDER, displayOrder); } return scmProperty; } private Map getResponseMap(String responseBody) { Map map = null; try { map = parseResponseToMap(responseBody); } catch (Exception e) { throw new RuntimeException("Response should be returned as a map"); } return map; } Map<String, String> toMaterialDataMap(Map map) { try { if (map == null || map.isEmpty()) { return null; } Map scmData = null; try { scmData = (Map) map.get("scm-data"); } catch (Exception e) { throw new RuntimeException("SCM data should be of type map"); } return scmData; } catch (Exception e) { throw new RuntimeException(format("Unable to de-serialize json response. %s", e.getMessage())); } } SCMRevision toSCMRevision(Map map) { try { if (map == null || map.get("revision") == null) { throw new RuntimeException("SCM revision cannot be empty"); } Map revisionMap = null; try { revisionMap = (Map) map.get("revision"); } catch (Exception e) { throw new RuntimeException("SCM revision should be of type map"); } return getScmRevisionFromMap(revisionMap); } catch (Exception e) { throw new RuntimeException(format("Unable to de-serialize json response. %s", e.getMessage())); } } List<SCMRevision> toSCMRevisions(Map map) { try { List<SCMRevision> scmRevisions = new ArrayList<>(); if (map == null || map.get("revisions") == null) { return scmRevisions; } List revisionMaps = null; try { revisionMaps = (List) map.get("revisions"); } catch (Exception e) { throw new RuntimeException("'revisions' should be of type list of map"); } if (revisionMaps != null && !revisionMaps.isEmpty()) { for (Object revision : revisionMaps) { if (!(revision instanceof Map)) { throw new RuntimeException("SCM revision should be of type map"); } } for (Object revisionObj : revisionMaps) { Map revisionMap = (Map) revisionObj; SCMRevision scmRevision = getScmRevisionFromMap(revisionMap); scmRevisions.add(scmRevision); } } return scmRevisions; } catch (Exception e) { throw new RuntimeException(format("Unable to de-serialize json response. %s", e.getMessage())); } } SCMRevision getScmRevisionFromMap(Map map) { String revision; try { revision = (String) map.get("revision"); } catch (Exception e) { throw new RuntimeException("SCM revision should be of type string"); } if (isEmpty(revision)) { throw new RuntimeException("SCM revision's 'revision' is a required field"); } Date timestamp; try { String timestampString = (String) map.get("timestamp"); timestamp = new SimpleDateFormat(DATE_PATTERN).parse(timestampString); } catch (Exception e) { throw new RuntimeException("SCM revision timestamp should be of type string with format yyyy-MM-dd'T'HH:mm:ss.SSS'Z' and cannot be empty"); } String revisionComment; try { revisionComment = (String) map.get("revisionComment"); } catch (Exception e) { throw new RuntimeException("SCM revision comment should be of type string"); } String user; try { user = (String) map.get("user"); } catch (Exception e) { throw new RuntimeException("SCM revision user should be of type string"); } Map data = (Map) map.get("data"); List<ModifiedFile> modifiedFiles = new ArrayList<>(); if (map.containsKey("modifiedFiles") && map.get("modifiedFiles") != null) { List modifiedFileMaps = null; try { modifiedFileMaps = (List) map.get("modifiedFiles"); } catch (Exception e) { throw new RuntimeException("SCM revision 'modifiedFiles' should be of type list of map"); } if (!modifiedFileMaps.isEmpty()) { for (Object message : modifiedFileMaps) { if (!(message instanceof Map)) { throw new RuntimeException("SCM revision 'modified file' should be of type map"); } } for (Object modifiedFileObj : modifiedFileMaps) { Map modifiedFileMap = (Map) modifiedFileObj; String fileName; try { fileName = (String) modifiedFileMap.get("fileName"); } catch (Exception e) { throw new RuntimeException("modified file 'fileName' should be of type string"); } if (isEmpty(fileName)) { throw new RuntimeException("modified file 'fileName' is a required field"); } String actionStr = null; ModifiedAction action; try { actionStr = (String) modifiedFileMap.get("action"); } catch (Exception e) { throw new RuntimeException("modified file 'action' should be of type string"); } try { action = ModifiedAction.valueOf(actionStr); } catch (Exception e) { throw new RuntimeException(String.format("modified file 'action' can only be %s, %s, %s", ModifiedAction.added, ModifiedAction.modified, ModifiedAction.deleted)); } modifiedFiles.add(new ModifiedFile(fileName, action)); } } } return new SCMRevision(revision, timestamp, user, revisionComment, data, modifiedFiles); } private Map scmRevisionToMap(SCMRevision scmRevision) { Map map = new LinkedHashMap(); map.put("revision", scmRevision.getRevision()); map.put("timestamp", new SimpleDateFormat(DATE_PATTERN).format(scmRevision.getTimestamp())); map.put("data", scmRevision.getData()); return map; } }