diff --git a/oslcjira/.checkstyle b/oslcjira/.checkstyle new file mode 100644 index 0000000..75246d3 --- /dev/null +++ b/oslcjira/.checkstyle @@ -0,0 +1,7 @@ + + + + + + + diff --git a/oslcjira/.classpath b/oslcjira/.classpath new file mode 100644 index 0000000..eca4bc2 --- /dev/null +++ b/oslcjira/.classpath @@ -0,0 +1,278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/oslcjira/.project b/oslcjira/.project new file mode 100644 index 0000000..14cf940 --- /dev/null +++ b/oslcjira/.project @@ -0,0 +1,18 @@ + + + oslcjira + This is the OSLC plugin for Atlassian JIRA. NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse. + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + + diff --git a/oslcjira/LICENSE b/oslcjira/LICENSE new file mode 100644 index 0000000..72e1d67 --- /dev/null +++ b/oslcjira/LICENSE @@ -0,0 +1,23 @@ + +Copyright (c) 2015, Ericsson AB. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or other +materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. \ No newline at end of file diff --git a/oslcjira/NOTICE b/oslcjira/NOTICE new file mode 100644 index 0000000..bd24f77 --- /dev/null +++ b/oslcjira/NOTICE @@ -0,0 +1,35 @@ +Ericsson Jira OSLC Plugin +Copyright (C) 2015 Ericsson AB. + +This product includes software developed at +Ericsson AB. (www.ericsson.com). + +This product includes code available under a +Eclipse Distribution License v. 1.0 + +Copyright (c) 2011-2013 IBM Corporation. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the Eclipse Foundation, Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/oslcjira/pom.xml b/oslcjira/pom.xml new file mode 100644 index 0000000..accd96e --- /dev/null +++ b/oslcjira/pom.xml @@ -0,0 +1,235 @@ + + + + 4.0.0 + com.ericsson.jira.oslc + oslcjira + 1.0 + + Ericsson + http://www.ericsson.se + + + + The BSD 2-Clause License + https://opensource.org/licenses/BSD-2-Clause + repo + + + + JIRA OSLC Provider + This is the OSLC plugin for Atlassian JIRA. + atlassian-plugin + + + lyo-releases + https://repo.eclipse.org/content/repositories/lyo-releases/ + + + + + + + + + + + + javax.servlet + javax.servlet-api + 3.0.1 + provided + + + com.google.code.gson + gson + 2.2.4 + + + org.apache.commons + commons-lang3 + 3.1 + + + javax.ws.rs + jsr311-api + 1.1.1 + provided + + + org.slf4j + slf4j-api + 1.6.6 + provided + + + javax.xml.bind + jaxb-api + 2.1 + provided + + + + com.atlassian.jira + jira-api + ${jira.version} + provided + + + com.atlassian.jira + jira-core + ${jira.version} + provided + + + com.atlassian.plugins.rest + atlassian-rest-common + 1.0.2 + provided + + + com.atlassian.sal + sal-api + 2.6.0 + provided + + + com.atlassian.templaterenderer + atlassian-template-renderer-api + 1.3.1 + provided + + + org.apache.httpcomponents + httpcore + 4.1.1 + + + org.apache.httpcomponents + httpclient + 4.1.1 + + + com.atlassian.activeobjects + activeobjects-plugin + 0.23.8 + provided + + + + org.eclipse.lyo.oslc4j.core + oslc4j-core + 2.1.0 + + + org.eclipse.lyo.oslc4j.core + oslc4j-json4j-provider + 2.1.0 + + + org.eclipse.lyo.oslc4j.core + oslc4j-jena-provider + 2.1.0 + + + xml-apis + xml-apis + + + + + org.eclipse.lyo.core.query + oslc-query + [1.1,) + + + org.eclipse.lyo.server + oauth-core + 2.1.0 + + + org.eclipse.lyo.server + oauth-consumer-store + 2.1.0 + + + + + + + + + + com.atlassian.maven.plugins + maven-jira-plugin + ${amps.version} + true + + ${jira.version} + ${jira.version} + + + + + + maven-compiler-plugin + + 1.6 + 1.6 + + + + ${buildDirectory} + + + ${project.basedir}/target + 6.3.6 + 5.0.4 + 1.2.0 + + 5.2.26 + + diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/SyncConfigLoader.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/SyncConfigLoader.java new file mode 100644 index 0000000..6d56784 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/SyncConfigLoader.java @@ -0,0 +1,841 @@ +package com.ericsson.eif.leansync.mapping; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.io.ByteArrayInputStream; +import java.io.StringWriter; +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.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.apache.xpath.jaxp.XPathFactoryImpl; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSOutput; +import org.w3c.dom.ls.LSSerializer; + +import com.ericsson.eif.leansync.mapping.data.ActionType; +import com.ericsson.eif.leansync.mapping.data.SyncConfiguration; +import com.ericsson.eif.leansync.mapping.data.SyncField; +import com.ericsson.eif.leansync.mapping.data.SyncGeneralField; +import com.ericsson.eif.leansync.mapping.data.SyncMapping; +import com.ericsson.eif.leansync.mapping.data.SyncTemplate; +import com.ericsson.eif.leansync.mapping.data.SyncXmlFieldConfig; +import com.ericsson.eif.leansync.mapping.exceptions.SyncConfigurationException; + +/** + * A class which servers for loading LeanSync configuration from xml + * + */ +public class SyncConfigLoader { + private final String CONFIG_NODE_CONNECTION = "connection"; + private final String CONFIG_ATTR_NS = "ns"; + private final String CONFIG_ATTR_NAME= "name"; + private final String CONFIG_ATTR_ID = "id"; + private final String CONFIG_ATTR_XPATH = "xpath"; + private final String CONFIG_ATTR_FIELD_TYPE = "fieldType"; + private final String CONFIG_ATTR_CONTENT_TYPE = "contentType"; + private final String CONFIG_ATTR_MAP_TO = "mapTo"; + private final String CONFIG_ATTR_NOTIFY_CHANGE = "notifyChange"; + private final String CONFIG_ATTR_ACTION = "action"; + private final String CONFIG_ATTR_KEEP_TAGS = "keepTags"; + private final String CONFIG_ATTR_ENCODE_HTML = "encodeHtml"; + private final String CONFIG_ATTR_TO_DATE_FORMAT = "toDateFormat"; + private final String CONFIG_ATTR_FROM_DATE_FORMAT = "fromDateFormat"; + private final String CONFIG_ATTR_USERNAME = "username"; + private final String CONFIG_ATTR_PASSWORD = "password"; + private final String CONFIG_NODE_CONFIG_FULL = "//configurations/configuration"; + private final String CONFIG_NODE_PROJECT_REL = "projects/project"; + private final String CONFIG_NODE_HEADER_REL = "headers/header"; + private final String CONFIG_NODE_DOMAIN_REL = "domains/domain"; + private final String CONFIG_NODE_RDFTYPE_REL = "rdfTypes/rdfType"; + private final String CONFIG_ATTR_HEADER_VALUE = "value"; + private final String CONFIG_NODE_TYPE_REL = "issueTypes/issueType"; + private final String CONFIG_NODE_MAPPING_IN_REL = "mappings/mappingIn"; + private final String CONFIG_NODE_MAPPING_OUT_REL = "mappings/mappingOut"; + private final String CONFIG_NODE_XMLFIELDS = "xmlFields"; + private final String CONFIG_NODE_FIELDS = "fields"; + private final String CONFIG_NODE_FIELD = "field"; + private final String CONFIG_NODE_TEMPLATE = "template"; + private final String CONFIG_NODE_TEMPLATE_FIELD_REL = "templateFields/templateField"; + private final String CONFIG_ATTR_ID_PREFIX = "idPrefix"; + private final String CONFIG_ATTR_ID_SUFFIX = "idSuffix"; + private final String CONFIG_ATTR_ALWAYS_SAVE = "alwaysSave"; + private final String CONFIG_NODE_ERRORLOG = "errorLog"; + private final String CONFIG_NODE_MAP_REL = "maps/map"; + private final String CONFIG_ATTR_KEY = "key"; + private final String CONFIG_ATTR_VALUE = "value"; + private final String CONFIG_ATTR_DEFAULT= "default"; + + /** + * Load LeanSync configuration from xml + * @param inputConfiguration the xml containing LeanSync configuration + * @return LeanSync configuration for each project + * @throws Exception + */ + public Map loadConfiguration(String inputConfiguration) throws Exception { + Map confMap = new HashMap(); + // initialize dom and xpath factory + DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); + domFactory.setNamespaceAware(true); + DocumentBuilder builder = domFactory.newDocumentBuilder(); + ByteArrayInputStream is = new ByteArrayInputStream(inputConfiguration.getBytes()); + Document doc = builder.parse(is); + + XPathFactory xpathFactory = new XPathFactoryImpl(); + XPath xpath = xpathFactory.newXPath(); + + // locate all property nodes: + NodeList configurationList = (NodeList) xpath.evaluate(CONFIG_NODE_CONFIG_FULL, doc, XPathConstants.NODESET); + + if (configurationList != null) { + for (int i = 0; i < configurationList.getLength(); i++) { + Node configuration = configurationList.item(i); + SyncConfiguration fieldConfiguration = processConfiguration(configuration, xpath); + for (String project : fieldConfiguration.getProjects()) { + confMap.put(project, fieldConfiguration); + } + } + } + return confMap; + } + + /** + * Load the configuration section from LeanSync configuration + * @param configurationNode the configuration section in xml + * @param xpath the path to the configuration section in xml + * @return the configuration for LeanSync + * @throws Exception + */ + private SyncConfiguration processConfiguration(Node configurationNode, XPath xpath ) throws Exception { + SyncConfiguration configuration = new SyncConfiguration(); + + processErrorLog(configurationNode, configuration, xpath); + processProjects(configurationNode, configuration, xpath); + processIssueTypes(configurationNode, configuration, xpath); + processDomains(configurationNode, configuration, xpath); + processMappings(configurationNode, configuration, xpath); + + return configuration; + } + + /** + * It validates if the IDs in the templates are defined in the field configuration + * @param mapping the mapping configuration + * @param name information text for validation. It specifies the type of mapping + * @return the validation message, it's empty if the configuration is valid + */ + private static String validateMapping(SyncMapping mapping, String name){ + List templates = mapping.getTemplates(); + StringBuilder validation = new StringBuilder(); + if (templates != null) { + for (SyncTemplate template : templates) { + String validateTemplate = validateTemplate(mapping, template, name); + if (validateTemplate != null) { + validation.append(validateTemplate); + } + } + } + if(validation.length() == 0){ + return null; + }else{ + return validation.toString(); + } + } + + /** + * It validates if the IDs in the template are defined in the field configuration + * @param mapping the mapping configuration + * @param temlate the template containing IDs which are checked if there are defined in the field configuration + * @param name information text for validation. It specifies the type of mapping + * @return the validation message, it's empty if the configuration is valid + */ + private static String validateTemplate(SyncMapping mapping, SyncTemplate template, String name){ + if(template.getTemplate() == null || mapping.getFields() == null){ + return null; + } + StringBuilder result = new StringBuilder(); + + List templateItem = new ArrayList(); + Pattern p = Pattern.compile(template.getIdPrefix() + ".+?" + template.getIdSuffix()); + Matcher m = p.matcher(template.getTemplate()); + + Set declaredFelds = new HashSet(); + List fields = mapping.getFields(); + for (SyncField leanSyncField : fields) { + declaredFelds.add(template.getIdPrefix() + leanSyncField.getId() + template.getIdSuffix()); + } + + List xmlFieldConfigsList = mapping.getXmlFieldConfigs(); + for (SyncXmlFieldConfig xmlFieldConfig : xmlFieldConfigsList) { + List fieldList = xmlFieldConfig.getFields(); + for (SyncField leanSyncField : fieldList) { + declaredFelds.add(template.getIdPrefix() + leanSyncField.getId() + template.getIdSuffix()); + } + } + + while(m.find()) { + templateItem.add(m.group()); + } + for (String item : templateItem) { + if(!declaredFelds.contains(item)){ + if(result.length() != 0){ + result.append(", "); + } + result.append(item); + } + } + + if(result.length() > 0){ + return name + " mapping - following items do not contain field declaration: " + result.toString() +". "; + } + + return null; + } + + /** + * Load the error log section + * @param configurationNode the configuration section + * @param configuration the configuration data + * @param xpath the path to the error log section + * @throws XPathExpressionException + */ + private void processErrorLog(Node configurationNode, SyncConfiguration configuration, XPath xpath) throws XPathExpressionException { + Node errorLogNode = (Node) xpath.evaluate(CONFIG_NODE_ERRORLOG, configurationNode, XPathConstants.NODE); + + if (errorLogNode == null) { + return; + } + + NamedNodeMap attributes = errorLogNode.getAttributes(); + Node nameNode = attributes.getNamedItem(CONFIG_ATTR_NAME); + + if (nameNode != null) { + configuration.setErrorLog(nameNode.getNodeValue()); + } + + } + + /** + * Load the projects section with the list of the projects which the configuration is valid for + * @param configurationNode the configuration section + * @param configuration the configuration data + * @param xpath the path to projects section + * @throws XPathExpressionException + */ + private void processProjects(Node configurationNode, SyncConfiguration configuration, XPath xpath) throws XPathExpressionException { + Set values = processStringNodeListAsSet(configurationNode, xpath, CONFIG_NODE_PROJECT_REL); + if(values != null){ + configuration.setProjects(values); + } + } + + /** + * Load the domains section with the list of the domains which the configuration is valid for + * @param configurationNode the configuration section + * @param configuration the configuration data + * @param xpath the xpath instance + * @throws XPathExpressionException + */ + private void processDomains(Node configurationNode, SyncConfiguration configuration, XPath xpath) throws XPathExpressionException { + Set values = processStringNodeListAsSet(configurationNode, xpath, CONFIG_NODE_DOMAIN_REL); + if(values != null){ + configuration.setDomains(values); + } + } + + /** + * Put the values of the nodes to the set + * @param parentNode the parent node of the nodes with the values + * @param xpath the xpath instance + * @param path the path to the nodes with the values + * @return the loaded set of the values + * @throws XPathExpressionException + */ + private Set processStringNodeListAsSet(Node parentNode, XPath xpath, String path) throws XPathExpressionException { + NodeList nodeList = (NodeList) xpath.evaluate(path, parentNode, XPathConstants.NODESET); + if(nodeList == null){ + return null; + } + + Set values = new HashSet(); + + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node != null) { + String value = node.getTextContent(); + if (value != null && !value.isEmpty()) { + values.add(value); + } + } + } + return values; + + } + + /** + * Load the issue types section with the list of the issue types which the configuration is valid for + * @param configurationNode the configuration section + * @param configuration the configuration data + * @param xpath the xpath instance + * @throws XPathExpressionException + */ + private void processIssueTypes(Node configurationNode, SyncConfiguration configuration, XPath xpath) throws XPathExpressionException { + Set values = processStringNodeListAsSet(configurationNode, xpath, CONFIG_NODE_TYPE_REL); + if(values != null){ + configuration.setIssueTypes(values); + } + } + + /** + * Load the mappings section with the list of the mapping configurations. + * The mapping configurations are added to the configuration data (configuration) + * @param configurationNode the configuration section + * @param configuration the configuration data + * @param xpath the xpath instance + * @throws XPathExpressionException + */ + private void processMappings(Node configurationNode, SyncConfiguration configuration, XPath xpath) throws Exception { + + String validationResult = null; + String validationInResult = processMappingList(CONFIG_NODE_MAPPING_IN_REL, configurationNode, configuration, xpath); + if (validationInResult != null) { + validationResult = (validationResult == null) ? new String(validationInResult) : validationResult + validationInResult; + } + + String validationOutResult = processMappingList(CONFIG_NODE_MAPPING_OUT_REL, configurationNode, configuration, xpath); + if (validationOutResult != null) { + validationResult = (validationResult == null) ? new String(validationOutResult) : validationResult + validationOutResult; + } + + if (validationResult != null) { + throw new SyncConfigurationException(validationResult); + } + } + + private String processMappingList(String mappingExpression, Node configurationNode, SyncConfiguration configuration, XPath xpath) throws Exception { + NodeList mappingList = (NodeList) xpath.evaluate(mappingExpression, configurationNode, XPathConstants.NODESET); + + String validationResult = null; + + if (mappingList != null) { + for (int i = 0; i < mappingList.getLength(); i++) { + Node mappingNode = mappingList.item(i); + SyncMapping mapping = processMapping(mappingNode, configuration, xpath); + String result = null; + if (CONFIG_NODE_MAPPING_IN_REL.equals(mappingExpression)) { + configuration.addInMapping(mapping); + result = validateMapping(mapping, "Inbound"); + } else if (CONFIG_NODE_MAPPING_OUT_REL.equals(mappingExpression)) { + configuration.addOutMapping(mapping); + result = validateMapping(mapping, "Outbound"); + } + if (result != null) { + validationResult = (validationResult == null) ? new String(result) : validationResult + result; + } + } + } + + return validationResult; + } + + /** + * Load the mapping section. + * @param mappingNode the mapping section + * @param config the configuration data + * @param xpath the xpath instance + * @return the mapping data + * @throws XPathExpressionException + * @throws SyncConfigurationException + */ + private SyncMapping processMapping(Node mappingNode, SyncConfiguration config, XPath xpath) throws XPathExpressionException, SyncConfigurationException { + SyncMapping leanSyncMapping = new SyncMapping(); + + processConnection(mappingNode, leanSyncMapping, xpath); + processFieldConfig(mappingNode, leanSyncMapping, xpath); + + return leanSyncMapping; + } + + + /** + * Load the templates section. The templates are added to the mapping data (mapping) + * @param mappingNode the mapping section + * @param mapping the mapping data + * @param xpath the xpath instance + * @throws XPathExpressionException + * @throws SyncConfigurationException + */ + private void processTemplates(Node mappingNode, SyncMapping mapping, XPath xpath) throws XPathExpressionException, SyncConfigurationException { + NodeList templateList = (NodeList) xpath.evaluate(CONFIG_NODE_TEMPLATE_FIELD_REL, mappingNode, XPathConstants.NODESET); + if(templateList == null){ + return; + } + + for (int i = 0; i < templateList.getLength(); i++) { + Node templateNode = templateList.item(i); + SyncTemplate template = processTemplate(templateNode, xpath); + if (template != null) { + mapping.addTemplate(template); + } + } + } + + /** + * Load the template section. + * @param templateFieldNode the templates section + * @param xpath the xpath instance + * @return the template data + * @throws XPathExpressionException + * @throws SyncConfigurationException + */ + private SyncTemplate processTemplate(Node templateFieldNode, XPath xpath) throws XPathExpressionException, SyncConfigurationException { + SyncTemplate template = new SyncTemplate(); + processGeneralField(template, templateFieldNode, xpath); + + NamedNodeMap attrs = templateFieldNode.getAttributes(); + Node prefixNode = attrs.getNamedItem(CONFIG_ATTR_ID_PREFIX); + Node suffixNode = attrs.getNamedItem(CONFIG_ATTR_ID_SUFFIX); + Node alwaysSave = attrs.getNamedItem(CONFIG_ATTR_ALWAYS_SAVE); + Node templateNode = (Node) xpath.evaluate(CONFIG_NODE_TEMPLATE, templateFieldNode, XPathConstants.NODE); + + if (prefixNode != null) { + String prefix = prefixNode.getNodeValue(); + template.setIdPrefix(prefix); + } + + if (suffixNode != null) { + String suffix = suffixNode.getNodeValue(); + template.setIdSuffix(suffix); + } + + if (alwaysSave != null) { + template.setAlwaysSave(alwaysSave.getNodeValue()); + } + + if(templateNode != null){ + String templateText = loadXMLNodeValue(templateNode); + template.setTemplate(templateText); + } + + return template; + } + + /** + * Load the parameters which are common for all the types of the fields + * @param field the field configuration + * @param node the field section + * @param xpath xpath instance + * @throws XPathExpressionException + * @throws SyncConfigurationException + */ + private void processGeneralField(SyncGeneralField field, Node node, XPath xpath) throws XPathExpressionException, SyncConfigurationException { + NamedNodeMap attrs = node.getAttributes(); + Node nsNode = attrs.getNamedItem(CONFIG_ATTR_NS); + Node nameNode = attrs.getNamedItem(CONFIG_ATTR_NAME); + Node fieldTypeNode = attrs.getNamedItem(CONFIG_ATTR_FIELD_TYPE); + Node contentTypeNode = attrs.getNamedItem(CONFIG_ATTR_CONTENT_TYPE); + Node mapToNode = attrs.getNamedItem(CONFIG_ATTR_MAP_TO); + Node notifyChangeNode = attrs.getNamedItem(CONFIG_ATTR_NOTIFY_CHANGE); + Node actionNode = attrs.getNamedItem(CONFIG_ATTR_ACTION); + + if (nsNode != null) { + field.setNs(nsNode.getNodeValue()); + } + if (nameNode != null) { + field.setName(nameNode.getNodeValue()); + } + if (fieldTypeNode != null) { + field.setFieldType(fieldTypeNode.getNodeValue()); + } + if (contentTypeNode != null) { + field.setContentType(contentTypeNode.getNodeValue()); + } + if (mapToNode != null) { + field.setMapTo(mapToNode.getNodeValue()); + } + if (notifyChangeNode != null) { + String notifyChangeVal = notifyChangeNode.getNodeValue(); + if(SyncConstants.BOOLEAN_FALSE.equalsIgnoreCase(notifyChangeVal)) { + field.setNotifyChange(false); + } + } + if (actionNode != null) { + String nodeValue = actionNode.getNodeValue(); + ActionType actionType = ActionType.getActionType(nodeValue); + field.setAction(actionType); + } + + Map valueMapping = processValueMapping(node, xpath); + field.setValueMapping(valueMapping); + + String defaultValue = processDefaultValue(node, xpath); + field.setDefaultValue(defaultValue); + } + + + /** + * Load the fields section. + * @param fieldsNode the fields section + * @param xpath xpath instance + * @return loaded the list of the field configuration + * @throws XPathExpressionException + * @throws SyncConfigurationException + */ + private List processFields(Node fieldsNode, XPath xpath) throws XPathExpressionException, SyncConfigurationException { + NodeList fieldNodeList = (NodeList) xpath.evaluate(CONFIG_NODE_FIELD, fieldsNode, XPathConstants.NODESET); + if(fieldNodeList == null){ + return null; + } + List fieldList = new ArrayList(); + + for (int i = 0; i < fieldNodeList.getLength(); i++) { + Node fieldNode = fieldNodeList.item(i); + if (fieldNode != null) { + SyncField field = processField(fieldNode, xpath); + if(field != null){ + fieldList.add(field); + } + } + } + return fieldList; + } + + /** + * Load the field section. + * @param fieldNode the field section + * @param xpath xpath instance + * @return loaded the field configuration + * @throws XPathExpressionException + * @throws SyncConfigurationException + */ + private SyncField processField(Node fieldNode, XPath xpath) throws XPathExpressionException, SyncConfigurationException { + SyncField field = new SyncField(); + processGeneralField(field, fieldNode, xpath); + + NamedNodeMap attributes = fieldNode.getAttributes(); + + Node idNode = attributes.getNamedItem(CONFIG_ATTR_ID); + Node xpathNode = attributes.getNamedItem(CONFIG_ATTR_XPATH); + Node keepTags = attributes.getNamedItem(CONFIG_ATTR_KEEP_TAGS); + Node encodeHTML = attributes.getNamedItem(CONFIG_ATTR_ENCODE_HTML); + Node toDateFormat = attributes.getNamedItem(CONFIG_ATTR_TO_DATE_FORMAT); + Node fromDateFormat = attributes.getNamedItem(CONFIG_ATTR_FROM_DATE_FORMAT); + + if (idNode != null) { + field.setId(idNode.getNodeValue()); + } + if (xpathNode != null) { + field.setXpath(xpathNode.getNodeValue()); + } + if (keepTags != null) { + String keepTagsVal = keepTags.getNodeValue(); + if(SyncConstants.BOOLEAN_TRUE.equalsIgnoreCase(keepTagsVal)) { + field.setKeepTags(true); + } + } + if (encodeHTML != null) { + String encodeHTMLVal = encodeHTML.getNodeValue(); + if(SyncConstants.BOOLEAN_TRUE.equalsIgnoreCase(encodeHTMLVal)) { + field.setEncodeHtml(true); + } + } + if (toDateFormat != null) { + field.setToDateFormat(toDateFormat.getNodeValue()); + } + if (fromDateFormat != null) { + field.setFromDateFormat(fromDateFormat.getNodeValue()); + } + + Map valueMapping = processValueMapping(fieldNode, xpath); + field.setValueMapping(valueMapping); + + return field; + } + + /** + * Load the connection section. + * The connection configuration is added to the mapping data + * @param mappingNode the mapping section + * @param xpath xpath instance + * @throws XPathExpressionException + * @throws SyncConfigurationException + */ + private void processConnection(Node mappingNode, SyncMapping mapping, XPath xpath) throws XPathExpressionException { + NodeList connNodeList = (NodeList) xpath.evaluate(CONFIG_NODE_CONNECTION, mappingNode, XPathConstants.NODESET); + + //username attribute + if (connNodeList != null && connNodeList.getLength() > 0) { + Node connNode = connNodeList.item(0); + NamedNodeMap connAttributes = connNode.getAttributes(); + Node usernameNode = connAttributes.getNamedItem(CONFIG_ATTR_USERNAME); + if (usernameNode != null) { + String username = usernameNode.getNodeValue(); + mapping.setUsername(username); + } + + //password attribute + Node passwordNode = connAttributes.getNamedItem(CONFIG_ATTR_PASSWORD); + if (passwordNode != null) { + String password = passwordNode.getNodeValue(); + mapping.setPassword(password); + } + + //Headers + NodeList headderList = (NodeList) xpath.evaluate(CONFIG_NODE_HEADER_REL, connNode, XPathConstants.NODESET); + if (headderList != null) { + HashMap headers = new HashMap(); + + for (int i = 0; i < headderList.getLength(); i++) { + Node headerNode = headderList.item(i); + NamedNodeMap attributes = headerNode.getAttributes(); + if (attributes != null) { + Node nameNode = attributes.getNamedItem(CONFIG_ATTR_NAME); + Node valueNode = attributes.getNamedItem(CONFIG_ATTR_HEADER_VALUE); + if (nameNode != null && nameNode.getNodeValue() != null && !nameNode.getNodeValue().isEmpty() && valueNode != null && valueNode.getNodeValue() != null) { + headers.put(nameNode.getNodeValue(), valueNode.getNodeValue()); + } + } + } + + if (!headers.isEmpty()) { + mapping.setHeaders(headers); + } + } + + //RDF types + NodeList rdfTypeList = (NodeList) xpath.evaluate(CONFIG_NODE_RDFTYPE_REL, connNode, XPathConstants.NODESET); + if (rdfTypeList != null) { + Set rdfTypes = new HashSet(); + + for (int i = 0; i < rdfTypeList.getLength(); i++) { + Node rdfTypeNode = rdfTypeList.item(i); + NamedNodeMap attributes = rdfTypeNode.getAttributes(); + if (attributes != null) { + Node valueNode = attributes.getNamedItem(CONFIG_ATTR_VALUE); + if (valueNode != null && valueNode.getNodeValue() != null && valueNode.getNodeValue() != null && !valueNode.getNodeValue().isEmpty() ) { + rdfTypes.add(valueNode.getNodeValue()); + } + } + } + + if (!rdfTypes.isEmpty()) { + mapping.setRdfTypes(rdfTypes); + } + } + + } + } + + /** + * Load the field configurations section. + * The field configurations are added to the mapping data (mapping) + * @param mappingNode the section containing field mapping configuration + * @param mapping the mapping data + * @param xpath xpath instance + * @throws XPathExpressionException + * @throws SyncConfigurationException + */ + private void processFieldConfig(Node mappingNode, SyncMapping mapping, XPath xpath) throws XPathExpressionException, SyncConfigurationException { + NodeList fieldsList = (NodeList) xpath.evaluate(CONFIG_NODE_FIELDS, mappingNode, XPathConstants.NODESET); + + if (fieldsList != null) { + for (int i = 0; i < fieldsList.getLength(); i++) { + Node fieldsNode = fieldsList.item(i); + List fieldList = processFields(fieldsNode, xpath); + if(fieldList != null) { + mapping.setFields(fieldList); + } + } + } + processTemplates(mappingNode, mapping, xpath); + processXmlFieldConfigs(mappingNode, mapping, xpath); + + } + + /** + * Load the field configurations section in xml content of the field. + * The field configurations are added to the mapping data (mapping) + * @param mappingNode the section containing the field mapping configurations + * @param mapping the mapping data + * @param xpath xpath instance + * @throws XPathExpressionException + * @throws SyncConfigurationException + */ + private void processXmlFieldConfigs(Node mappingNode, SyncMapping mapping, XPath xpath) throws XPathExpressionException, SyncConfigurationException { + + NodeList xmlFieldsConfigList = (NodeList) xpath.evaluate(CONFIG_NODE_XMLFIELDS, mappingNode, XPathConstants.NODESET); + if(xmlFieldsConfigList == null){ + return; + } + + for (int i = 0; i < xmlFieldsConfigList.getLength(); i++) { + Node xmlFieldsConfigNode = xmlFieldsConfigList.item(i); + if (xmlFieldsConfigNode != null) { + SyncXmlFieldConfig xmlFieldConfig = processXmlFieldConfig(xmlFieldsConfigNode, xpath); + if(xmlFieldConfig != null){ + mapping.addXmlFieldConfig(xmlFieldConfig); + } + } + } + } + + /** + * Load the field configuration section in xml content of the field. + * @param xmlFieldsConfigNode the section containing the field mapping configurations + * @param xpath the xpath instance + * @return loaded the field configuration section + * @throws XPathExpressionException + * @throws SyncConfigurationException + */ + private SyncXmlFieldConfig processXmlFieldConfig(Node xmlFieldsConfigNode, XPath xpath) throws XPathExpressionException, SyncConfigurationException { + SyncXmlFieldConfig xmlFieldConfig = new SyncXmlFieldConfig(); + + processXmlFieldsConfigProperties(xmlFieldsConfigNode, xmlFieldConfig); + List fields = processFields(xmlFieldsConfigNode, xpath); + if(fields != null){ + xmlFieldConfig.setFields(fields); + } + + return xmlFieldConfig; + } + + /** + * Load the properties of the xml field configuration + * @param xmlFieldsNode the field configuration containing the properties for mapping + * @param xmlFieldsConfig field mapping configuration data + * @throws XPathExpressionException + */ + private void processXmlFieldsConfigProperties(Node xmlFieldsNode, SyncXmlFieldConfig xmlFieldsConfig) throws XPathExpressionException { + NamedNodeMap attributes = xmlFieldsNode.getAttributes(); + + Node nsNode = attributes.getNamedItem(CONFIG_ATTR_NS); + Node nameNode = attributes.getNamedItem(CONFIG_ATTR_NAME); + + if (nsNode != null) { + xmlFieldsConfig.setNs(nsNode.getNodeValue()); + } + if (nameNode != null) { + xmlFieldsConfig.setName(nameNode.getNodeValue()); + } + } + + /** + * Load the section the value mapping + * @param node the value mapping section + * @param xpath xpath instance + * @return the value mapping table + * @throws XPathExpressionException + */ + private Map processValueMapping(Node node, XPath xpath) throws XPathExpressionException { + NodeList mapList = (NodeList) xpath.evaluate(CONFIG_NODE_MAP_REL, node, XPathConstants.NODESET); + + Map valueMapper = null; + if (mapList != null && mapList.getLength() > 0) { + valueMapper = new HashMap(); + for (int i = 0; i < mapList.getLength(); i++) { + Node mapNode = mapList.item(i); + + NamedNodeMap attrs = mapNode.getAttributes(); + Node keyNode = attrs.getNamedItem(CONFIG_ATTR_KEY); + Node valueNode = attrs.getNamedItem(CONFIG_ATTR_VALUE); + + if (keyNode != null && keyNode.getNodeValue() != null && valueNode != null && valueNode.getNodeValue() != null) { + valueMapper.put(keyNode.getNodeValue(), valueNode.getNodeValue()); + } + } + } + return valueMapper; + } + + /** + * Load default value for the value mapping + * @param node the section with default value configuration + * @param xpath xpath instance + * @return the default value for the value mapping + * @throws XPathExpressionException + */ + private String processDefaultValue(Node node, XPath xpath) throws XPathExpressionException { + Node defaultNode = (Node) xpath.evaluate(CONFIG_ATTR_DEFAULT, node, XPathConstants.NODE); + if(defaultNode != null){ + NamedNodeMap attrsDefault = defaultNode.getAttributes(); + Node defaultValueNode = attrsDefault.getNamedItem(CONFIG_ATTR_VALUE); + if(defaultValueNode != null){ + return defaultValueNode.getNodeValue(); + } + } + return null; + } + + /** + * Load xml value from the node + * @param node the node containing xml content + * @return xml value of the node + */ + public static String loadXMLNodeValue(Node node){ + if(node == null){ + return null; + } + + Document document = node.getOwnerDocument(); + DOMImplementationLS domImplLS = (DOMImplementationLS) document + .getImplementation(); + LSSerializer serializer = domImplLS.createLSSerializer(); + + LSOutput output=domImplLS.createLSOutput(); + output.setEncoding(document.getInputEncoding()); + StringWriter writer=new StringWriter(); + output.setCharacterStream(writer); + serializer.getDomConfig().setParameter("xml-declaration", false); + serializer.write(node,output); + String value = writer.toString(); + if(value != null){ + value = value.replaceFirst("<"+node.getNodeName()+".*?>\n?", ""); + int idx = value.indexOf(""); + if(idx >= 0){ + value = value.substring(0, idx); + } + } + return value; + } +} diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/SyncConstants.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/SyncConstants.java new file mode 100644 index 0000000..aba0d82 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/SyncConstants.java @@ -0,0 +1,45 @@ +package com.ericsson.eif.leansync.mapping; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * The constants for LeanSync + * + */ +public interface SyncConstants { + public static final String CONFIG_CONTENT_TYPE_HTML = "html"; + public static final String CONFIG_FIELD_TYPE_CUSTOM = "custom"; + public static final String ID_PREFIX_DEFAULT_VALUE = "%"; + public static final String ID_SUFFIX_DEFAULT_VALUE = "%"; + public static final String BOOLEAN_FALSE = "false"; + public static final String BOOLEAN_TRUE = "true"; + public static final String INBOUND_SYNC_STATUS_MARK = "---Inbound sync---"; + public static final String OUTBOUND_SYNC_STATUS_MARK = "---Outbound sync---"; + public static final String END_OF_LINE = "\n"; + public static final String HTML_END_OF_LINE = "
"; +} diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/SyncHelper.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/SyncHelper.java new file mode 100644 index 0000000..0722cdd --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/SyncHelper.java @@ -0,0 +1,201 @@ +package com.ericsson.eif.leansync.mapping; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringEscapeUtils; + +import com.ericsson.eif.leansync.mapping.data.SyncGeneralField; + + +public class SyncHelper { + + /** + * Encode all non alphanumeric chars are replaced with hex code + * and surrounded by "_" characters. + * @param name the name of tag + * @return encoded name + */ + public static String encodeTagName(String name) { + if(name == null){ + return null; + } + + final int len = name.length(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) { + Character ch = name.charAt(i); + if (!Character.isDigit(ch) && !Character.isLetter(ch)) { + // Replace with _hex value of ascii_ + sb.append('_'); + sb.append(Integer.toHexString((int) ch)); + sb.append('_'); + } else { + sb.append(ch); + } + } + + return sb.toString(); + } + + /** + * It creates the error information for inbound and outbound connection. + * Inbound information are at first and then the outbound information is. + * The section for inbound and outbound connection are + * separated by constants INBOUND_SYNC_STATUS_MARK and OUTBOUND_SYNC_STATUS_MARK + * If inbound information is added the current outbound information must be kept and vice-versia + * Set status as null to clear information for defined direction (in/out) + * @param content the current value of status + * @param status the information whichewill be added + * @param isInbound choice if added information is for in/out connection + * @return sync status + */ + public static String createSyncStatus(String content, String status, + boolean isInbound) { + + if (content == null) { + content = ""; + } + StringBuilder result = new StringBuilder(); + + int idxInboundMark = content.indexOf(SyncConstants.INBOUND_SYNC_STATUS_MARK); + int idxOutboundMark = content.indexOf(SyncConstants.OUTBOUND_SYNC_STATUS_MARK); + + if (isInbound) { + // Set inbound status + + if (status != null) { + result.append(SyncConstants.INBOUND_SYNC_STATUS_MARK); + result.append(SyncConstants.END_OF_LINE); + result.append(status.trim()); + } + if (idxOutboundMark >= 0) { + result.append(SyncConstants.END_OF_LINE); + String outBoundText = content.substring(idxOutboundMark); + result.append(outBoundText.trim()); + } + } else { + // Set Outbound status + if (idxInboundMark >= 0) { + String inboundText = null; + if (idxOutboundMark >= 0) { + inboundText = content.substring(idxInboundMark, + idxOutboundMark); + } else { + inboundText = content.substring(idxInboundMark); + } + result.append(inboundText.trim()); + result.append(SyncConstants.END_OF_LINE); + } + if (status != null) { + result.append(SyncConstants.OUTBOUND_SYNC_STATUS_MARK); + result.append(SyncConstants.END_OF_LINE); + result.append(status.trim()); + } + } + return result.toString().trim(); + } + + /** + * Add text to another text + * @param text current text + * @param textToAdd text which will be added to current text + * @return result text + */ + public static String appendText(String text, String textToAdd ){ + if(text == null){ + text = textToAdd; + }else if(textToAdd == null){ + text = textToAdd; + }else{ + text+= textToAdd; + } + return text; + } + + /** + * It maps the value to the defined value according to the configuration. + * When the value mapping doesn't match corresponding value the default value is set if it's defined + * If the mapping doesn't matches the value and default value is not defined the input value is returned + * @param value the value which will be mapped + * @param field the configuration of field which contains value mapping and defined default value + * @return mapped value + */ + public static String mapToValue(String value, SyncGeneralField field){ + Map valueMapping = field.getValueMapping(); + String defaultValue = field.getDefaultValue(); + + if(valueMapping != null && valueMapping.containsKey(value)){ + return valueMapping.get(value); + }else if(defaultValue != null){ + return defaultValue; + } + return value; + } + + + /** + * Convert the list of the string to the one string where each items are separated by the end of the line + * @param list the list of the string which will be put to one string + * @return converted the list of string to the one string + */ + public static String convertStringListToText(List list){ + if(list == null){ + return null; + } + + StringBuilder sb = new StringBuilder(); + for (String str : list) { + sb.append(str); + sb.append(SyncConstants.END_OF_LINE); + } + return sb.toString(); + } + + /** + * Replace \n for the
+ */ + public static String convertToHTML(String text){ + if(text == null){ + return null; + } + return text.replaceAll(SyncConstants.END_OF_LINE, SyncConstants.HTML_END_OF_LINE); + } + + /** + * Escape html text + */ + public static String encodeHTML(String text){ + if(text == null){ + return null; + } + return StringEscapeUtils.escapeHtml(text); + } +} diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/ActionType.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/ActionType.java new file mode 100644 index 0000000..3050682 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/ActionType.java @@ -0,0 +1,48 @@ +package com.ericsson.eif.leansync.mapping.data; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * The enumeration for action type. + * CREATE - a configuration is valid only when a resource in created + * UPDATE - the configuration is valid only when the resource is updated + * UNDEF - the configuration is valid for both action + * + */ +public enum ActionType { + CREATE, UPDATE, UNDEF; + + public static ActionType getActionType(String type) { + if("CREATE".equalsIgnoreCase(type)){ + return ActionType.CREATE; + }else if ("UPDATE".equalsIgnoreCase(type)){ + return UPDATE; + } + return ActionType.UNDEF; + } +} diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncConfiguration.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncConfiguration.java new file mode 100644 index 0000000..3d8dea9 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncConfiguration.java @@ -0,0 +1,140 @@ +package com.ericsson.eif.leansync.mapping.data; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.xml.bind.annotation.XmlElement; + +/** + * Data class representing the configuration. There can be one or more configurations for different projects + * + */ +public class SyncConfiguration { + + private Set projects; + private Set issueTypes; + private Set domains; + @XmlElement(name="inMappings") + private List inMappingList; + @XmlElement(name="outMappings") + private List outMappingList; + private String errorLog; + + public SyncConfiguration(){ + projects = new HashSet(); + issueTypes = new HashSet(); + domains = new HashSet(); + inMappingList = new ArrayList(); + outMappingList = new ArrayList(); + } + + public Set getProjects() { + return projects; + } + + public void setProjects(Set projects) { + this.projects = projects; + } + + public void addProject(String project) { + this.projects.add(project); + } + + public Set getIssueTypes() { + return issueTypes; + } + + public void setIssueTypes(Set issueTypes) { + this.issueTypes = issueTypes; + } + + public void addIssueType(String issueType) { + this.issueTypes.add(issueType); + } + + public boolean containsIssueType(String issueType) { + return (this.issueTypes != null && this.issueTypes.contains(issueType)); + } + + public List getInMappings() { + return inMappingList; + } + + public void setInMapping(List inMappings) { + this.inMappingList = inMappings; + } + + public List getOutMappings() { + return outMappingList; + } + + public void setOutMappings(List outMappings) { + this.outMappingList = outMappings; + } + + public void addOutMapping(SyncMapping mappings) { + this.outMappingList.add(mappings); + } + public void addInMapping(SyncMapping mappings) { + this.inMappingList.add(mappings); + } + + public SyncMapping getFirstInMapping() { + if(inMappingList != null && inMappingList.size() > 0){ + return inMappingList.get(0); + } + return null; + } + + public SyncMapping getFirstOutMapping() { + if(outMappingList != null && outMappingList.size() > 0){ + return outMappingList.get(0); + } + return null; + } + + public String getErrorLog() { + return errorLog; + } + + public void setErrorLog(String errorLog) { + this.errorLog = errorLog; + } + + public Set getDomains() { + return domains; + } + + public void setDomains(Set domains) { + this.domains = domains; + } + +} \ No newline at end of file diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncField.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncField.java new file mode 100644 index 0000000..7835c4c --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncField.java @@ -0,0 +1,97 @@ +package com.ericsson.eif.leansync.mapping.data; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * Data class containing a configuration for the synchronization between fields + * + */ +public class SyncField extends SyncGeneralField{ + //unique ID of field which is used e,g, in the template + private String id; + //location of the field in the xml. The path is defined by XPATH + private String xpath; + //true - the tags from the value of the filed will not be removed + private boolean keepTags; + //true - encode HTML character e.g. '<' -> '!lt;' + private boolean encodeHtml; + // input value of date will be converted to this format + private String toDateFormat; + //specifies the date format in input value of date + private String fromDateFormat; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getXpath() { + return xpath; + } + + public void setXpath(String xpath) { + this.xpath = xpath; + } + + public boolean isKeepTags() { + return keepTags; + } + + public void setKeepTags(boolean keepTags) { + this.keepTags = keepTags; + } + + public boolean isEncodeHtml() { + return encodeHtml; + } + + public void setEncodeHtml(boolean encodeHtml) { + this.encodeHtml = encodeHtml; + } + + public String getToDateFormat() { + return toDateFormat; + } + + public void setToDateFormat(String toDateFormat) { + this.toDateFormat = toDateFormat; + } + + public String getFromDateFormat() { + return fromDateFormat; + } + + public void setFromDateFormat(String fromDateFormat) { + this.fromDateFormat = fromDateFormat; + } + + +} \ No newline at end of file diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncGeneralField.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncGeneralField.java new file mode 100644 index 0000000..ec6a6da --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncGeneralField.java @@ -0,0 +1,111 @@ +package com.ericsson.eif.leansync.mapping.data; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.util.Map; + +/** + * Data class contains the common parameters for the fields of configuration + * + */ +public abstract class SyncGeneralField { + //the namespace of the field which will be mapped + private String ns; + //the name of the field whihc will be mapped + private String name; + //the type of field e.g. custom -> specifies the the field is custom and not general field of the system + private String fieldType; + //the type of field e.g. html - the characters "\n" will be replaced by
in outgoing data + private String contentType; + //The name of the field where the value will be put + private String mapTo; + //The type of action with resource - create or update + private ActionType action; + //the value can be mapped to another value e.g. for priority: A->1. B->2, C->3 + private Map valueMapping; + //true - when the field is change then the change is propagated to a remote system + private boolean notifyChange = true; + //when the value mapping doesn't match the value then the default value is used + private String defaultValue; + + public String getNs() { + return ns; + } + public void setNs(String ns) { + this.ns = ns; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getFieldType() { + return fieldType; + } + public void setFieldType(String fieldType) { + this.fieldType = fieldType; + } + public String getContentType() { + return contentType; + } + public void setContentType(String contentType) { + this.contentType = contentType; + } + public String getMapTo() { + return mapTo; + } + public void setMapTo(String mapTo) { + this.mapTo = mapTo; + } + public Map getValueMapping() { + return valueMapping; + } + public void setValueMapping(Map valueMapping) { + this.valueMapping = valueMapping; + } + public ActionType getAction() { + return action; + } + public void setAction(ActionType action) { + this.action = action; + } + public boolean isNotifyChange() { + return notifyChange; + } + public void setNotifyChange(boolean notifyChange) { + this.notifyChange = notifyChange; + } + public String getDefaultValue() { + return defaultValue; + } + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + +} diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncMapping.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncMapping.java new file mode 100644 index 0000000..439384d --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncMapping.java @@ -0,0 +1,162 @@ +package com.ericsson.eif.leansync.mapping.data; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * Data class for mapping between the fields. There can be two mapping for incoming and outgoing connections + * + */ +public class SyncMapping { + //the http headers which are put to the header of outgoing request + private Map headers; + //it specifies rdf types of the outgoing content + private Set rdfTypes; + //it's used for basic auth to the remote system + private String username; + //it's used for basic auth to the remote system + private String password; + //sync configuration for field mapping + private List fields; + //sync configuration for field mapping - the fields are saved in xml + private List xmlFieldConfigs; + //for mapping M:1 + private List templates; + + public SyncMapping(){ + headers = new HashMap(); + rdfTypes = new HashSet(); + fields = new ArrayList(); + xmlFieldConfigs = new ArrayList(); + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setTemplates(List templates) { + this.templates = templates; + } + + public Set getRdfTypes() { + return rdfTypes; + } + + public void setRdfTypes(Set rdfTypes) { + this.rdfTypes = rdfTypes; + } + + public void addTemplate(SyncXmlFieldConfig config){ + if(xmlFieldConfigs == null){ + xmlFieldConfigs = new ArrayList(); + } + xmlFieldConfigs.add(config); + } + + public void addField(SyncField field){ + if(fields == null){ + fields = new ArrayList(); + } + fields.add(field); + } + + public void addTemplate(SyncTemplate template){ + if(templates == null){ + templates = new ArrayList(); + } + templates.add(template); + } + + public void addXmlFieldConfig(SyncXmlFieldConfig xmlFieldConfig){ + if(xmlFieldConfigs == null){ + xmlFieldConfigs = new ArrayList(); + } + xmlFieldConfigs.add(xmlFieldConfig); + } + + public void addHeader(String name, String value){ + if(headers == null){ + headers = new HashMap(); + } + headers.put(name, value); + } + + public void addRdfType(String value){ + if(rdfTypes == null){ + rdfTypes = new HashSet(); + } + rdfTypes.add(value); + } + + public List getFields() { + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } + + public List getXmlFieldConfigs() { + return xmlFieldConfigs; + } + + public void setXmlFieldConfigs(List xmlFieldConfigs) { + this.xmlFieldConfigs = xmlFieldConfigs; + } + + public List getTemplates() { + return templates; + } +} diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncSnapshot.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncSnapshot.java new file mode 100644 index 0000000..814195e --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncSnapshot.java @@ -0,0 +1,61 @@ +package com.ericsson.eif.leansync.mapping.data; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * Data class contains the value of template and configuration of template field + * It serves for saving a snapshot and for field mapping M:1 + * + */ +public class SyncSnapshot { + //the configuration for template field + private SyncTemplate templateConfig; + //the value of the field which are mapped by the template + private String value; + + public SyncSnapshot(SyncTemplate templateConfig) { + this.templateConfig = templateConfig; + } + public SyncSnapshot(SyncTemplate templateConfig, String value) { + this.templateConfig = templateConfig; + this.value = value; + } + public SyncTemplate getTemplateConfig() { + return templateConfig; + } + public void setTemplateConfig(SyncTemplate templateConfig) { + this.templateConfig = templateConfig; + } + public String getValue() { + return value; + } + public void setValue(String value) { + this.value = value; + } + +} diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncTemplate.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncTemplate.java new file mode 100644 index 0000000..e6a9180 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncTemplate.java @@ -0,0 +1,78 @@ +package com.ericsson.eif.leansync.mapping.data; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import com.ericsson.eif.leansync.mapping.SyncConstants; + +/** + * Data class contains the template for the field mapping M:1 or the snapshot + * + */ +public class SyncTemplate extends SyncGeneralField{ + //the template containing special marks for inserting the field values + private String template; + //prefix for id of the field in the template + private String idPrefix; + //suffix for id of the field in the template + private String idSuffix; + //true - the mapped value of the field will be save even when the error accurs + private String alwaysSave; + + public SyncTemplate(){ + idPrefix = SyncConstants.ID_PREFIX_DEFAULT_VALUE; + idSuffix = SyncConstants.ID_SUFFIX_DEFAULT_VALUE; + } + + public String getTemplate() { + return template; + } + public void setTemplate(String template) { + this.template = template; + } + public String getIdPrefix() { + return idPrefix; + } + public void setIdPrefix(String idPrefix) { + this.idPrefix = idPrefix; + } + public String getIdSuffix() { + return idSuffix; + } + public void setIdSuffix(String idSuffix) { + this.idSuffix = idSuffix; + } + + public String getAlwaysSave() { + return alwaysSave; + } + + public void setAlwaysSave(String alwaysSave) { + this.alwaysSave = alwaysSave; + } + +} diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncXmlFieldConfig.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncXmlFieldConfig.java new file mode 100644 index 0000000..dec6862 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/data/SyncXmlFieldConfig.java @@ -0,0 +1,62 @@ +package com.ericsson.eif.leansync.mapping.data; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.util.List; + +/** + * Data class containing a configuration for the synchronization between fields + * The configuration is for the fields which are in the xml content of the field + * + */ +public class SyncXmlFieldConfig { + private String ns; + private String name; + private List fields; + + public String getNs() { + return ns; + } + public void setNs(String ns) { + this.ns = ns; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public List getFields() { + return fields; + } + public void setFields(List fields) { + this.fields = fields; + } + + +} diff --git a/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/exceptions/SyncConfigurationException.java b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/exceptions/SyncConfigurationException.java new file mode 100644 index 0000000..50816e6 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/eif/leansync/mapping/exceptions/SyncConfigurationException.java @@ -0,0 +1,52 @@ +package com.ericsson.eif.leansync.mapping.exceptions; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * The exception is thrown when the the Sync configuration is not valid. + * + */ +public class SyncConfigurationException extends Exception { + + private static final long serialVersionUID = -9007611492783867749L; + + public SyncConfigurationException(){ + } + + public SyncConfigurationException(String message){ + super(message); + } + + public SyncConfigurationException(Throwable cause){ + super(cause); + } + + public SyncConfigurationException(String message, Throwable cause){ + super(message, cause); + } +} \ No newline at end of file diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/Constants.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/Constants.java new file mode 100644 index 0000000..7895b59 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/Constants.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2012 IBM, 2013 Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * + * Russell Boykin - initial API and implementation + * Alberto Giammaria - initial API and implementation + * Chris Peters - initial API and implementation + * Gianluca Bernardini - initial API and implementation + * Michael Fiedler - Bugzilla adpater implementations + *******************************************************************************/ +package com.ericsson.jira.oslc; + +import org.eclipse.lyo.oslc4j.core.model.OslcConstants; + +public interface Constants +{ + public static String CHANGE_MANAGEMENT_DOMAIN = "http://open-services.net/ns/cm#"; + public static String CHANGE_MANAGEMENT_NAMESPACE = "http://open-services.net/ns/cm#"; + public static String CHANGE_MANAGEMENT_NAMESPACE_PREFIX = "oslc_cm"; + public static String FOAF_NAMESPACE = "http://xmlns.com/foaf/0.1/"; + public static String FOAF_NAMESPACE_PREFIX = "foaf"; + public static String QUALITY_MANAGEMENT_NAMESPACE = "http://open-services.net/ns/qm#"; + public static String QUALITY_MANAGEMENT_PREFIX = "oslc_qm"; + public static String REQUIREMENTS_MANAGEMENT_NAMESPACE = "http://open-services.net/ns/rm#"; + public static String REQUIREMENTS_MANAGEMENT_PREFIX = "oslc_rm"; + public static String SOFTWARE_CONFIGURATION_MANAGEMENT_NAMESPACE = "http://open-services.net/ns/scm#"; + public static String SOFTWARE_CONFIGURATION_MANAGEMENT_PREFIX = "oslc_scm"; + public static String JIRA_DOMAIN = "http://atlassian.com/ns/cm#"; + public static String JIRA_NAMESPACE = "http://atlassian.com/ns/cm#"; + public static String JIRA_NAMESPACE_PREFIX = "jira"; + + public static String CHANGE_REQUEST = "ChangeRequest"; + public static String TYPE_CHANGE_REQUEST = CHANGE_MANAGEMENT_NAMESPACE + "ChangeRequest"; + public static String TYPE_RELATED_CHANGE_REQUEST = CHANGE_MANAGEMENT_NAMESPACE + "relatedChangeRequest"; + public static String TYPE_CHANGE_SET = SOFTWARE_CONFIGURATION_MANAGEMENT_NAMESPACE + "ChangeSet"; + public static String TYPE_DISCUSSION = OslcConstants.OSLC_CORE_NAMESPACE + "Discussion"; + public static String TYPE_PERSON = FOAF_NAMESPACE + "Person"; + public static String TYPE_REQUIREMENT = REQUIREMENTS_MANAGEMENT_NAMESPACE + "Requirement"; + public static String TYPE_TEST_CASE = QUALITY_MANAGEMENT_NAMESPACE + "TestCase"; + public static String TYPE_TEST_EXECUTION_RECORD = QUALITY_MANAGEMENT_NAMESPACE + "TestExecutionRecord"; + public static String TYPE_TEST_PLAN = QUALITY_MANAGEMENT_NAMESPACE + "TestPlan"; + public static String TYPE_TEST_RESULT = QUALITY_MANAGEMENT_NAMESPACE + "TestResult"; + public static String TYPE_TEST_SCRIPT = QUALITY_MANAGEMENT_NAMESPACE + "TestScript"; + + public static String PATH_CHANGE_REQUEST = "changeRequest"; + + public static String USAGE_LIST = CHANGE_MANAGEMENT_NAMESPACE + "list"; + + public static final String HDR_OSLC_VERSION = "OSLC-Core-Version"; + public static final String OSLC_VERSION_V2 = "2.0"; + + public static final String NEXT_PAGE = "jira.NextPage"; + + //Jira issue - OSLC types + public static final String JIRA_TYPE_ASIGNEE = Constants.JIRA_NAMESPACE + "assignee"; + public static final String JIRA_TYPE_REPORTER = Constants.JIRA_NAMESPACE + "reporter"; + public static final String JIRA_TYPE_DESCRIPTION = OslcConstants.DCTERMS_NAMESPACE + "description"; + public static final String JIRA_TYPE_ENVIRONMNET = Constants.JIRA_NAMESPACE + "environment"; + public static final String JIRA_TYPE_PRIORITY = Constants.JIRA_NAMESPACE + "IssuePriority"; + public static final String JIRA_TYPE_PROJECT_ID = Constants.JIRA_NAMESPACE + "projectId"; + public static final String JIRA_TYPE_STATUS = Constants.JIRA_NAMESPACE + "IssueStatus"; + public static final String JIRA_TYPE_ISSUE_TYPE = Constants.JIRA_NAMESPACE + "IssueType"; + public static final String JIRA_TYPE_COMPONENT = Constants.JIRA_NAMESPACE + "component"; + public static final String JIRA_TYPE_AFFECTS_VERSION = Constants.JIRA_NAMESPACE + "affectsVersion"; + public static final String JIRA_TYPE_FIX_VERSION = Constants.JIRA_NAMESPACE + "fixVersion"; + public static final String JIRA_TYPE_ORIGINAL_ESTIMATE = Constants.JIRA_NAMESPACE + "originalEstimate"; + public static final String JIRA_TYPE_REMAINING_ESTIMATE = Constants.JIRA_NAMESPACE + "remainingEstimate"; + public static final String JIRA_TYPE_TIME_SPENT = Constants.JIRA_NAMESPACE + "timeSpent"; + public static final String JIRA_TYPE_RESOLUTION = Constants.JIRA_NAMESPACE + "IssueResolution"; + public static final String JIRA_TYPE_LABEL = Constants.JIRA_NAMESPACE + "label"; + public static final String JIRA_TYPE_VOTER = Constants.JIRA_NAMESPACE + "voter"; + public static final String JIRA_TYPE_WATCHER = Constants.JIRA_NAMESPACE + "watcher"; + public static final String JIRA_TYPE_CUSTOM_FIELD = Constants.JIRA_NAMESPACE + "customField"; + public static final String JIRA_TYPE_HISTORY = Constants.JIRA_NAMESPACE + "IssueHistory"; + + //dcterms types + public static final String DCTERMS_TITLE = OslcConstants.DCTERMS_NAMESPACE + "title"; + public static final String DCTERMS_DESCRIPTION = OslcConstants.DCTERMS_NAMESPACE + "description"; + public static final String DCTERMS_IDENTIFIER = OslcConstants.DCTERMS_NAMESPACE + "identifier"; + public static final String DCTERMS_DUEDATE = OslcConstants.DCTERMS_NAMESPACE + "dueDate"; + + //OSLC constant + public static final String OSLC_CM_STATUS = "http://open-services.net/ns/cm#" + "status"; + +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/Credentials.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/Credentials.java new file mode 100644 index 0000000..d4320e4 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/Credentials.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2011 IBM Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package com.ericsson.jira.oslc; + +/** + * Encapsulates a Bugzilla username and password. + * + * @author Samuel Padgett + */ +public class Credentials { + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} \ No newline at end of file diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/HTTP.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/HTTP.java new file mode 100644 index 0000000..4779c80 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/HTTP.java @@ -0,0 +1,455 @@ +package com.ericsson.jira.oslc; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import net.oauth.OAuth; +import net.oauth.OAuthAccessor; +import net.oauth.OAuthException; +import net.oauth.OAuthMessage; + +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ericsson.jira.oslc.utils.LogUtils; + + +/** + * A simple HTTP client with support for authentication. + */ +public class HTTP { + private static final String CURRENT_CLASS = "HTTP"; + + public static enum OAuthPhases { + /** + * OAuth dance phase 1 - getting request token + */ + OAUTH_PHASE_1, + /** + * OAuth dance phase 2 - user authorization and getting access token + */ + OAUTH_PHASE_2, + /** + * OAuth authorization (using access token) + */ + OAUTH_PHASE_3 + }; + + private static final Logger logger = LoggerFactory.getLogger(HTTP.class); + + /** + * It wraps the DefaultHttpClient to avoid to use ssl connection. + * @param base default http client + * @return + */ + private static DefaultHttpClient wrapClient(DefaultHttpClient base) { + String currentMethod = "wrapClient"; + try { + SSLContext ctx = SSLContext.getInstance("TLS"); + X509TrustManager tm = new X509TrustManager() { + + public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return (X509Certificate[]) null; + } + }; + ctx.init(null, new TrustManager[] { tm }, null); + SSLSocketFactory ssf = new SSLSocketFactory(ctx); + ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + ClientConnectionManager ccm = base.getConnectionManager(); + SchemeRegistry sr = ccm.getSchemeRegistry(); + sr.register(new Scheme("https", ssf, 443)); + return new DefaultHttpClient(ccm, base.getParams()); + } + catch (Exception ex) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + ex.getMessage()); + return null; + } + } + + /** + * Creates OAuth header which is different for individual oauth phases + * @param url the url where the request will be sent + * @param method http method - POST, PUT, GET ... + * @param accessor OAuth accessor + * @param verifier verification code + * @param phase oauth phase + * @return OAuth header + */ + private static String getOAuthAuthorizationHeader( + String url, String method, OAuthAccessor accessor, String verifier, OAuthPhases phase) { + String currentMethod = "getOAuthAuthorization"; + + String oAuthHeader = null; + + List params = new ArrayList(); + switch (phase) { + case OAUTH_PHASE_1: + params.add(new OAuth.Parameter("oauth_callback", accessor.consumer.callbackURL)); + break; + case OAUTH_PHASE_2: + params.add(new OAuth.Parameter("oauth_token", accessor.requestToken)); + if (verifier != null) { + params.add(new OAuth.Parameter("oauth_verifier", verifier)); + } + break; + case OAUTH_PHASE_3: + params.add(new OAuth.Parameter("oauth_token", accessor.accessToken)); + break; + } + + OAuthMessage request; + try { + request = accessor.newRequestMessage(method, url, params); + oAuthHeader = request.getAuthorizationHeader("JIRA"); + } + catch (OAuthException e) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + } + catch (IOException e) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + } + catch (URISyntaxException e) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + } + + return oAuthHeader; + } + + /** + * Get resource on defined url + * @param url the location of the resource + * @param accept string with accept content definition (e.g. "text/html") + * @param user user login used in basic authorization + * @param password user password used in basic authorization + * @param headers the headers which will be added to the request + * @return http response from successful request, otherwise return null + * @throws ClientProtocolException + * @throws IOException + */ + public static HttpResponse get(String url, String accept, String user, String password, Map headers) + throws ClientProtocolException, IOException { + return get(url, accept, null, user, password, null, null, null, headers); + } + + /** + * Get resource on defined url + * @param url the location of the resource + * @param accept string with accept content definition (e.g. "text/html") + * @param content content-type - the type of the content in the body of the request (e.g. "text/html") + * @param user user login used in basic authorization + * @param password user password used in basic authorization + * @param headers the headers which will be added to the request + * @return http response from successful request, otherwise return null + * @throws ClientProtocolException + * @throws IOException + */ + public static HttpResponse get(String url, String accept, String content, String user, String password, Map headers) + throws ClientProtocolException, IOException { + return get(url, accept, content, user, password, null, null, OAuthPhases.OAUTH_PHASE_3, headers); + } + + /** + * Methods create and send http GET request to given url. + * @param url link, where request is sent + * @param accept string with accept content definition (e.g. "text/html") + * @param user user login used in basic authorization + * @param password user password used in basic authorization + * @param accessor used in OAuth authorization, contains consumer information and tokens + * @param phase OAuth authorization phase + * @param headers the headers which will be added to the request + * @return http response from successful request, otherwise return null + * @throws IOException + * @throws ClientProtocolException + */ + public static HttpResponse get(String url, String accept, String content, + String user, String password, OAuthAccessor accessor, String verifier, OAuthPhases phase, Map headers) + throws ClientProtocolException, IOException { + String currentMethod = "get"; + + DefaultHttpClient client = new DefaultHttpClient(); + client = wrapClient(client); + + HttpGet httpget = new HttpGet(url); + + + + httpget.addHeader("OSLC-Core-Version", "2.0"); + if (accept != null) + httpget.addHeader("Accept", accept); + if (content != null) + httpget.addHeader("Content-Type", content); + + if (accessor != null) { + httpget.addHeader( + "Authorization", getOAuthAuthorizationHeader(url, "GET", accessor, verifier, phase)); + } + else if (user != null && password != null) { + StringBuilder builder = new StringBuilder(); + httpget.addHeader("Authorization", "Basic " + + javax.xml.bind.DatatypeConverter.printBase64Binary( + builder.append(user).append(':').append(password).toString().getBytes())); + } + + if(headers != null){ + Set> entrySet = headers.entrySet(); + for (Entry entry : entrySet) { + httpget.addHeader(entry.getKey(), entry.getValue()); + } + } + + try { + String logMessage =LogUtils.createLogMessage(url, null, httpget.getAllHeaders(),"GET"); + logger.debug(logMessage); + HttpResponse response = client.execute(httpget); + logResponse(response, url, "GET"); + return response; + + } + catch (ClientProtocolException e) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + throw e; + } + catch (IOException e) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + throw e; + } + } + + /** + * Methods creates and send http POST request to given url. + * @param url link, where request is sent + * @param body body of request, which will be sent + * @param accept accept string with accept content definition (e.g. "text/html") + * @param content content string with body format definition (e.g. "text/html") + * @param user user login used in basic authorization + * @param password user password used in basic authorization + * @return http response from successful request, otherwise returns null + * @throws IOException + * @throws ClientProtocolException + */ + public static HttpResponse post(String url, String body, String accept, String content, + String user, String password) throws ClientProtocolException, IOException { + return post(url, body, accept, content, user, password, null, null, OAuthPhases.OAUTH_PHASE_3); + } + + /** + * Methods creates and send http POST request to given url. + * @param url link, where request is sent + * @param body body of request, which will be sent + * @param accept accept string with accept content definition (e.g. "text/html") + * @param content content string with body format definition (e.g. "text/html") + * @param user user login used in basic authorization + * @param password user password used in basic authorization + * @param accessor used in OAuth authorization, contains consumer information and tokens + * @param phase OAuth authorization phase + * @return http response from successful request, otherwise returns null + * @throws IOException + * @throws ClientProtocolException + */ + public static HttpResponse post(String url, String body, String accept, String content, + String user, String password, OAuthAccessor accessor, String verifier, OAuthPhases phase) + throws ClientProtocolException, IOException { + String currentMethod = "post"; + DefaultHttpClient client = new DefaultHttpClient(); + client = wrapClient(client); + + HttpPost httpPost = new HttpPost(url); + + httpPost.addHeader("OSLC-Core-Version", "2.0"); + if (accept != null) + httpPost.addHeader("Accept", accept); + if (content != null) + httpPost.addHeader("Content-Type", content); + + if (accessor != null) { + httpPost.addHeader( + "Authorization", getOAuthAuthorizationHeader(url, "POST", accessor, verifier, phase)); + } + else if (user != null && password != null) { + StringBuilder builder = new StringBuilder(); + httpPost.addHeader("Authorization", "Basic " + + javax.xml.bind.DatatypeConverter.printBase64Binary( + builder.append(user).append(':').append(password).toString().getBytes())); + } + + try { + httpPost.setEntity(new StringEntity(body, "UTF-8")); + + String logMessage =LogUtils.createLogMessage(url, null, httpPost.getAllHeaders(),"POST"); + logger.debug(logMessage); + HttpResponse response = client.execute(httpPost); + logResponse(response, url, "POST"); + return response; + + } + catch (ClientProtocolException e) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + throw e; + } + catch (IOException e) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + throw e; + } + } + + /** + * Methods creates and send http PUT request to given url. + * @param url link, where request is sent + * @param body body of request, which will be sent + * @param accept accept string with accept content definition (e.g. "text/html") + * @param content content string with body format definition (e.g. "text/html") + * @param user user login used in basic authorization + * @param password user password used in basic authorization + * @param accessor used in OAuth authorization, contains consumer information and tokens + * @param phase OAuth authorization phase + * @return http response from successful request, otherwise returns null + * @throws IOException + * @throws ClientProtocolException + */ + public static HttpResponse put(String url, String body, String accept, String content, + String user, String password, OAuthAccessor accessor, String appendix, OAuthPhases phase) + throws ClientProtocolException, IOException { + return put(url, body, accept, content, + user, password, accessor, appendix, phase, null); +} + + /** + * Methods creates and send http PUT request to given url. + * @param url link, where request is sent + * @param body body of request, which will be sent + * @param accept accept string with accept content definition (e.g. "text/html") + * @param content content string with body format definition (e.g. "text/html") + * @param user user login used in basic authorization + * @param password user password used in basic authorization + * @param accessor used in OAuth authorization, contains consumer information and tokens + * @param phase OAuth authorization phase + * @param headers http headers + * @return http response from successful request, otherwise returns null + * @throws IOException + * @throws ClientProtocolException + */ + public static HttpResponse put(String url, String body, String accept, String content, + String user, String password, OAuthAccessor accessor, String appendix, OAuthPhases phase, Map headers) + throws ClientProtocolException, IOException { + String currentMethod = "put"; + + DefaultHttpClient client = new DefaultHttpClient(); + client = wrapClient(client); + + String targetUrl = url; + if (appendix != null) + targetUrl += appendix; + + HttpPut httpput = new HttpPut(targetUrl); + + httpput.addHeader("OSLC-Core-Version", "2.0"); + if (accept != null) + httpput.addHeader("Accept", accept); + if (content != null) + httpput.addHeader("Content-Type", content); + + + if(headers != null){ + Set> entrySet = headers.entrySet(); + for (Entry entry : entrySet) { + httpput.addHeader(entry.getKey(), entry.getValue()); + } + } + + if (accessor != null) { + httpput.addHeader( + "Authorization", "OAuth " + getOAuthAuthorizationHeader(targetUrl, "PUT", accessor, null, phase)); + } + else if (user != null && password != null) { + StringBuilder builder = new StringBuilder(); + httpput.addHeader("Authorization", "Basic " + + javax.xml.bind.DatatypeConverter.printBase64Binary( + builder.append(user).append(':').append(password).toString().getBytes())); + } + + try { + httpput.setEntity(new StringEntity(body, "UTF-8")); + String logMessage =LogUtils.createLogMessage(url, body, httpput.getAllHeaders(),"PUT"); + logger.debug(logMessage); + + HttpResponse response = client.execute(httpput); + logResponse(response, url, "PUT"); + + return response; + + } + catch (ClientProtocolException e) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + throw e; + } + catch (IOException e) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + throw e; + } + } + + private static void logResponse(HttpResponse response, String url, String action){ + if (response != null && response.getStatusLine() != null) { + logger.debug("Response of " + action + " request for uri = " + url + ", responseCode = " + response.getStatusLine().getStatusCode() + ", responsePhrase = " + response.getStatusLine().getReasonPhrase()); + } else { + logger.error("Response of " + action + " request for uri = " + url + " is null"); + } + } + + +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/PluginConfig.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/PluginConfig.java new file mode 100644 index 0000000..2b00a4e --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/PluginConfig.java @@ -0,0 +1,155 @@ +package com.ericsson.jira.oslc; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.ericsson.jira.oslc.resources.ao.AOManager; + +/** + * + * The configuration for the plugin + * It;s possible to set the list of projects which will be visible from outside (e.g. OSLC catalog contain only projects + * in the list). If no project is set then all projects + * will be visible from outside. The list contains the IDs of projects, not the names because the name can be changed + * It's also possible to set the list of issue types. The rule is the same as for the projects. + * + * + */ +public class PluginConfig { + private Set filteredProjects; + private Set filteredTypes; + private static PluginConfig instance; + public static final String FILTERED_PROJECTS = "filteredProjects"; + public static final String FILTERED_TYPES = "filteredTypes"; + + public static PluginConfig getInstance() throws Exception { + if (instance == null) { + instance = new PluginConfig(true); + } + return instance; + } + + public static PluginConfig getInstance(boolean loadConfig) throws Exception { + if (instance == null) { + instance = new PluginConfig(loadConfig); + } + return instance; + } + + /** + * It creates empty configuration and if the argument loadConfig is set on 'true' then the configuration + * is loaded from DB + * @param loadConfig the sync configuration will be loaded from DB, false - it creates empty configuration + * @throws Exception + */ + private PluginConfig(boolean loadConfig) throws Exception { + filteredProjects = new HashSet(); + filteredTypes = new HashSet(); + if(loadConfig){ + loadConfiguration(); + } + } + + /** + * It loads plugin configuration from DB + * @throws Exception + */ + private void loadConfiguration() throws Exception { + + AOManager mngr = AOManager.getInstance(); + Map configValues = mngr.getConfigValues(); + if (configValues != null) { + loadFilter(filteredProjects, configValues.get(FILTERED_PROJECTS)); + loadFilter(filteredTypes, configValues.get(FILTERED_TYPES)); + }else{ + filteredProjects.clear(); + filteredTypes.clear(); + } + } + + /** + * It creates sync configuration from defined configuration which is stored in the argument inputConfiguration + * @param inputFilteredProjects the list of ids of projects separated by comma + * @param inputFilteredTypes the list of ids of issue types separated by comma + * @throws Exception + */ + public void loadConfiguration(String inputFilteredProjects, String inputFilteredTypes) throws Exception { + if (inputFilteredProjects != null && !inputFilteredProjects.isEmpty()) { + loadFilter(filteredProjects, inputFilteredProjects); + }else{ + filteredProjects.clear(); + } + + if (inputFilteredTypes != null && !inputFilteredTypes.isEmpty()) { + loadFilter(filteredTypes, inputFilteredTypes); + }else{ + filteredTypes.clear(); + } + } + + /** + * Parses the value of argument input containing the list of projects/issue types separated by comma. + * The set of project/issue types saves to argument filter + * @param filter the set of projects/issue types extracted from input + * @param input the list of projects/issue types separated by comma + * @throws Exception + */ + public void loadFilter(Setfilter, String input) throws Exception { + filter.clear(); + if (input != null && !input.isEmpty()) { + String[] splited = input.trim().split(","); + for (String str : splited) { + str= str.trim(); + if(!str.isEmpty()){ + filter.add(new Long(str)); + } + } + } + } + + public Set getFilteredProjects() { + return filteredProjects; + } + + public void setFilteredProjects(Set filteredProjects) { + this.filteredProjects = filteredProjects; + } + + public Set getFilteredTypes() { + return filteredTypes; + } + + public void setFilteredTypes(Set filteredTypes) { + this.filteredTypes = filteredTypes; + } + + +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/constants/JiraConstants.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/constants/JiraConstants.java new file mode 100644 index 0000000..f982b54 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/constants/JiraConstants.java @@ -0,0 +1,77 @@ +package com.ericsson.jira.oslc.constants; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +public interface JiraConstants { + public static final String REST_URL="/rest/jirarestresource/1.0/"; + public static final String ACTIVATE_CONSUMER_URL="oauth/consumers/activate/"; + public static final String REMOVE_CONSUMER_URL="oauth/consumers/remove/"; + public static final String REMOVE_RS_LINK = "oauth/rootserviceslinks/removelink/"; + public static final String REMOVE_SP_CATALOG_LINK = "oauth/serviceprovidercatalogslinks/removelink/"; + public static final String REMOVE_SERVICEPROVIDER_LINK = "oauth/serviceproviderslinks/removelink/"; + public static final String OAUTH_CONSUMER_PAGE_URL="/plugins/servlet/jiraservlet/OAuthConsumerServlet"; + public static final String ROOTSEERVICES_MANAGEMENTT_PAGE = "/plugins/servlet/jiraservlet/RootServicesManagementServlet"; + public static final String CATALOGS_MANAGEMENT_PAGE = "/plugins/servlet/jiraservlet/ServiceProviderCatalogsManagementServlet"; + public static final String PROJECT_RELATIONSHIPS_PAGE = "/plugins/servlet/jiraservlet/ProjectRelationshipsServlet"; + public static final String ISSUE_TYPE_PATH = "issueTypes/"; + public static final String ISSUE_PRIORITY_PATH = "issuePriorities/"; + public static final String ISSUE_STATUS_PATH = "issueStates/"; + public static final String ISSUE_RESOLUTION_PATH = "issueResolutions/"; + public static final String CREATION_DIALOG_WIDTH="900px"; + public static final String CREATION_DIALOG_HEIGHT="600px"; + public static final String SELECTION_DIALOG_WIDTH="900px"; + public static final String SELECTION_DIALOG_HEIGHT="600px"; + public static final String CM_CHANGE_REQUEST= "http://open-services.net/ns/cm#ChangeRequest"; + public static final String CREATE_ISSUE = "/plugins/servlet/jiraservlet/createissue"; + public static final String SELECT_ISSUE = "/plugins/servlet/jiraservlet/selectissue"; + public static final String REMOVE_OSLC_LINK_FROM_REMOTE_APP = "oslc/links/removeFromRemoteApp/"; + public static final String REMOVE_OSLC_LINK_FROM_JIRA = "oslc/links/removeFromJira/"; + public static final String ADD_OSLC_LINK_DIALOG="/plugins/servlet/jiraservlet/addoslclinkdialog"; + public static final String ADD_OSLC_LINK_TO_REMOTE_APP = "oslc/links/addToRemoteApp/"; + public static final String ADD_OSLC_LINK_TO_JIRA = "oslc/links/addToJira/"; + public static final String OSLC_CUSTOM_FIELD_NAME="External Links"; + public static final String OSLC_CUSTOM_FIELD_LABEL="Label"; + public static final String OSLC_CUSTOM_FIELD_URI="URI"; + public static final String GET_OSLC_LINK_TYPES="oslc/links/types/"; + public static final String OSLC_RESPONSE_TYPE_1 = "#oslc-windowName-1.0"; + public static final String OSLC_RESPONSE_TYPE_2 = "#oslc-postMessage-1.0"; + public static final String OSLC_RESPONSE_TYPE_3 = "#oslc-core-windowName-1.0"; + public static final String OSLC_RESPONSE_TYPE_4 = "#oslc-core-postMessage-1.0"; + public static final String SESSION_OAUTHACCESSOR = "oAuthAccessor"; + public static final String SESSION_CURRENT_LINK = "currentOperationLink"; + public static final String OAUTH_CALLBACK_SERVICE_URL = REST_URL + "oauth/authorizationcallback"; + public static final String OAUTH_EXT_CALLBACK_SERVICE_URL = REST_URL + "oauth/authorizationexternalcallback"; + public static final String RELATED_CHANGE_REQUEST_URL_APPENDIX = "?oslc.properties=oslc_cm%3ArelatedChangeRequest&oslc.prefix=oslc_cm%3D%3Chttp%3A%2F%2Fopen-services.net%2Fns%2Fcm%23%3E"; + public static final String ISSUE_ICON = "/images/icons/favicon.png"; + public static final String REALM_NAME = "JIRA"; + public static final String SYNC_HEADER_NAME = "LeanSync"; + public static final int SINGLE_TEXT_LIMIT = 255; + public static final String XSD_PATH = "/config/leanSyncConfig.xsd"; + + +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/customfields/OSLCLink.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/customfields/OSLCLink.java new file mode 100644 index 0000000..7eb2e56 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/customfields/OSLCLink.java @@ -0,0 +1,120 @@ +package com.ericsson.jira.oslc.customfields; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.atlassian.jira.component.ComponentAccessor; +import com.atlassian.jira.issue.CustomFieldManager; +import com.atlassian.jira.issue.Issue; +import com.atlassian.jira.issue.customfields.impl.GenericTextCFType; +import com.atlassian.jira.issue.customfields.manager.GenericConfigManager; +import com.atlassian.jira.issue.customfields.persistence.CustomFieldValuePersister; +import com.atlassian.jira.issue.fields.CustomField; +import com.atlassian.jira.issue.fields.layout.field.FieldLayoutItem; +import com.atlassian.jira.security.Permissions; +import com.atlassian.jira.user.ApplicationUser; +import com.ericsson.jira.oslc.constants.JiraConstants; +import com.ericsson.jira.oslc.exceptions.PermissionException; +import com.ericsson.jira.oslc.managers.JiraManager; +import com.ericsson.jira.oslc.managers.PermissionManager; +import com.ericsson.jira.oslc.utils.AppLinksRepository; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * It represents the link to remote resource. + * + */ +public class OSLCLink extends GenericTextCFType { + private static final String CURRENT_CLASS = "OSLCLink"; + private static final Logger logger = LoggerFactory.getLogger(OSLCLink.class); + + protected OSLCLink(CustomFieldValuePersister customFieldValuePersister, GenericConfigManager genericConfigManager) { + super(customFieldValuePersister, genericConfigManager); + } + + @Override + public Map getVelocityParameters(final Issue issue, final CustomField field, final FieldLayoutItem fieldLayoutItem) { + final Map map = super.getVelocityParameters(issue, field, fieldLayoutItem); + + CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager(); + + + if (issue == null || issue.getId() == null) { + return map; + } + + CustomField customField = customFieldManager.getCustomFieldObjectByName(JiraConstants.OSLC_CUSTOM_FIELD_NAME); + + if (customField == null) { + return map; + } + + String ApplicationLinks = (String) customField.getValue(issue); + + AppLinksRepository appLinkList = new AppLinksRepository(); + try { + GsonBuilder gsonBuilder = new GsonBuilder(); + Gson gson = gsonBuilder.create(); + appLinkList = gson.fromJson(ApplicationLinks, AppLinksRepository.class); + + } catch (com.google.gson.JsonSyntaxException e) { + logger.error(CURRENT_CLASS, e); + } + + if (appLinkList == null) { + appLinkList = new AppLinksRepository(); + } + + ApplicationUser user = PermissionManager.getLoggedUser(); + boolean editable = false; + if (user != null){ + try { + PermissionManager.checkPermissionWithUser(user, issue, Permissions.EDIT_ISSUE); + editable = true; + } catch (PermissionException e) { + editable = false; + } + } + map.put("editable", editable); + map.put("restURL", JiraManager.getRestUrl()); + map.put("baseURL", JiraManager.getBaseUrl()); + map.put("link_addoslclinkdialog", JiraManager.getBaseUrl() + JiraConstants.ADD_OSLC_LINK_DIALOG + "?issuekey=" + issue.getKey()); + map.put("removeOslcLinkURLFromRemoteApp", JiraConstants.REMOVE_OSLC_LINK_FROM_REMOTE_APP); + map.put("removeOslcLinkURLFromJira", JiraConstants.REMOVE_OSLC_LINK_FROM_JIRA); + map.put("issueID", issue.getId().toString()); + map.put("appLinkList", appLinkList.GetAllAppLinks()); + map.put("oauthcallback", JiraManager.getBaseUrl() + JiraConstants.OAUTH_CALLBACK_SERVICE_URL); + + return map; + } +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/events/IssueEventType.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/events/IssueEventType.java new file mode 100644 index 0000000..736cdd3 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/events/IssueEventType.java @@ -0,0 +1,36 @@ +package com.ericsson.jira.oslc.events; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * Enum for type of events which are not fired automatically by JIRA and has to be fired manually. + * + */ +public enum IssueEventType { + ADD_EXT_LINK, REMOVE_EXT_LINK; +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/events/IssueEventsHandler.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/events/IssueEventsHandler.java new file mode 100644 index 0000000..fe99446 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/events/IssueEventsHandler.java @@ -0,0 +1,302 @@ +package com.ericsson.jira.oslc.events; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.ofbiz.core.entity.GenericEntityException; +import org.ofbiz.core.entity.GenericValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; + +import com.atlassian.event.api.EventListener; +import com.atlassian.event.api.EventPublisher; +import com.atlassian.jira.event.issue.IssueEvent; +import com.atlassian.jira.event.type.EventType; +import com.atlassian.jira.issue.Issue; +import com.atlassian.jira.project.Project; +import com.ericsson.eif.leansync.mapping.data.SyncConfiguration; +import com.ericsson.eif.leansync.mapping.data.SyncField; +import com.ericsson.eif.leansync.mapping.data.SyncMapping; +import com.ericsson.eif.leansync.mapping.data.SyncTemplate; +import com.ericsson.eif.leansync.mapping.data.SyncXmlFieldConfig; +import com.ericsson.jira.oslc.sync.JiraObjectMapping; +import com.ericsson.jira.oslc.sync.OutboundSyncUtils; +import com.ericsson.jira.oslc.sync.SyncConfig; +import com.ericsson.jira.oslc.sync.SyncUtils; +import com.ericsson.jira.oslc.utils.ErrorSyncHandler; +/** + * + * A Listener which is called whenever events occur on JIRA issue + */ +public class IssueEventsHandler implements InitializingBean, DisposableBean { + private static Logger logger = LoggerFactory.getLogger(IssueEventsHandler.class); + private static final String CURRENT_CLASS = "JiraIssueEventsHandler"; + private final EventPublisher eventPublisher; + + /** + * Constructor. + * + * @param eventPublisher injected {@code EventPublisher} implementation. + */ + public IssueEventsHandler(EventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + @Override + public void destroy() throws Exception { + this.eventPublisher.unregister(this); + } + + @Override + public void afterPropertiesSet() throws Exception { + this.eventPublisher.register(this); + } + + /** + * Receives any {@code IssueEvent}s sent by JIRA. + * + * @param issueEvent the IssueEvent passed to us + */ + @EventListener + public void onIssueEvent(IssueEvent issueEvent) { + Issue issue = issueEvent.getIssue(); + + if (issue == null) { + logger.debug(CURRENT_CLASS + ".onIssueEvent for issue null"); + return; + } + + logger.debug(CURRENT_CLASS + ".onIssueEvent: IssueEvent for issue " + + issue.getKey()); + + + try { + Project project = issueEvent.getProject(); + + if(EventType.ISSUE_CREATED_ID.equals(issueEvent.getEventTypeId())){ + issueEvent = null; + } + updateSnapshot(issue, project, issueEvent); + } catch (Exception e) { + logger.error("CURRENT_CLASS", e); + } + } + + /** + * Receives any {@code IssueEvent}s sent by JIRA. + * + * @param issueEvent the RestIssueEvent passed to us. It's custom event which is fired from our code manually + */ + @EventListener + public void onIssueEvent(RestIssueEvent event) { + Issue issue = event.getIssue(); + if(issue == null){ + logger.error(CURRENT_CLASS + ".onIssueEvent: RestIssueEvent for issue null"); + return; + }else{ + logger.debug(CURRENT_CLASS + ".onIssueEvent: RestIssueEvent for issue " + + issue.getKey()); + } + + + Project project = issue.getProjectObject(); + try { + updateSnapshot(issue, project, null); + } catch (Exception e) { + logger.error("CURRENT_CLASS", e); + } + } + + /** + * It update the JIRA snapshot in a remote resource + * @param issue JIRA issue which fires a event + * @param project the project of JIRA issue + * @param issueEvent the event which was fired + * @throws Exception + */ + private void updateSnapshot(Issue issue, Project project, IssueEvent issueEvent) throws Exception { + + SyncConfiguration leanSyncConfig = getLeanSyncConfiguration(issue, project); + if (leanSyncConfig == null) { + return; + } + + ErrorSyncHandler errorHandler = new ErrorSyncHandler(); + + try { + SyncMapping firstInMapping = leanSyncConfig.getFirstInMapping(); + if (firstInMapping == null) { + logger.debug(CURRENT_CLASS + ".updateSnapshot: " + " In mapping is null"); + return; + } + + List templates = firstInMapping.getTemplates(); + List fields = firstInMapping.getFields(); + List xmlFieldConfigs = firstInMapping.getXmlFieldConfigs(); + if (allowUpdate(issueEvent, templates, fields, xmlFieldConfigs, leanSyncConfig.getErrorLog())) { + OutboundSyncUtils.updateRemoteResource(issue, leanSyncConfig, errorHandler); + } else { + logger.debug(CURRENT_CLASS + ".updateSnapshot: " + " Update remote resource " + issue.getKey() + " was not allowed."); + } + } catch (Exception e) { + errorHandler.addMessage(e.getMessage()); + logger.error("CURRENT_CLASS", e); + } finally { + //Save error log to error custom field if it's configured + String errorMessage = (errorHandler != null && !errorHandler.isLogEmpty())?errorHandler.getMessagesAsString():null; + SyncUtils.updateErrorLog(leanSyncConfig, issue, errorMessage, false); + } + + } + + /** + * It checks if the update of the remote resource is allowed. It verifies if the fields + * which have been changed are in the notification list. + * @param issueEvent the event which was fired + * @param templates the list of templates + * @param fields the list of configurations of fields + * @param xmlFieldConfigs the list of configurations of xml fields + * @param errorLogName the name of Error custom field + * @return true - the remote resource can be updated, otherwise false + * @throws GenericEntityException + */ + private boolean allowUpdate(IssueEvent issueEvent, List templates, List fields, List xmlFieldConfigs, String errorLogName) + throws GenericEntityException { + if(issueEvent == null || issueEvent.getWorklog() != null){ + return true; + } + + Set notNotifiedFields = new HashSet(); + + if (errorLogName != null && !"".equals(errorLogName)) { + notNotifiedFields.add(errorLogName); + } + + if (templates != null) { + for (SyncTemplate template : templates) { + String mapTo = template.getMapTo(); + if (mapTo != null && !"".equals(mapTo) && !template.isNotifyChange()) { + notNotifiedFields.add(mapTo); + } + } + } + + if (fields != null) { + for (SyncField field : fields) { + String mapTo = field.getMapTo(); + if (mapTo != null && !"".equals(mapTo) && !field.isNotifyChange()) { + notNotifiedFields.add(mapTo); + } + } + } + + if (xmlFieldConfigs != null) { + for (SyncXmlFieldConfig xmlFieldConfig : xmlFieldConfigs) { + for (SyncField field : xmlFieldConfig.getFields()) { + String mapTo = field.getMapTo(); + if (mapTo != null && !"".equals(mapTo) && !field.isNotifyChange()) { + notNotifiedFields.add(mapTo); + } + } + } + } + + if(issueEvent.getComment() != null && !notNotifiedFields.contains(JiraObjectMapping.COMMENTS.getName())){ + return true; + } + + GenericValue changeLog = issueEvent.getChangeLog(); + if (changeLog != null) { + List changeItemList = changeLog.getRelated("ChildChangeItem"); + if (changeItemList != null) { + if (notNotifiedFields.size() < 1 && changeItemList.size() > 0) { + return true; + } + for (GenericValue changeItem : changeItemList) { + String name = changeItem.get("field").toString(); + String fieldType = changeItem.get("fieldtype").toString(); + if (fieldType != null && "jira".equals(fieldType)) { + name = JiraObjectMapping.getFieldIdsToLabels().get(name); + } + if (!notNotifiedFields.contains(name)) { + return true; + } + } + } + } + return false; + } + + /** + * Fetch LeanSync Configuration based on project and issue type + * @param issue JIRA issue which has been changed + * @param project the project of JIRA issue + * @return LeanSync configuration fro the issue + * @throws Exception + */ + public SyncConfiguration getLeanSyncConfiguration(Issue issue, Project project) throws Exception { + Long projectId = null; + if (project != null && project.getId() != null ) { + projectId = project.getId(); + }else{ + logger.debug(CURRENT_CLASS + ".onIssueEvent: event " + " for issue " + issue.getKey() + " - project is null"); + return null; + } + + String issueTypeId = null; + if (issue.getIssueTypeObject() != null && issue.getIssueTypeObject().getId() != null) { + issueTypeId = issue.getIssueTypeObject().getId(); + } else { + logger.debug(CURRENT_CLASS + ".updateSnapshot: " + " for issue " + issue.getKey() + " - issue type is null"); + return null; + } + + SyncConfig config = SyncConfig.getInstance(); + Map fieldConfMap = config.getConfigurationMap(); + + logger.debug(CURRENT_CLASS + ".onIssueEvent: event " + " for issue " + issue.getKey() + " - project is " + projectId + " - issueType is " + issueTypeId); + if (projectId != null && fieldConfMap != null) { + SyncConfiguration leanSyncConfig = fieldConfMap.get(projectId.toString()); + + if (leanSyncConfig != null && issueTypeId != null && leanSyncConfig.containsIssueType(issueTypeId)) { + logger.debug(CURRENT_CLASS + ".getLeanSyncConfiguration: " + " Configuration found"); + return leanSyncConfig; + } + }else{ + logger.debug(CURRENT_CLASS + ".getLeanSyncConfiguration: " + " Configuration not found for project " + projectId + " and issueType " + issueTypeId); + } + + return null; + } +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/events/RestIssueEvent.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/events/RestIssueEvent.java new file mode 100644 index 0000000..f4678e4 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/events/RestIssueEvent.java @@ -0,0 +1,76 @@ +package com.ericsson.jira.oslc.events; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.util.Map; + +import com.atlassian.crowd.embedded.api.User; +import com.atlassian.jira.event.AbstractEvent; +import com.atlassian.jira.issue.Issue; + +/** + * It represents custom event which are fired manually from the code e.g. when + * external link is added. + * + */ +public class RestIssueEvent extends AbstractEvent { + private Issue issue; + private User user; + private IssueEventType type; + + public RestIssueEvent(final Issue issue, final User user, IssueEventType type) { + super(); + this.issue = issue; + this.user = user; + this.type = type; + } + + public RestIssueEvent(final Issue issue, final User user, Map parameters) { + super(parameters); + this.issue = issue; + this.user = user; + } + + public Issue getIssue() { + return issue; + } + + public User getUser() { + return user; + } + + public IssueEventType getType() { + return type; + } + + public void setType(IssueEventType type) { + this.type = type; + } + + +} \ No newline at end of file diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/GetIssueException.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/GetIssueException.java new file mode 100644 index 0000000..a0d8fc5 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/GetIssueException.java @@ -0,0 +1,53 @@ +package com.ericsson.jira.oslc.exceptions; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * The exception is thrown when a issue is not available + * + */ +public class GetIssueException extends Exception { + + private static final long serialVersionUID = -1945771994194731300L; + + public GetIssueException() { + } + + public GetIssueException(String message) { + super(message); + } + + public GetIssueException(Throwable cause) { + super(cause); + } + + public GetIssueException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/IssueTransitionException.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/IssueTransitionException.java new file mode 100644 index 0000000..cc39679 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/IssueTransitionException.java @@ -0,0 +1,51 @@ +package com.ericsson.jira.oslc.exceptions; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * The exception is thrown when transition of issue failed + * + */ +public class IssueTransitionException extends Exception{ + private static final long serialVersionUID = 8985395102999065444L; + + public IssueTransitionException(){ + } + + public IssueTransitionException(String message){ + super(message); + } + + public IssueTransitionException(Throwable cause){ + super(cause); + } + + public IssueTransitionException(String message, Throwable cause){ + super(message, cause); + } +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/IssueValidationException.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/IssueValidationException.java new file mode 100644 index 0000000..e9973ac --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/IssueValidationException.java @@ -0,0 +1,52 @@ +package com.ericsson.jira.oslc.exceptions; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * The exception is thrown when error occurs during a validation + * + */ +public class IssueValidationException extends Exception{ + private static final long serialVersionUID = -8581963580698927791L; + + public IssueValidationException(){ + } + + public IssueValidationException(String message){ + super(message); + } + + public IssueValidationException(Throwable cause){ + super(cause); + } + + public IssueValidationException(String message, Throwable cause){ + super(message, cause); + } + +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/NoResourceException.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/NoResourceException.java new file mode 100644 index 0000000..063359d --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/NoResourceException.java @@ -0,0 +1,52 @@ +package com.ericsson.jira.oslc.exceptions; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * The exception is thrown when a resource is not available + * + */ +public class NoResourceException extends Exception { + + private static final long serialVersionUID = 1583709001536069481L; + + public NoResourceException(){ + } + + public NoResourceException(String message){ + super(message); + } + + public NoResourceException(Throwable cause){ + super(cause); + } + + public NoResourceException(String message, Throwable cause){ + super(message, cause); + } +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/PermissionException.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/PermissionException.java new file mode 100644 index 0000000..314cd66 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/PermissionException.java @@ -0,0 +1,51 @@ +package com.ericsson.jira.oslc.exceptions; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * The exception is thrown when permission problem occurs + * + */ +public class PermissionException extends Exception { + private static final long serialVersionUID = 2207461020664304332L; + + public PermissionException(){ + } + + public PermissionException(String message){ + super(message); + } + + public PermissionException(Throwable cause){ + super(cause); + } + + public PermissionException(String message, Throwable cause){ + super(message, cause); + } +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/PreconditionException.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/PreconditionException.java new file mode 100644 index 0000000..7a85441 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/PreconditionException.java @@ -0,0 +1,52 @@ +package com.ericsson.jira.oslc.exceptions; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * The exception is thrown when a precondition failed + * + */ +public class PreconditionException extends Exception { + + private static final long serialVersionUID = 4468614532312055881L; + + public PreconditionException(){ + } + + public PreconditionException(String message){ + super(message); + } + + public PreconditionException(Throwable cause){ + super(cause); + } + + public PreconditionException(String message, Throwable cause){ + super(message, cause); + } +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/StatusException.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/StatusException.java new file mode 100644 index 0000000..b142282 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/exceptions/StatusException.java @@ -0,0 +1,53 @@ +package com.ericsson.jira.oslc.exceptions; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * The exception is thrown when the validations or the operations with the issue failed in LeanSync. + * + */ +public class StatusException extends Exception { + private static final long serialVersionUID = 9139099220017790017L; + + + public StatusException(){ + } + + public StatusException(String message){ + super(message); + } + + public StatusException(Throwable cause){ + super(cause); + } + + public StatusException(String message, Throwable cause){ + super(message, cause); + } + +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/handlers/OAuthHandler.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/handlers/OAuthHandler.java new file mode 100644 index 0000000..929bc4f --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/handlers/OAuthHandler.java @@ -0,0 +1,297 @@ +package com.ericsson.jira.oslc.handlers; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpSession; + +import net.oauth.OAuthAccessor; +import net.oauth.OAuthConsumer; +import net.oauth.OAuthServiceProvider; + +import org.eclipse.lyo.server.oauth.core.OAuthConfiguration; +import org.eclipse.lyo.server.oauth.core.consumer.ConsumerStore; +import org.eclipse.lyo.server.oauth.core.consumer.ConsumerStoreException; +import org.eclipse.lyo.server.oauth.core.consumer.LyoOAuthConsumer; +import org.eclipse.lyo.server.oauth.core.token.LRUCache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ericsson.jira.oslc.constants.JiraConstants; +import com.ericsson.jira.oslc.oslcclient.Client; +import com.ericsson.jira.oslc.resources.RootServices; +import com.ericsson.jira.oslc.resources.ao.AOConsumerStore; +import com.ericsson.jira.oslc.resources.ao.AOManager; +import com.ericsson.jira.oslc.resources.ao.RootServicesEntity; +import com.ericsson.jira.oslc.utils.OSLCUtils; + +/** + * A class contains the methods for work with OAuth consumer store, Consumer, Accessor and other OAuth stuff. + * + */ +public class OAuthHandler { + private static Logger logger = LoggerFactory.getLogger(OAuthHandler.class); + private static final String CURRENT_CLASS = "OAuthHandler"; + private static LRUCache rootServicesCache = new LRUCache (10); + + /** + * It loads the consumer store and saves it to OAuth configuration + * @param config OAuth configuration where the cosnumer store will be put to + * @throws ConsumerStoreException + * @throws ClassNotFoundException + * @throws SQLException + */ + public static void loadOAuthConsumers(OAuthConfiguration config) throws ConsumerStoreException, ClassNotFoundException, SQLException { + config.setConsumerStore(loadConsumerStore()); + logger.info("OAuth consumers added"); + } + + /** + * Loads the consumer store from DB + * @return the consumer store + * @throws ConsumerStoreException + * @throws ClassNotFoundException + * @throws SQLException + */ + private static ConsumerStore loadConsumerStore() throws ConsumerStoreException, ClassNotFoundException, SQLException{ + AOConsumerStore aoConsumerStore = new AOConsumerStore(); + return aoConsumerStore; + } + + /** + * Adds a consumer to the consumer store + * @param consumer the consumer containing its name, if it's provisional + * @return consumer store containing all the consumers including added consumer + * @throws ConsumerStoreException + * @throws ClassNotFoundException + * @throws SQLException + */ + public static ConsumerStore addConsumer(LyoOAuthConsumer consumer) throws ConsumerStoreException, ClassNotFoundException, SQLException{ + String currentMethod = "addConsumer"; + if(consumer == null){ + logger.debug(CURRENT_CLASS + "." + currentMethod + " Consumer is null"); + return loadConsumerStore(); + } + + ConsumerStore consumerStore = loadConsumerStore(); + consumerStore.addConsumer(consumer); + + logger.debug(CURRENT_CLASS + "." + currentMethod + " consumer added"); + return consumerStore; + } + + /** + * Removes a consumer defined by consumerKey. + * @param consumerKey the key of consumer + * @return consumer store containing all the consumers without removed consumer + * @throws ClassNotFoundException + * @throws SQLException + * @throws ConsumerStoreException + */ + public static ConsumerStore removeConsumer(String consumerKey) throws ClassNotFoundException, SQLException, ConsumerStoreException { + String currentMethod = "removeConsumer"; + logger.debug(CURRENT_CLASS + "." + currentMethod + " Consumer key: " + consumerKey); + + ConsumerStore consumerStore = loadConsumerStore(); + if (consumerKey != null) { + consumerStore.removeConsumer(consumerKey); + } + + logger.debug(CURRENT_CLASS + "." + currentMethod + " consumer removed"); + return consumerStore; + + } + + /** + * Updates a consumer defined by consumerKey. It's needed to specify if the consumer is provisional or not + * @param consumerKey the key of consumer + * @param provisional specifies if the consumer is provisional or + * @return consumer store containing all the consumers including updated the consumer + * @throws ClassNotFoundException + * @throws SQLException + * @throws ConsumerStoreException + */ + public static ConsumerStore updateConsumer(String consumerKey, boolean provisional) throws ClassNotFoundException, SQLException, ConsumerStoreException { + String currentMethod = "updateConsumer"; + logger.debug(CURRENT_CLASS + "." + currentMethod + " Consumer key: " + consumerKey + ", provisional = " + provisional); + + ConsumerStore consumerStore = loadConsumerStore(); + if (consumerKey != null) { + LyoOAuthConsumer consumer = consumerStore.getConsumer(consumerKey); + consumer.setProvisional(provisional); + consumerStore.updateConsumer(consumer); + } + + logger.debug(CURRENT_CLASS + "." + currentMethod + " consumer updated"); + return consumerStore; + } + + /** + * Returns the consumer for entered resource. It goes through all registered + * rootservices and tries to match oAuthDomain of rootservices with URI of + * entered resource + * + * @param resourceURI uri of Resource + * @return consumer for entered resource + */ + public static OAuthConsumer getConsumer(String resourceURI) { + AOManager mngr = AOManager.getInstance(); + List rootServicesList = mngr.all(RootServicesEntity.class); + List addToCache = new ArrayList(); + + for (RootServicesEntity rootServicesEntity : rootServicesList) { + RootServices rootServices = rootServicesCache.get(rootServicesEntity.getRootServicesURI()); + if (rootServices != null) { + if (matchAuthDomain(resourceURI, rootServices.getOAuthDomain())) { + return getConsumer(rootServices, rootServicesEntity); + } + } else { + addToCache.add(rootServicesEntity); + } + } + + for (RootServicesEntity rootServicesEntity : addToCache) { + + Client client = new Client(); + + //If entity is root services, then call to Client.getRootServicesDetails. + //If entity is catalog, 1st try get values from entity. If values are not set in catalog, + //then new Client function will be called, which fetch OAuth values from catalog. + RootServices rootServices = null; + if (rootServicesEntity.isRootServices() == true) { + rootServices = client.getRootServicesDetails(rootServicesEntity.getRootServicesURI()); + } + else { + if (OSLCUtils.isNullOrEmpty(rootServicesEntity.getConsumerKey()) == false && + OSLCUtils.isNullOrEmpty(rootServicesEntity.getConsumerSecret()) == false && + OSLCUtils.isNullOrEmpty(rootServicesEntity.getOAuthDomain()) == false && + OSLCUtils.isNullOrEmpty(rootServicesEntity.getRequestTokenURI()) == false && + OSLCUtils.isNullOrEmpty(rootServicesEntity.getAccessTokenURI()) == false && + OSLCUtils.isNullOrEmpty(rootServicesEntity.getUserAuthURI()) == false) { + + rootServices = new RootServices(); + rootServices.setOAuthAccessTokenURL(rootServicesEntity.getAccessTokenURI()); + rootServices.setOAuthDomain(rootServicesEntity.getOAuthDomain()); + rootServices.setOAuthRequestTokenURL(rootServicesEntity.getRequestTokenURI()); + rootServices.setOAuthUserAuthorizationURL(rootServicesEntity.getUserAuthURI()); + } + else { + rootServices = client.getRootServicesDetailsFromCatalog(rootServicesEntity.getRootServicesURI()); + } + } + + if (rootServices != null) { + rootServicesCache.put(rootServicesEntity.getRootServicesURI(), rootServices); + if (matchAuthDomain(resourceURI, rootServices.getOAuthDomain())) { + return getConsumer(rootServices, rootServicesEntity); + } + } + } + + return null; + + } + + /** + * Matches entered URI with oAuth domain. If OAuth domain is a part of URI then return true, else false + * @param uri uri of resource + * @param oAuthDomain OAthDomain of rootservices + * @return true if OAuth domain is part of URI, else false + */ + public static boolean matchAuthDomain(String uri, String oAuthDomain) { + return (uri != null && !uri.isEmpty() && oAuthDomain != null && !oAuthDomain.isEmpty() && uri.startsWith(oAuthDomain)); + } + + /** + * Creates and returns the consumer based on input parameters + * @param rootServices - rootservices which has been fetched from friend's side + * @param rootServicesEntity - rootservices of registered friend + * @return consumer based on input parameters + */ + public static OAuthConsumer getConsumer(RootServices rootServices, RootServicesEntity rootServicesEntity){ + OAuthServiceProvider provider = new OAuthServiceProvider(rootServices.getOAuthRequestTokenURL(), rootServices.getOAuthUserAuthorizationURL(), rootServices.getOAuthAccessTokenURL()); + OAuthConsumer oauthConsumer = new OAuthConsumer("", rootServicesEntity.getConsumerKey(), rootServicesEntity.getConsumerSecret(), provider); + return oauthConsumer; + } + + /** + * Returns a consumer according to defined a consumer key + * @param consumerKey a key of consumer which is returned + * @return a consumer according to defined a consumer key + * @throws ClassNotFoundException + * @throws SQLException + * @throws ConsumerStoreException + */ + public static LyoOAuthConsumer getConsumerByKey(String consumerKey) throws ClassNotFoundException, SQLException, ConsumerStoreException { + String currentMethod = "getConsumerByKey"; + logger.debug(CURRENT_CLASS + "." + currentMethod + " Consumer key: " + consumerKey); + + ConsumerStore consumerStore = loadConsumerStore(); + if (consumerKey != null) { + return consumerStore.getConsumer(consumerKey); + } + return null; + } + + public static OAuthAccessor getAccessorFromSession(HttpSession session, String consumerKey, String url) { + OAuthAccessor ac = null; + try { + String oAuthAccessorSession = JiraConstants.SESSION_OAUTHACCESSOR + consumerKey; + ac = (OAuthAccessor) session.getAttribute(oAuthAccessorSession); + } + catch (ClassCastException e) { + logger.debug(CURRENT_CLASS + ".getAccessorFormSession Exception: " + e.getMessage()); + e.printStackTrace(); + } + return ac; + } + + /** + * It saves an accessor to the session. The accessor are saved to the session according to the consumer key + * @param session the session which contains the accessors + * @param ac accessor to save + * @param consumerKey a key consumers + */ + public static void saveAccessorToSession(HttpSession session, OAuthAccessor ac, String consumerKey) { + String oAuthAccessorSession = JiraConstants.SESSION_OAUTHACCESSOR + consumerKey; + session.setAttribute(oAuthAccessorSession, ac); + } + + /** + * Clear the cache of rootservices. + */ + public static void clearRootServicesCache(){ + if(rootServicesCache != null){ + rootServicesCache.clear(); + } + + } +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/managers/FieldManager.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/managers/FieldManager.java new file mode 100644 index 0000000..7523b97 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/managers/FieldManager.java @@ -0,0 +1,428 @@ +package com.ericsson.jira.oslc.managers; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +import com.atlassian.crowd.embedded.api.User; +import com.atlassian.jira.bc.project.component.ProjectComponent; +import com.atlassian.jira.component.ComponentAccessor; +import com.atlassian.jira.config.PriorityManager; +import com.atlassian.jira.config.ResolutionManager; +import com.atlassian.jira.issue.Issue; +import com.atlassian.jira.issue.IssueManager; +import com.atlassian.jira.issue.MutableIssue; +import com.atlassian.jira.issue.changehistory.ChangeHistoryManager; +import com.atlassian.jira.issue.issuetype.IssueType; +import com.atlassian.jira.issue.label.LabelManager; +import com.atlassian.jira.issue.priority.Priority; +import com.atlassian.jira.issue.resolution.Resolution; +import com.atlassian.jira.issue.status.Status; +import com.atlassian.jira.issue.vote.VoteManager; +import com.atlassian.jira.issue.watchers.WatcherManager; +import com.atlassian.jira.project.Project; +import com.atlassian.jira.project.version.Version; +import com.atlassian.jira.security.Permissions; +import com.atlassian.jira.user.ApplicationUser; +import com.atlassian.jira.user.util.UserManager; +import com.atlassian.jira.workflow.JiraWorkflow; +import com.atlassian.jira.workflow.WorkflowManager; +import com.ericsson.jira.oslc.PluginConfig; +import com.ericsson.jira.oslc.exceptions.NoResourceException; +import com.ericsson.jira.oslc.exceptions.PermissionException; +import com.ericsson.jira.oslc.resources.JiraChangeRequest; +import com.ericsson.jira.oslc.resources.JiraHistoryRequest; + +/** + * A class offering the methods for operations with JIRA field + * + */ +public class FieldManager { + + /** + * It returns the name of priority according to ID + * @param id the ID od priority + * @return the name of priority according to ID + */ + public static String getPriorityName(String id) { + PriorityManager prMngr = ComponentAccessor.getComponent(PriorityManager.class); + List priorities = prMngr.getPriorities(); + Iterator pit = priorities.iterator(); + while (pit.hasNext()) { + Priority pr = pit.next(); + if (pr != null) { + if (pr.getId().compareToIgnoreCase(id) == 0) { + return pr.getName(); + } + } + } + return null; + } + + /** + * It returns the status of the issue according to defined ID of status + * @param issueId the ID of issue + * @param issueStatusId the ID of status + * @return the status of the issue according to defined ID of status + */ + public static String getStatusNameForIssue(String issueId, String issueStatusId) { + final IssueManager issueManager = ComponentAccessor.getIssueManager(); + final MutableIssue issue = issueManager.getIssueObject(issueId); + + if (issue != null) { + WorkflowManager wMngr = ComponentAccessor.getWorkflowManager(); + JiraWorkflow workflow = wMngr.getWorkflow(issue); + List statuses = workflow.getLinkedStatusObjects(); + for (Status st : statuses) { + if (st.getId().compareToIgnoreCase(issueStatusId) == 0) { + return st.getName(); + } + } + } + + return null; + } + + /** + * It returns the resolution name issue according to ID of resolution + * @param resolutionId ID of resolution + * @return resolution name + */ + public static String getResolutionName(String resolutionId) { + ResolutionManager resMngr = ComponentAccessor.getComponent(ResolutionManager.class); + List resolutions = resMngr.getResolutions(); + Iterator rit = resolutions.iterator(); + while (rit.hasNext()) { + Resolution res = rit.next(); + if (res != null) { + if (res.getId().compareToIgnoreCase(resolutionId) == 0) { + return res.getName(); + } + } + } + return null; + } + + /** + * Get all issue types + * @param request + * @return all issue types + * @throws IOException + */ + public static String[] getIssueTypes() throws IOException { + List types = ComponentAccessor.getConstantsManager().getAllIssueTypeIds(); + return types.toArray(new String[] {}); + } + + /** + * Get a list of issue types which are filtered according to Plugin configuration + * @param request + * @return list of filtered issue types + * @throws Exception + */ + public static List getFilteredIssueTypes() throws Exception { + List issueTypes = null; + + PluginConfig config = PluginConfig.getInstance(); + Set filteredTypes = config.getFilteredTypes(); + + if(filteredTypes == null || filteredTypes.isEmpty()){ + issueTypes = ComponentAccessor.getConstantsManager().getAllIssueTypeIds(); + }else{ + issueTypes = new ArrayList(); + for (Long id : filteredTypes) { + IssueType issueType = ComponentAccessor.getConstantsManager().getIssueTypeObject(id.toString()); + if(issueType != null){ + issueTypes.add(issueType.getId()); + } + } + } + + return issueTypes; + } + + + + + + /** + * Get the name of issue type according to ID + * @param id ID of issue type + * @return the name of issue type + * @throws IOException + */ + public static String getIssueType(String id) throws IOException { + Collection types = ComponentAccessor.getConstantsManager().getAllIssueTypeObjects(); + for (IssueType t : types) + if (t.getId().compareTo(id) == 0) return t.getName(); + + return null; + } + + /** + * Get History of Issue as Resource which will be sent to the client + * @param request HttpServletRequest + * @param issueKey the key of Issue + * @return History of Issue as Resource which will be sent to the client + * @throws PermissionException + * @throws NoResourceException + */ + public static JiraHistoryRequest getHistoryOfIssue(final HttpServletRequest request, String issueKey) throws PermissionException, NoResourceException { + final IssueManager issueManager = ComponentAccessor.getIssueManager(); + final MutableIssue issue = issueManager.getIssueObject(issueKey); + + if (issue == null) { + throw new NoResourceException("The issue " + issueKey + " doesn't exist."); + } + + PermissionManager.checkPermission(request, issue, Permissions.BROWSE); + + ChangeHistoryManager hMngr = ComponentAccessor.getChangeHistoryManager(); + JiraHistoryRequest history = new JiraHistoryRequest(hMngr.getChangeHistories(issue)); + + return history; + } + + /** + * Function prepares list of components to set to issue. Requested list is compared against + * allowed project components. Components which are not allowed are ignored. + * @param issue JIRA issue + * @param cList the list of project component names + * @return the list of project components + */ + public static Collection componentsToSet(final Issue issue, List cList) { + Project prj = issue.getProjectObject(); + Collection pcc = prj.getProjectComponents(); + Collection toSet = new ArrayList(); + for (String c : cList) { + for (ProjectComponent pc : pcc) { + if (pc.getName().compareTo(c) == 0) { + toSet.add(pc); + break; + } + } + } + return toSet; + } + + /** + * Function prepares list of component ids to set to issue. Requested list is compared against + * allowed component. The component which is not allowed is ignored. + * @param issue JIRA issue + * @param name the version names + * @return the list of versions + */ + public static Long[] componentNamesToIds(final Project prj, String name) { + if(name == null){ + return null; + } + + ArrayList list = new ArrayList(); + list.add(name); + return componentNamesToIds(prj, list); + } + + /** + * Function prepares list of component ids to set to issue. Requested list is compared against + * allowed components. The components which are not allowed are ignored. + * @param issue JIRA issue + * @param list the list of component names + * @return the list of components + */ + public static Long[] componentNamesToIds(final Project prj, List list) { + if(prj == null || list == null){ + return null; + } + + Collection col = prj.getProjectComponents(); + Collection ids = new ArrayList(); + for (String s : list) { + for (ProjectComponent elem : col) { + if (elem.getName().compareTo(s) == 0) { + ids.add(elem.getId()); + break; + } + } + } + return ids.toArray(new Long[ids.size()]); + } + + /** + * Function prepares list of versions to set to issue. Requested list is compared against + * allowed versions. Versions which are not allowed are ignored. + * @param issue JIRA issue + * @param list the list of version names + * @return the list of versions + */ + public static Collection versionsToSet(final Issue issue, List list) { + Project prj = issue.getProjectObject(); + Collection col = prj.getVersions(); + Collection toSet = new ArrayList(); + for (String s : list) { + for (Version elem : col) { + if (elem.getName().compareTo(s) == 0) { + toSet.add(elem); + break; + } + } + } + return toSet; + } + + /** + * Function prepares list of version ids to set to issue. Requested list is compared against + * allowed version. The version which is not allowed are ignored. + * @param issue JIRA issue + * @param name version name + * @return the list of version ids + */ + public static Long[] versionNamesToIds(final Project prj, String name) { + if(name == null){ + return null; + } + + ArrayList list = new ArrayList(); + list.add(name); + return versionNamesToIds(prj, list); + } + + + + /** + * Function prepares list of version ids to set to issue. Requested list is compared against + * allowed version ids. The versions which are not allowed are ignored. + * @param issue JIRA issue + * @param list the list of version names + * @return the list of versions + */ + public static Long[] versionNamesToIds(final Project prj, List list) { + if(prj == null || list == null){ + return null; + } + + Collection col = prj.getVersions(); + Collection ids = new ArrayList(); + for (String s : list) { + for (Version elem : col) { + if (elem.getName().compareTo(s) == 0) { + ids.add(elem.getId()); + break; + } + } + } + + return ids.toArray(new Long[ids.size()]); + } + + /** + * It updates labels from JiraChangeRequest + * @param issue the issue which will be updated + * @param jcr JIRA Change request + * @param user the updated will be done by the user + */ + public static void updateLabels(MutableIssue issue, JiraChangeRequest jcr, User user) { + Set names = jcr.getLabels(); + LabelManager lMngr = ComponentAccessor.getComponent(LabelManager.class); + lMngr.setLabels(user, issue.getId(), names, false, false); + } + + /** + * It updates voters from JiraChangeRequest + * @param issue the issue which will be updated + * @param jcr JIRA Change request + * @param user the updated will be done by the user + */ + public static void updateVoters(MutableIssue issue, JiraChangeRequest jcr, User user) { + List names = jcr.getVoters(); + + List newVoters = new ArrayList(); + UserManager um = ComponentAccessor.getUserManager(); + for (String name : names) { + ApplicationUser u = um.getUserByName(name); + if (u != null) { + newVoters.add(u); + } + } + + VoteManager vMngr = ComponentAccessor.getVoteManager(); + List voters = vMngr.getVotersFor(issue, ComponentAccessor.getJiraAuthenticationContext().getLocale()); + + //remove existing voters, who aren't in incoming list + for (ApplicationUser voter : voters) { + if (newVoters.contains(voter) == false) { + vMngr.removeVote(voter, issue); + } + } + + //add voters from incoming list + for (ApplicationUser voter : newVoters) { + vMngr.addVote(voter, issue); + } + } + + /** + * It updates watchers from JiraChangeRequest + * @param issue the issue which will be updated + * @param jcr JIRA Change request + * @param user the updated will be done by the user + */ + public static void updateWatchers(MutableIssue issue, JiraChangeRequest jcr, User user) { + List names = jcr.getWatchers(); + + List newWatchers = new ArrayList(); + UserManager um = ComponentAccessor.getUserManager(); + for (String name : names) { + ApplicationUser u = um.getUserByName(name); + if (u != null) { + newWatchers.add(u); + } + } + + WatcherManager watcherMngr = ComponentAccessor.getWatcherManager(); + List wList = watcherMngr.getWatchers(issue, ComponentAccessor.getJiraAuthenticationContext().getLocale()); + + //remove existing watchers, who aren't in incoming list + for (ApplicationUser u : wList) { + if (newWatchers.contains(u) == false) { + watcherMngr.stopWatching(u, issue); + } + } + + //add watchers from incoming list + for (ApplicationUser u : newWatchers) { + watcherMngr.startWatching(u, issue); + } + } +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/managers/JiraManager.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/managers/JiraManager.java new file mode 100644 index 0000000..c08ec09 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/managers/JiraManager.java @@ -0,0 +1,814 @@ +/******************************************************************************* + * Copyright (c) 2011, 2013 IBM Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * + * Sam Padgett - initial API and implementation + * Michael Fiedler - adapted for OSLC4J + *******************************************************************************/ + +package com.ericsson.jira.oslc.managers; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.WebApplicationException; + +import org.ofbiz.core.entity.GenericEntityException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.atlassian.crowd.embedded.api.User; +import com.atlassian.jira.bc.issue.IssueService; +import com.atlassian.jira.bc.issue.IssueService.CreateValidationResult; +import com.atlassian.jira.bc.issue.IssueService.IssueResult; +import com.atlassian.jira.bc.issue.IssueService.IssueValidationResult; +import com.atlassian.jira.bc.issue.IssueService.TransitionValidationResult; +import com.atlassian.jira.bc.issue.IssueService.UpdateValidationResult; +import com.atlassian.jira.bc.project.component.ProjectComponent; +import com.atlassian.jira.component.ComponentAccessor; +import com.atlassian.jira.config.properties.APKeys; +import com.atlassian.jira.issue.Issue; +import com.atlassian.jira.issue.IssueInputParameters; +import com.atlassian.jira.issue.IssueInputParametersImpl; +import com.atlassian.jira.issue.IssueManager; +import com.atlassian.jira.issue.MutableIssue; +import com.atlassian.jira.issue.fields.CustomField; +import com.atlassian.jira.issue.managers.DefaultCustomFieldManager; +import com.atlassian.jira.issue.managers.DefaultIssueManager; +import com.atlassian.jira.issue.status.Status; +import com.atlassian.jira.project.Project; +import com.atlassian.jira.project.ProjectManager; +import com.atlassian.jira.project.version.Version; +import com.atlassian.jira.security.JiraAuthenticationContext; +import com.atlassian.jira.security.Permissions; +import com.atlassian.jira.user.ApplicationUser; +import com.atlassian.jira.user.ApplicationUsers; +import com.atlassian.jira.user.util.UserManager; +import com.atlassian.jira.util.ErrorCollection; +import com.atlassian.jira.workflow.JiraWorkflow; +import com.atlassian.jira.workflow.WorkflowManager; +import com.ericsson.eif.leansync.mapping.data.SyncConfiguration; +import com.ericsson.jira.oslc.Constants; +import com.ericsson.jira.oslc.PluginConfig; +import com.ericsson.jira.oslc.constants.JiraConstants; +import com.ericsson.jira.oslc.events.IssueEventType; +import com.ericsson.jira.oslc.exceptions.GetIssueException; +import com.ericsson.jira.oslc.exceptions.IssueTransitionException; +import com.ericsson.jira.oslc.exceptions.IssueValidationException; +import com.ericsson.jira.oslc.exceptions.NoResourceException; +import com.ericsson.jira.oslc.exceptions.PermissionException; +import com.ericsson.jira.oslc.exceptions.PreconditionException; +import com.ericsson.jira.oslc.exceptions.StatusException; +import com.ericsson.jira.oslc.resources.JiraChangeRequest; +import com.ericsson.jira.oslc.services.ServiceHelper; +import com.ericsson.jira.oslc.servlet.ServiceProviderCatalogSingleton; +import com.ericsson.jira.oslc.sync.SyncUtils; +import com.ericsson.jira.oslc.utils.AppLinksRepository; +import com.ericsson.jira.oslc.utils.JiraIssueInputParameters; +import com.ericsson.jira.oslc.utils.OSLCUtils; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.opensymphony.workflow.loader.ActionDescriptor; +import com.opensymphony.workflow.loader.StepDescriptor; + +/** + * + * It contains the methods for operations with JIRA issue + * + */ +public class JiraManager { + private static final String CURRENT_CLASS = "JiraManager"; + private static Logger logger = LoggerFactory.getLogger(JiraManager.class); + + + /** + * Get a list of Issues for a project ID using paging + * + * @param httpServletRequest HttpServletRequest + * @param projectKeyString the key of project as String + * @return The list of change requests + * @throws IOException + * @throws ServletException + * @throws URISyntaxException + * @throws PermissionException + */ + public static List getIssuesByProject(final HttpServletRequest httpServletRequest, + final String projectKeyString) throws IOException, ServletException, + URISyntaxException, PermissionException { + String currentMethod = "getIssuesByProject"; + String userName = PermissionManager.getUserName(httpServletRequest); + + UserManager um = ComponentAccessor.getComponent(UserManager.class); + User user = ApplicationUsers.toDirectoryUser(um.getUserByName(userName)); + + //get issue for project + ProjectManager projectManager = ComponentAccessor.getProjectManager(); + long prjid = Long.parseLong(projectKeyString); + Project prj = projectManager.getProjectObj(prjid); + + ApplicationUser appUser = PermissionManager.getAppUserFromRequest(httpServletRequest); + PermissionManager.checkPermission(appUser, prj, Permissions.BROWSE); + + IssueManager issueManager = ComponentAccessor.getIssueManager(); + IssueService issueService = ComponentAccessor.getIssueService(); + Collection ids; + try { + ids = issueManager.getIssueIdsForProject(prj.getId()); + } + catch (GenericEntityException e1) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e1.getMessage()); + return null; + } + + List results = new ArrayList(); + Iterator it = ids.iterator(); + while (it.hasNext()) + { + final IssueService.IssueResult issueResult = issueService.getIssue(user, it.next()); + final MutableIssue issue = issueResult.getIssue(); + + if(!PermissionManager.hasPermission(appUser, issue, Permissions.BROWSE)){ + continue; + } + + JiraChangeRequest jcr = JiraChangeRequest.fromJiraIssue(issue); + jcr.setServiceProvider(ServiceProviderCatalogSingleton.getServiceProvider(httpServletRequest, projectKeyString).getAbout()); + + URI about; + try { + about = new URI(ServiceHelper.getOslcBaseUri(httpServletRequest) + "/" + projectKeyString + "/changeRequests/" + jcr.getIdentifier()); + } + catch (URISyntaxException e) { + logger.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + throw new WebApplicationException(e); + } + + jcr.setAbout(about); + results.add(jcr); + } + + return results; + } + + + /** + * Get a Jira Issue by id + * + * @param request + * @param IssueId + * @return Issue + * @throws IOException + * @throws ServletException + */ + public static JiraChangeRequest getIssueById(final HttpServletRequest request, final Long issueId) + throws IOException, ServletException, URISyntaxException { + + final IssueManager issueManager = ComponentAccessor.getIssueManager(); + final MutableIssue issue = issueManager.getIssueObject(issueId); + + JiraChangeRequest jcr = JiraChangeRequest.fromJiraIssue(issue); + return jcr; + } + + /** + * Get a Jira Issue by id + * + * @param request + * @param IssueIdString + * @return Issue + * @throws IOException + * @throws ServletException + * @throws PermissionException + * @throws NoResourceException + */ + public static JiraChangeRequest getIssueById(final HttpServletRequest request, final String issueId) + throws IOException, ServletException, URISyntaxException, PermissionException, NoResourceException { + + final IssueManager issueManager = ComponentAccessor.getIssueManager(); + final MutableIssue issue = issueManager.getIssueObject(issueId); + + if(issue == null){ + throw new NoResourceException("The issue " + issueId + " doesn't exist."); + } + + PermissionManager.checkPermission(request, issue, Permissions.BROWSE); + + JiraChangeRequest jcr = JiraChangeRequest.fromJiraIssue(issue); + return jcr; + } + + /** + * Create a new JIRA issue from a JIRAChangeRequest + * + * @param httpServletRequest + * @param jcr JIRAChangeRequest + * @param productIdString the id of product + * @param syncType the type of synchronization + * @return id of the new JIRA issue + * @throws IOException + * @throws ServletException + */ + public static Long createIssue(HttpServletRequest httpServletRequest, final JiraChangeRequest jcr, final String projectIdString, String syncType) throws Exception { + logger.debug("JiraManager - createIssue"); + + String userName = PermissionManager.getUserName(httpServletRequest); + UserManager um = ComponentAccessor.getComponent(UserManager.class); + ApplicationUser appUser = um.getUserByName(userName); + User user = ApplicationUsers.toDirectoryUser(um.getUserByName(userName)); + JiraAuthenticationContext authenticationContext = ComponentAccessor.getJiraAuthenticationContext(); + authenticationContext.setLoggedInUser(appUser); + + + ProjectManager projectManager = ComponentAccessor.getComponent(ProjectManager.class); + Project p = projectManager.getProjectObj(new Long(projectIdString)) ; + if (p == null) { + logger.warn("Project does not exist!"); + throw new PreconditionException("Project with id " + projectIdString + " does not exist!"); + } + + PermissionManager.checkPermission(appUser, p, Permissions.CREATE_ISSUE); + + jcr.setProjectId(p.getId()); + jcr.setProject(p.getKey()); + + if(jcr.getReporter() == null || jcr.getReporter().isEmpty()){ + jcr.setReporter(user.getName()); + } + + SyncConfiguration leanSyncConfiguration = SyncUtils.getLeanSyncConfiguration(jcr, null); + + JiraIssueInputParameters jiraIssueInputParams = new JiraIssueInputParameters(); + boolean generateStatusLog = false; + try { + jiraIssueInputParams = JiraChangeRequest.toIssueParameters(jcr, null, null, syncType, leanSyncConfiguration, p); + IssueInputParameters issueInputParams = jiraIssueInputParams.getIssueInputParameters(); + + String errorLogName = null; + if (leanSyncConfiguration != null) { + errorLogName = leanSyncConfiguration.getErrorLog(); + } + + // handle errors only for LeanSync + if (syncType != null) { + + if (!jiraIssueInputParams.getErrorSyncHandler().isLogEmpty()) { + generateStatusLog = true; + logger.warn("Error during parsing issue"); + JiraIssueInputParameters newJiraIssueInputParameters = new JiraIssueInputParameters(); + + SyncUtils.putValueToErrorField(null, newJiraIssueInputParameters, errorLogName, jiraIssueInputParams.getErrorSyncHandler()); + SyncUtils.handleErrorState(null, newJiraIssueInputParameters, jiraIssueInputParams, leanSyncConfiguration, false); + + issueInputParams = newJiraIssueInputParameters.getIssueInputParameters(); + }else if (!jiraIssueInputParams.getWarnSyncHandler().isLogEmpty()) { + SyncUtils.putValueToErrorField(null, jiraIssueInputParams, errorLogName, jiraIssueInputParams.getWarnSyncHandler()); + } else { + // only clear Error custom filed + SyncUtils.putValueToErrorField(null, jiraIssueInputParams, errorLogName, null); + + } + } + + IssueService issueService = ComponentAccessor.getComponent(IssueService.class); + IssueValidationResult validationResult = issueService.validateCreate(user, issueInputParams); + + if (!validationResult.isValid()) { + logger.warn("Create issue - parameters are not valid."); + ErrorCollection errorCollection = validationResult.getErrorCollection(); + throwErrorException(errorCollection); + } + + IssueResult issueResult = issueService.create(user, (CreateValidationResult)validationResult); + if (!issueResult.isValid()) { + logger.warn("Error during creating issue"); + ErrorCollection errorCollection = issueResult.getErrorCollection(); + throwErrorException(errorCollection); + } + + MutableIssue createdIssue = issueResult.getIssue(); + if (createdIssue != null && syncType == null) { + List cList = jcr.getComponents(); + Collection toSet1 = FieldManager.componentsToSet(createdIssue, cList); + createdIssue.setComponentObjects(toSet1); + + List avList = jcr.getAffectsVersions(); + Collection toSet2 = FieldManager.versionsToSet(createdIssue, avList); + createdIssue.setAffectedVersions(toSet2); + + List fvList = jcr.getFixVersions(); + Collection toSet3 = FieldManager.versionsToSet(createdIssue, fvList); + createdIssue.setFixVersions(toSet3); + } + + return issueResult.getIssue().getId(); + + } + catch (Exception e) { + logger.error("Error", e); + if (syncType == null) { + throw e; + } + generateStatusLog = true; + String exceptionMessage = "CreateIssue error: Occurs error during parsing and validating input values."; + + if (jiraIssueInputParams != null) { + jiraIssueInputParams.getErrorSyncHandler().addMessage(e.getMessage()); + exceptionMessage += ": Message:" + jiraIssueInputParams.getErrorSyncHandler().getMessagesAsString(); + } + logger.warn(exceptionMessage); + }finally { + if (generateStatusLog && syncType != null) { + String statusMessage = "Error"; + final String errorValues = jiraIssueInputParams.getErrorSyncHandler().getMessagesAsString(); + + if (errorValues != null) { + statusMessage += ": " + errorValues; + } + logger.warn(statusMessage); + throw new StatusException(statusMessage); + } + } + return null; + } + + /** + * Returns the list of projects where the users has Browse permission. The list is filtered by defined filter + * in the plugin configuration + * @param httpServletRequest HttpServletRequest + * @return the list of projects where the users has Browse permission + * @throws Exception + */ + public static Project[] getProjects(HttpServletRequest httpServletRequest) throws Exception { + ProjectManager projectManager = ComponentAccessor.getComponent(ProjectManager.class); + if (projectManager == null){ + logger.debug("Project manager is null!"); + return null; + } + + List filteredProjects = new ArrayList(); + ApplicationUser appUser = PermissionManager.getAppUserFromRequest(httpServletRequest); + + PluginConfig config = PluginConfig.getInstance(); + Set filteredProjectIDs = config.getFilteredProjects(); + + if(filteredProjectIDs == null || filteredProjectIDs.isEmpty()){ + List allProjects = projectManager.getProjectObjects(); + for (Project project : allProjects) { + if(PermissionManager.hasPermission(appUser,project, Permissions.BROWSE)){ + filteredProjects.add(project); + } + } + }else{ + for (Long projId : filteredProjectIDs) { + Project project = projectManager.getProjectObj(projId); + if(project != null && PermissionManager.hasPermission(appUser,project, Permissions.BROWSE)){ + filteredProjects.add(project); + } + } + } + + if (filteredProjects.size() <= 0) { + logger.debug("There are no projects!"); + return null; + } + + return filteredProjects.toArray(new Project[]{}); + } + + /** + * Returns the URL where the REST API is + * @return the URL where the REST API is + */ + public static String getRestUrl() { + return ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL) + JiraConstants.REST_URL; + } + + /** + * Returns the base URL + * @return the base URL + */ + public static String getBaseUrl() { + return ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL); + } + + /** + * Add External link + * @param issueId the ID of issue where the links will be added to + * @param linksList the list of the links which will be added to the issue + * @return true - if the links will be added to the issue successfully, otherwise false + * @throws IOException + * @throws ServletException + * @throws PermissionException + */ + public static Boolean addOSLCLink(final Long issueId, final ArrayList linksList) throws IOException, ServletException, PermissionException { + logger.debug("JiraManager - addOSLCLink"); + DefaultIssueManager issueManager = ComponentAccessor.getComponent(DefaultIssueManager.class); + MutableIssue issue = issueManager.getIssueObject(issueId); + + PermissionManager.checkPermission(null, issue, Permissions.EDIT_ISSUE); + + DefaultCustomFieldManager cfManager = (DefaultCustomFieldManager) ComponentAccessor.getCustomFieldManager(); + CustomField cf = cfManager.getCustomFieldObjectByName(JiraConstants.OSLC_CUSTOM_FIELD_NAME); + + if (cf == null){ + return false; + } + + // prepare GSON + GsonBuilder gsonBuilder = new GsonBuilder(); + Gson gson = gsonBuilder.create(); + + String uri; + String label; + + for (int iLink = 0; iLink < linksList.size(); iLink++) { + ArrayList onelink = (ArrayList)linksList.get(iLink); + if (onelink.size() > 1){ + label = (String) onelink.get(0); + uri = (String) onelink.get(1); + } + else { + continue; + } + // get all application links which are already saved in custom + // field + String links = (String) cf.getValue(issue); + AppLinksRepository appLinkList = new AppLinksRepository(); + + if (links != "") { + try { + appLinkList = gson.fromJson(links, AppLinksRepository.class); + } catch (com.google.gson.JsonSyntaxException e) { + logger.debug("JiraManager - addOSLCLink - " + e.getMessage()); + } + + if (appLinkList == null){ + appLinkList = new AppLinksRepository(); + } + + appLinkList.addAppLink(label, uri, true); + } + + String updatedLinks = ""; + updatedLinks = gson.toJson(appLinkList); + + if (updatedLinks != null && updatedLinks != "") { + cf.createValue(issue, updatedLinks); + } + + } // for + + ApplicationUser user = PermissionManager.getLoggedUser(); + OSLCUtils.fireRestIssueEvent(issue, user, IssueEventType.ADD_EXT_LINK); + + return true; + } + + + /** + * It removes the external link from the issue + * @param issueId the ID of issue + * @param URItoRemove URI of the link which will be removed from the issue + * @return true - if the links will be removed to the issue successfully, otherwise false + * @throws GetIssueException + * @throws PermissionException + */ + public static Boolean removeOSLCLink(final String issueId, final String URItoRemove) throws GetIssueException, PermissionException{ + String currentMethod = "removeOSLCLink"; + logger.debug(CURRENT_CLASS + "." + currentMethod); + + + DefaultIssueManager issueManager = ComponentAccessor.getComponent(DefaultIssueManager.class); + Long issueIdLong = (long) -1; + + try { + issueIdLong = Long.valueOf(issueId).longValue(); + } catch (Exception ex) { + logger.error(CURRENT_CLASS + "." + currentMethod + "Exception: " + ex.getMessage()); + throw new GetIssueException("Issue not available"); + } + + MutableIssue issue = issueManager.getIssueObject(issueIdLong); + ApplicationUser user = PermissionManager.getLoggedUser(); + PermissionManager.checkPermissionWithUser(user, issue, Permissions.EDIT_ISSUE); + + if(issue == null) throw new GetIssueException("Issue not available"); + + DefaultCustomFieldManager cfManager = (DefaultCustomFieldManager) ComponentAccessor.getCustomFieldManager(); + CustomField cf = cfManager.getCustomFieldObjectByName(JiraConstants.OSLC_CUSTOM_FIELD_NAME); + + if (cf == null){ + throw new GetIssueException("Custom field ("+ JiraConstants.OSLC_CUSTOM_FIELD_NAME + ") not available"); + } + + // prepare GSON + GsonBuilder gsonBuilder = new GsonBuilder(); + Gson gson = gsonBuilder.create(); + + + // get all application links which are already saved in custom field + String appLinks = (String) cf.getValue(issue); + AppLinksRepository appLinkList = new AppLinksRepository(); + + if (appLinks == null){ + return false; + }else if (appLinks == ""){ + return true; + } + + try{ + appLinkList = gson.fromJson(appLinks, AppLinksRepository.class); + } catch (com.google.gson.JsonSyntaxException e) { + logger.error(CURRENT_CLASS + "." + currentMethod + "Exception: " + e.getMessage()); + } + + if (appLinkList == null || URItoRemove == null){ + return false; + } + + if (appLinkList.removeAppLink(URItoRemove)){ + String updatedAppLinks = ""; + if(appLinkList.GetAllAppLinks().size() == 0){ + updatedAppLinks = ""; + } + else { + updatedAppLinks = gson.toJson(appLinkList); + } + + if (updatedAppLinks != null) { + cf.createValue(issue, updatedAppLinks); + return true; + } + } + + return false; + + } + + /** + * It changes the status of the issue + * @param issue + * @param targetStatus the target status + * @throws Exception + */ + public static void changeIssueState(Issue issue, String targetStatus) throws Exception { + Status current_status = issue.getStatusObject(); + + if (current_status.getId() == targetStatus) { + //same states - do nothing + return; + } + + WorkflowManager wMngr = ComponentAccessor.getWorkflowManager(); + JiraWorkflow workflow = wMngr.getWorkflow(issue); + + List statuses = workflow.getLinkedStatusObjects(); + Status next_status = null; + for (Status st : statuses) { + if (st.getId().compareToIgnoreCase(targetStatus) == 0) { + next_status = st; + break; + } + } + + if (next_status == null) { + throw new IssueTransitionException("Requested status (" + targetStatus + ") does not exist!"); + } + + if (current_status.getId() == next_status.getId()) { + //same states - do nothing + return; + } + + StepDescriptor step1 = workflow.getLinkedStep(current_status); + StepDescriptor step2 = workflow.getLinkedStep(next_status); + + List actions1 = step1.getActions(); + Collection actions2 = workflow.getActionsWithResult(step2); + + int actionId = -1; + for (ActionDescriptor d1 : actions1) { + for (ActionDescriptor d2 : actions2) { + if (d1.getId() == d2.getId()) { + actionId = d1.getId(); + break; + } + } + } + + if (actionId == -1) { + throw new IssueTransitionException("Transition from " + current_status.getName() + + " to " + next_status.getName() + " is not allowed!"); + } + + IssueService issueService = ComponentAccessor.getComponent(IssueService.class); + IssueService.IssueResult transResult; + + IssueInputParameters issueInputParameters = new IssueInputParametersImpl(); + + ApplicationUser aUser = ComponentAccessor.getJiraAuthenticationContext().getUser(); + User user = ApplicationUsers.toDirectoryUser(aUser); + + TransitionValidationResult validationResult = issueService.validateTransition( + user, issue.getId(), actionId, issueInputParameters); + if (validationResult.isValid() == false) { + logger.warn("Change issue state - parameters are not valid."); + ErrorCollection errorCollection = validationResult.getErrorCollection(); + throwErrorException(errorCollection); + } + + transResult = issueService.transition(user, validationResult); + if (transResult.isValid() == false) { + logger.warn("Error during issue state change!"); + ErrorCollection errorCollection = transResult.getErrorCollection(); + throwErrorException(errorCollection); + } + } + + /** + * Update existing JIRA issue from a JIRAChangeRequest + * + * @param httpServletRequest + * @param jcr JIRAChangeRequest + * @param issueId the id of updated issue + * @param selectedProperties OSLC slected properties + * @param syncType the type of synchronization + * @return id of the new JIRA issue + * @throws IOException + * @throws ServletException + */ + public static void updateIssue(HttpServletRequest httpServletRequest, final JiraChangeRequest jcr, final String issueId, Map selectedProperties, String syncType) throws Exception { + logger.debug("JiraManager - updateIssue"); + + final IssueManager issueManager = ComponentAccessor.getIssueManager(); + final MutableIssue issue = issueManager.getIssueObject(issueId); + + if (issue == null) { + logger.warn("Issue does not exist!"); + throw new NoResourceException("Issue with id " + issueId + " does not exist!"); + } + + PermissionManager.checkPermission(httpServletRequest, issue, Permissions.EDIT_ISSUE); + + String userName = PermissionManager.getUserName(httpServletRequest); + UserManager um = ComponentAccessor.getComponent(UserManager.class); + ApplicationUser appUser = um.getUserByName(userName); + JiraAuthenticationContext authenticationContext = ComponentAccessor.getJiraAuthenticationContext(); + authenticationContext.setLoggedInUser(appUser); + User user = ApplicationUsers.toDirectoryUser(um.getUserByName(userName)); + IssueService issueService = ComponentAccessor.getIssueService(); + + if (selectedProperties != null && selectedProperties.isEmpty()) { + selectedProperties = null; + } + + // If user has the permission to edit issue (checked above), there still could be restrictions + // to edit certain field (e.g. user can't change reporter). So following statement checks + // also for some of sub-permissions. + if (selectedProperties != null && syncType == null) { + PermissionManager.checkUpdatePermissions(appUser, issue, jcr, selectedProperties); + } + + // check configuration. If occurs error => not possible to set to ErrorSnapshot. Unknown "projectId" + SyncConfiguration leanSyncConfiguration = SyncUtils.getLeanSyncConfiguration(jcr, issue); + if (syncType != null && (leanSyncConfiguration == null || leanSyncConfiguration.getFirstInMapping() == null)) { + logger.debug("Issue with id " + issueId + " has wrong lean sync setting (projectId or issueTypeId!"); + throw new Exception("Issue with id " + issueId + " has wrong lean sync setting (projectId or issueTypeId!"); + } + + String errorLogName = null; + JiraIssueInputParameters jiraIssueInputParams = new JiraIssueInputParameters(); + if (leanSyncConfiguration != null) { + errorLogName = leanSyncConfiguration.getErrorLog(); + } + + // flag if occurs some error during parsing. true-will be updated status(error) log and thrown exception to client + boolean generateStatusLog = false; + try { + jiraIssueInputParams = JiraChangeRequest.toIssueParameters(jcr, issue, selectedProperties, syncType, leanSyncConfiguration, issue.getProjectObject()); + IssueInputParameters issueInputParams = jiraIssueInputParams.getIssueInputParameters(); + + // handle errors only for LeanSync + if (syncType != null) { + if (!jiraIssueInputParams.getErrorSyncHandler().isLogEmpty()) { + generateStatusLog = true; + logger.warn("Error during parsing issue"); + JiraIssueInputParameters newJiraIssueInputParameters = new JiraIssueInputParameters(); + + SyncUtils.putValueToErrorField(issue, newJiraIssueInputParameters, errorLogName, jiraIssueInputParams.getErrorSyncHandler()); + SyncUtils.handleErrorState(issue, newJiraIssueInputParameters, jiraIssueInputParams, leanSyncConfiguration, false); + + issueInputParams = newJiraIssueInputParameters.getIssueInputParameters(); + }else if (!jiraIssueInputParams.getWarnSyncHandler().isLogEmpty()) { + SyncUtils.putValueToErrorField(issue, jiraIssueInputParams, errorLogName, jiraIssueInputParams.getWarnSyncHandler()); + } else { + // only clear Error custom filed + SyncUtils.putValueToErrorField(issue, jiraIssueInputParams, errorLogName, null); + + } + } + + IssueValidationResult validationResult = issueService.validateUpdate(user, issue.getId(), issueInputParams); + if (!validationResult.isValid()) { + logger.warn("Update issue - parameters are not valid."); + ErrorCollection errorCollection = validationResult.getErrorCollection(); + throwErrorException(errorCollection); + } + + IssueService.IssueResult issueResult = issueService.update(user, (UpdateValidationResult)validationResult); + if (!issueResult.isValid()) { + logger.warn("Error during updating issue"); + ErrorCollection errorCollection = issueResult.getErrorCollection(); + throwErrorException(errorCollection); + } + + //after successful update, check if there is also request for change state + MutableIssue updatedIssue = issueResult.getIssue(); + if (updatedIssue != null && syncType == null) { + String statusId = issueInputParams.getStatusId(); + if (statusId != null) { + changeIssueState(issueResult.getIssue(), statusId); + } + + if (OSLCUtils.allowUpdate(selectedProperties, Constants.JIRA_TYPE_LABEL)) { + FieldManager.updateLabels(updatedIssue, jcr, user); + } + + if (OSLCUtils.allowUpdate(selectedProperties, Constants.JIRA_TYPE_VOTER)) { + FieldManager.updateVoters(updatedIssue, jcr, user); + } + + if (OSLCUtils.allowUpdate(selectedProperties, Constants.JIRA_TYPE_WATCHER)) { + FieldManager.updateWatchers(updatedIssue, jcr, user); + } + } + } catch (Exception e) { + logger.error("Error", e); + if (syncType == null) { + throw e; + } + //continue for LeanSync + generateStatusLog = true; // occurs error => generate snapshot and status information + jiraIssueInputParams.getErrorSyncHandler().addMessage(e.getMessage()); + + logger.warn(jiraIssueInputParams.getErrorSyncHandler().getMessagesAsString()); + + SyncUtils.saveErrorStatus(issue, jiraIssueInputParams.getErrorSyncHandler(), errorLogName); + SyncUtils.handleErrorState(issue, null, jiraIssueInputParams, leanSyncConfiguration, true); + } finally { + if (generateStatusLog && syncType != null) { + String statusMessage = "Error"; + final String errorValues = jiraIssueInputParams.getErrorSyncHandler().getMessagesAsString(); + + if (errorValues != null) { + statusMessage += ": " + errorValues; + } + logger.warn("Error", statusMessage); + throw new StatusException(statusMessage); + } + } + + } + + /** + * It prepares error message from Error Collection and then throws IssueValidationException + * @param errorCollection collection of errors + * @throws IssueValidationException if the errors is not empty then throws IssueValidationException + */ + private static void throwErrorException(ErrorCollection errorCollection) throws IssueValidationException{ + StringBuilder sb = new StringBuilder(); + + Map errors = errorCollection.getErrors(); + Collection errorMessages = errorCollection.getErrorMessages(); + + if (errors != null && !errors.isEmpty()) { + sb.append(errors.toString()); + } + if (errorMessages != null && !errorMessages.isEmpty()) { + sb.append(errorMessages.toString()); + } + String message = sb.toString(); + logger.warn("Validation result: " + message); + throw new IssueValidationException(message); + } + + +} \ No newline at end of file diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/managers/PermissionManager.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/managers/PermissionManager.java new file mode 100644 index 0000000..b17382a --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/managers/PermissionManager.java @@ -0,0 +1,276 @@ +package com.ericsson.jira.oslc.managers; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.WebApplicationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.atlassian.jira.component.ComponentAccessor; +import com.atlassian.jira.issue.Issue; +import com.atlassian.jira.permission.GlobalPermissionKey; +import com.atlassian.jira.project.Project; +import com.atlassian.jira.security.Permissions; +import com.atlassian.jira.user.ApplicationUser; +import com.atlassian.jira.user.util.UserManager; +import com.ericsson.jira.oslc.Constants; +import com.ericsson.jira.oslc.exceptions.PermissionException; +import com.ericsson.jira.oslc.resources.JiraChangeRequest; +import com.ericsson.jira.oslc.resources.JiraIssueStatus; +import com.ericsson.jira.oslc.servlet.CredentialsFilter; +import com.ericsson.jira.oslc.utils.OSLCUtils; + + +/** + * A class for operations with the users and theirs permissions + * + */ +public class PermissionManager { + private static Logger logger = LoggerFactory.getLogger(PermissionManager.class); + + /** + * Checks a permission for logged user and the issue. + * @param request HttpServletRequest + * @param issue JIRA issue + * @param permission the permission which will be checked + * @throws PermissionException if the user doesn't have the permission the exception will be thrown + */ + public static void checkPermission(final HttpServletRequest request, final Issue issue, int permission) throws PermissionException { + ApplicationUser appUser = null; + if (request != null) { + appUser = getAppUserFromRequest(request); + } + if (appUser == null) { + appUser = getLoggedUser(); + if (appUser == null) { + throw new PermissionException("User is not defined."); + } + } + + boolean allowed = hasPermission(appUser, issue, permission); + if (!allowed) { + throw new PermissionException(); + } + } + + /** + * Checks a permission for defined user and the issue. + * @param appUser the user which will be checked + * @param issue JIRA issue + * @param permission the permission which will be checked + * @throws PermissionException if the user doesn't have the permission the exception will be thrown + */ + public static void checkPermissionWithUser(ApplicationUser appUser, final Issue issue, int permission) throws PermissionException { + boolean allowed = ComponentAccessor.getPermissionManager().hasPermission(permission, issue, appUser); + if (!allowed) { + throw new PermissionException(); + } + } + + /** + * Checks a permission for defined user and the issue. + * If the user doesn't have the permission the exception will be thrown with defined message {additionalMesage} + * @param appUser the user which will be checked + * @param issue JIRA issue + * @param permission the permission which will be checked + * @param additionalMesage if the user doesn't have the permission the exception will be thrown with this message + * @throws PermissionException if the user doesn't have the permission the exception will be thrown + */ + public static void checkPermissionWithUser(ApplicationUser appUser, final Issue issue, int permission, String additionalMesage) throws PermissionException { + boolean allowed = ComponentAccessor.getPermissionManager().hasPermission(permission, issue, appUser); + if (!allowed) { + throw new PermissionException(additionalMesage); + } + } + + /** + * Checks a permission for defined user and the issue. + * @param appUser the user that will be checked + * @param issue JIRA issue + * @param permission the permission which will be checked + * @return true - the user has a permission, otherwise false + */ + public static boolean hasPermission(ApplicationUser appUser, final Issue issue, int permission) { + return ComponentAccessor.getPermissionManager().hasPermission(permission, issue, appUser); + } + + /** + * Checks a permission for defined user and the project. + * @param appUser the user that will be checked + * @param project the project that will be checked + * @param permission the permission which will be checked + * @return true - the user has a permission on the project, otherwise false + */ + public static boolean hasPermission(ApplicationUser appUser, final Project project, int permission) { + return ComponentAccessor.getPermissionManager().hasPermission(permission, project, appUser); + } + + /** + * Checks a permission for defined user and the project. + * @param appUser the user that will be checked + * @param project the project that will be checked + * @param permission the permission which will be checked + * @return false - the user hasn't a permission on the project + * @throws PermissionException if the user doesn't have the permission the exception will be thrown + */ + public static boolean checkPermission(ApplicationUser appUser, final Project project, int permission) throws PermissionException { + boolean allowed = ComponentAccessor.getPermissionManager().hasPermission(permission, project, appUser); + if (!allowed) { + throw new PermissionException(); + } + return allowed; + } + + /** + * Check if the user has update permission on the JIRA issue + * @param appUser the user that will be checked + * @param issue JIRA issue + * @param jcr JiraChangeRequest + * @param selectedProperties OSLC properties + * @return the user has a permission on the issue, otherwise false + * @throws PermissionException if the user doesn't have the permission the exception will be thrown + */ + public static boolean checkUpdatePermissions(ApplicationUser appUser, final Issue issue, final JiraChangeRequest jcr, final Map selectedProperties) throws PermissionException { + + if (OSLCUtils.allowUpdate(selectedProperties, Constants.JIRA_TYPE_WATCHER)) { + checkPermissionWithUser(appUser, issue, Permissions.MANAGE_WATCHER_LIST, "User " + appUser.getName() + " has not permission to manage watchers!"); + } + + if (OSLCUtils.allowUpdate(selectedProperties, Constants.JIRA_TYPE_REPORTER)) { + checkPermissionWithUser(appUser, issue, Permissions.MODIFY_REPORTER, "User " + appUser.getName() + " has not permission to change reporter!"); + } + + if (OSLCUtils.allowUpdate(selectedProperties, Constants.DCTERMS_DUEDATE) || OSLCUtils.allowUpdate(selectedProperties, Constants.JIRA_TYPE_ORIGINAL_ESTIMATE) || OSLCUtils.allowUpdate(selectedProperties, Constants.JIRA_TYPE_REMAINING_ESTIMATE)) { + checkPermissionWithUser(appUser, issue, Permissions.SCHEDULE_ISSUE, "User " + appUser.getName() + " has not permission to schedule issue!"); + } + + if (OSLCUtils.allowUpdate(selectedProperties, Constants.JIRA_TYPE_STATUS)) { + checkPermissionWithUser(appUser, issue, Permissions.TRANSITION_ISSUE, "User " + appUser.getName() + " has not permission to change status!"); + + // user can change status, but could be restricted from closing issue + JiraIssueStatus status = jcr.getIssueStatus(); + if (status != null) { + String statusId = OSLCUtils.getValueFromAbout(status.getAbout()); + if (statusId.compareTo("6") == 0) { // 6 - Closed status + checkPermissionWithUser(appUser, issue, Permissions.CLOSE_ISSUE, "User " + appUser.getName() + " has not permission to close issue!"); + } + } + // NOTE: + // Previous code will work only for standard Jira workflow, which contains + // Closed status. + // If there is different workflow for certain issue type, which has + // different end state, + // it won't work. + } + + if (OSLCUtils.allowUpdate(selectedProperties, Constants.JIRA_TYPE_ASIGNEE)) { + checkPermissionWithUser(appUser, issue, Permissions.ASSIGN_ISSUE, "User " + appUser.getName() + " has not permission to change asignee!"); + + // user can assign someone to issue, but that person could be restricted + // from beeing assigned + UserManager um = ComponentAccessor.getUserManager(); + ApplicationUser asignee = um.getUserByName(jcr.getAssignee()); + // If incoming assignee is not defined (null), we assume that 'Unassigned' + // should be set. + // In other cases do permission checking. + if (asignee != null) { + checkPermissionWithUser(asignee, issue, Permissions.ASSIGNABLE_USER, "User " + jcr.getAssignee() + " can't be assigned to issue " + issue.getKey() + "!"); + } + } + + return true; + } + + /** + * Return true if the user has specified permission + * @param user Application use + * @param permission specific permission + * @return true if the user has specified permission else false + */ + public static boolean hasPermission(ApplicationUser user, GlobalPermissionKey permission) { + return ComponentAccessor.getGlobalPermissionManager().hasPermission(permission, user); + } + + /** + * Check if the user is admin + * @param userManager User manager + * @return true the user is a admin, otherwise false + */ + public static boolean isSystemAdmin(com.atlassian.sal.api.user.UserManager userManager){ + ApplicationUser loggedUser = getLoggedUser(); + if(loggedUser != null){ + String username = getLoggedUser().getUsername(); + return userManager.isSystemAdmin(username); + } + + return false; + } + + /** + * Returns a logged user + * @param request HttpServletRequest + * @return logged user + */ + public static ApplicationUser getAppUserFromRequest(final HttpServletRequest request){ + String userName = getUserName(request); + + if(userName != null && !userName.trim().isEmpty()){ + UserManager um = ComponentAccessor.getComponent(UserManager.class); + ApplicationUser appUser = um.getUserByName(userName); + return appUser; + } + + return null; + } + + /** + * Returns logged user + * @return logged user + */ + public static ApplicationUser getLoggedUser() { + return ComponentAccessor.getJiraAuthenticationContext().getUser(); + } + + /** + * Returns a user name of logged a user + * @param request HttpServletRequest + * @return the user name of logged the user + */ + public static String getUserName(HttpServletRequest request) { + Object attribute = request.getAttribute(CredentialsFilter.USERNAME_ATTRIBUTE); + if (attribute == null) { + logger.error("Session does not contain any credentials"); + throw new WebApplicationException(401); + } + return (String) attribute; + } +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/oslcclient/Client.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/oslcclient/Client.java new file mode 100644 index 0000000..54bcd4b --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/oslcclient/Client.java @@ -0,0 +1,897 @@ +package com.ericsson.jira.oslc.oslcclient; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Map; + +import net.oauth.OAuthAccessor; +import net.oauth.OAuthConsumer; +import net.oauth.OAuthServiceProvider; + +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.wink.json4j.JSON; +import org.apache.wink.json4j.JSONException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.atlassian.jira.component.ComponentAccessor; +import com.atlassian.jira.config.properties.APKeys; +import com.ericsson.jira.oslc.HTTP; +import com.ericsson.jira.oslc.HTTP.OAuthPhases; +import com.ericsson.jira.oslc.constants.JiraConstants; +import com.ericsson.jira.oslc.resources.RootServices; +import com.ericsson.jira.oslc.resources.ServiceProvider; +import com.ericsson.jira.oslc.resources.ServiceProviderDialog; +import com.ericsson.jira.oslc.utils.LogUtils; +import com.ericsson.jira.oslc.utils.OSLCUtils; +import com.hp.hpl.jena.rdf.model.Literal; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.rdf.model.NodeIterator; +import com.hp.hpl.jena.rdf.model.Property; +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.Statement; +import com.hp.hpl.jena.rdf.model.StmtIterator; + +/** + * A class for operations with HTTP requests/reponse + * + */ +public class Client { + private static final String CURRENT_CLASS = "Client"; + private static final Logger log = LoggerFactory.getLogger(Client.class); + + //last status code from http response + private int responseCode; + //last phrase from http response + private String responsePhrase; + + public int getLastResponseCode() { + return responseCode; + } + + public String getLastResponsePhrase() { + return responsePhrase; + } + + /** + * Method extracts body from http response to String instance + * + * @param is input stream (should be accessible from http response) + * @return string instance with response body + */ + private String getResponseBody(InputStream is) { + String currentMethod = "getResponseBody"; + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + String line = ""; + StringBuilder sb = new StringBuilder(); + try { + while ((line = br.readLine()) != null) { + sb.append(line); + } + } + catch (IOException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + } + return sb.toString(); + } + + /** + * Method gets oAuth details from rootservices url. + * @param rootServicesURI + * @return instance of class with detailed data about rootservices + */ + public RootServices getRootServicesDetails(String rootServicesURI) { + String currentMethod = "getRootServicesDetails"; + + try { + //no authentication needed + HttpResponse resp = HTTP.get(rootServicesURI, "application/rdf+xml", null, null, null); + + if (resp != null) { + responseCode = resp.getStatusLine().getStatusCode(); + responsePhrase = resp.getStatusLine().getReasonPhrase(); + + if (responseCode == 200) { + try { + InputStream is = resp.getEntity().getContent(); + Model rdfModel = null; + rdfModel = ModelFactory.createDefaultModel(); + rdfModel.read(is, null); + + RootServices rs = new RootServices(); + final String ns = "http://jazz.net/xmlns/prod/jazz/jfs/1.0/"; + rs.setOAuthAccessTokenURL(OSLCUtils.getProperty(rdfModel, ns, "oauthAccessTokenUrl")); + rs.setOAuthDomain(OSLCUtils.getProperty(rdfModel, ns, "oauthDomain")); + rs.setOAuthRequestConsumerKeyURL(OSLCUtils.getProperty(rdfModel, ns, "oauthRequestConsumerKeyUrl")); + rs.setOAuthRequestTokenURL(OSLCUtils.getProperty(rdfModel, ns, "oauthRequestTokenUrl")); + rs.setOAuthUserAuthorizationURL(OSLCUtils.getProperty(rdfModel, ns, "oauthUserAuthorizationUrl")); + + return rs; + } + catch (Exception ex) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + ex.getMessage()); + ex.printStackTrace(); + responseCode = 500; + responsePhrase = "Error in parsing response data!\\nSource isn't root services or malformed!"; + } + } + } + } + catch (ClientProtocolException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + catch (IOException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + + return null; + } + + /** + * Gets in-bound consumer key from remote application. + * @param url consumer key request url + * @param consumerName consumer name + * @param secret consumer secret + * @param userID user id + * @return in-bound consumer key + */ + public String getConsumerKey(String url, String consumerName, String secret, String userID) { + String key = null; + try { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + sb.append("\"name\": \"" + consumerName + "\","); + sb.append("\"secretType\": \"string\","); + sb.append("\"secret\": \"" + secret + "\","); + sb.append("\"trusted\": false,"); + sb.append("\"userId\": null"); + sb.append("}"); + + HttpResponse resp = null; + + //no authentication needed + resp = HTTP.post(url, sb.toString(), null, "application/json", null, null); + if (resp != null) { + responseCode = resp.getStatusLine().getStatusCode(); + responsePhrase = resp.getStatusLine().getReasonPhrase(); + + //no authentication needed + if (responseCode != 200) { + resp = HTTP.post(url, sb.toString(), null, "text/json", null, null); + if (resp != null) { + responseCode = resp.getStatusLine().getStatusCode(); + responsePhrase = resp.getStatusLine().getReasonPhrase(); + } + } + + if (responseCode == 200) { + InputStream is = resp.getEntity().getContent(); + String body = getResponseBody(is); + + org.apache.wink.json4j.JSONObject j; + try { + j = (org.apache.wink.json4j.JSONObject) JSON.parse(body); + key = j.getString("key"); + } + catch (NullPointerException e) { + log.error(CURRENT_CLASS + ".getConsumerKey Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = "Error in parsing response data!\\nSource isn't consumer key data or malformed json data!"; + } + catch (JSONException e) { + log.error(CURRENT_CLASS + ".getConsumerKey Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = "Error in parsing response data!\\nSource isn't consumer key data or malformed json data!"; + } + } + } + } + catch (ClientProtocolException e) { + log.error(CURRENT_CLASS + ".getConsumerKey Exception: " + e.getMessage()); + e.printStackTrace(); + } + catch (IOException e) { + log.error(CURRENT_CLASS + ".getConsumerKey Exception: " + e.getMessage()); + e.printStackTrace(); + } + + return key; + } + + /** + * Method extracts service provider catalog uri from root services. + * @param rootServicesURI link to root services + * @return uri to service provider catalog + */ + public String getServicesProviderCatalogURI(String rootServicesURI) { + String currentMethod = "getServicesProviderCatalogURI"; + String result = null; + + try { + //no authentication needed + HttpResponse resp = HTTP.get(rootServicesURI, "application/rdf+xml", null, null, null); + + if (resp != null) { + responseCode = resp.getStatusLine().getStatusCode(); + responsePhrase = resp.getStatusLine().getReasonPhrase(); + + if (responseCode == 200) { + try { + InputStream is = resp.getEntity().getContent(); + Model rdfModel = null; + rdfModel = ModelFactory.createDefaultModel(); + rdfModel.read(is, null); + + result = OSLCUtils.getProperty(rdfModel, + "http://open-services.net/xmlns/cm/1.0/", "cmServiceProviders"); + } + catch (Exception ex) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + ex.getMessage()); + ex.printStackTrace(); + responseCode = 500; + responsePhrase = "Error in parsing response data!\\nSource isn't root services or malformed!"; + } + } + } + } + catch (ClientProtocolException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + catch (IOException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + + return result; + } + + public RootServices getRootServicesDetailsFromCatalog(String catalogURI) { + return null; + } + + /** + * Method gets service providers from catalog. + * @param catalogURI + * @return collection of string objects, which represents service providers in format: + * "title1","link1","title2","link2"..."titleN","linkN" + */ + public ArrayList getServiceProviders(String catalogURI, OAuthAccessor accessor) { + String currentMethod = "getServiceProviders"; + ArrayList serviceProviders = new ArrayList(); + + try { + //OAuth needed + HttpResponse resp = HTTP.get( + catalogURI, "application/rdf+xml", null, null, null, + accessor, null, OAuthPhases.OAUTH_PHASE_3, null); + + if (resp != null) { + responseCode = resp.getStatusLine().getStatusCode(); + responsePhrase = resp.getStatusLine().getReasonPhrase(); + + if (responseCode == 200) { + InputStream is = resp.getEntity().getContent(); + Model rdfModel = null; + rdfModel = ModelFactory.createDefaultModel(); + rdfModel.read(is, null); + + Property prop = rdfModel.getProperty("http://open-services.net/ns/core#" + "serviceProvider"); + StmtIterator statements = rdfModel.listStatements((Resource)null, prop, (RDFNode)null); + + while (statements.hasNext()) { + Statement st = statements.next(); + String providerURI = st.getObject().toString(); + + Property titleProp = rdfModel.createProperty("http://purl.org/dc/terms/title"); + Statement statement = rdfModel.getProperty((Resource)st.getObject(), titleProp); + String title = ""; + if (statement != null && statement.getObject() != null) + title = statement.getObject().toString(); + + int idx = title.indexOf("^^"); + if (idx > -1) + title = title.substring(0, idx); + + serviceProviders.add(title); + serviceProviders.add(providerURI); + } + } + } + } + catch (ClientProtocolException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + catch (IOException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + + return serviceProviders; + } + + public void extractDialogs(Model rdfModel, ServiceProvider sp, boolean isCreation) { + String dlgType = ""; + if (isCreation == true) + dlgType = "creationDialog"; + else + dlgType = "selectionDialog"; + + Property prop = rdfModel.createProperty("http://open-services.net/ns/core#", dlgType); + + NodeIterator nodes = rdfModel.listObjectsOfProperty(prop); + while (nodes.hasNext()) { + ServiceProviderDialog dlg = new ServiceProviderDialog(); + + Resource res = (Resource)nodes.next(); + + Property titleProp = rdfModel.createProperty("http://purl.org/dc/terms/title"); + String title = OSLCUtils.getResourceLiteralValue(res, titleProp); + dlg.setType(title); + + Property createDialogURLProperty = rdfModel.getProperty("http://open-services.net/ns/core#" + "dialog"); + String uri = res.getPropertyResourceValue(createDialogURLProperty).getURI(); + dlg.setLink(uri); + + Property cDlgWProp = rdfModel.getProperty("http://open-services.net/ns/core#" + "hintWidth"); + Property cDlgHProp = rdfModel.getProperty("http://open-services.net/ns/core#" + "hintHeight"); + + Statement st1 = res.getProperty(cDlgWProp); + if (st1 != null && st1.getObject() != null) + dlg.setWidth(st1.getObject().toString()); + + Statement st2 = res.getProperty(cDlgHProp); + if (st2 != null && st2.getObject() != null) + dlg.setHeight(st2.getObject().toString()); + + sp.addDialog(dlg, isCreation); + } + } + + /** + * Method loads details for given service provider: + * - creation dialogs (type, link, width, height) + * - selection dialogs (type, link, width, height) + * @param spURL link to service provider + * @return service provider detailed data + */ + public ServiceProvider getServiceProviderDetails(String spURL, OAuthAccessor accessor) { + String currentMethod = "getServiceProviderDetails"; + ServiceProvider sp = null; + + try { + //OAuth needed + HttpResponse resp = HTTP.get( + spURL, "application/rdf+xml", null, null, null, + accessor, null, OAuthPhases.OAUTH_PHASE_3, null); + + if (resp != null) { + responseCode = resp.getStatusLine().getStatusCode(); + responsePhrase = resp.getStatusLine().getReasonPhrase(); + + if (responseCode == 200) { + try { + InputStream is = resp.getEntity().getContent(); + Model rdfModel = null; + rdfModel = ModelFactory.createDefaultModel(); + rdfModel.read(is, null); + + sp = new ServiceProvider(); + + extractDialogs(rdfModel, sp, true); //get creation dialogs + extractDialogs(rdfModel, sp, false); //get selection dialogs + } + catch (Exception ex) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + ex.getMessage()); + ex.printStackTrace(); + responseCode = 500; + responsePhrase = "Error in parsing response data!\\nSource isn't service provider or malformed!"; + } + } + } + } + catch (ClientProtocolException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + catch (IOException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + + return sp; + } + + /** + * Add link to Jira issue to remote application. + * + * @param issueKey issue key in format PROJECT-# + * @param projectId id of project + * @param url link to remote application, where issue link will be sent to + */ + public void addLinkToRemoteURL(String issueKey, Long projectId, String projectName, String url, OAuthAccessor accessor) { + String currentMethod = "addLinkToRemoteURL"; + + // Construct link to issue. The very same link should be also in object in remote + // application. + String issueLinkToAdd = + ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL) + + "/rest/jirarestresource/1.0/" + projectId.toString() + "/changeRequests/" + issueKey; + + Model rdfModel = null; + boolean found = false; + + try { + // First get object from remote application. Response is expected to be in rdf+xml format + // and should contains list of jira issue links (relateChangeRequest). + + //OAuth needed + HttpResponse resp = HTTP.get( + url, "application/rdf+xml", null, null, null, + accessor, null, OAuthPhases.OAUTH_PHASE_3, null); + + if (resp != null) { + responseCode = resp.getStatusLine().getStatusCode(); + responsePhrase = resp.getStatusLine().getReasonPhrase(); + + if (responseCode == 200) { + try { + //get response body + InputStream is = resp.getEntity().getContent(); + String body = getResponseBody(is); + + //initiate rdf model from response body + rdfModel = ModelFactory.createDefaultModel(); + rdfModel.read(new ByteArrayInputStream(body.getBytes()), null); + + //find all occurences of "relatedChangeRequest" witin the response, resp. rdf model + Property prop = rdfModel.createProperty("http://open-services.net/ns/cm#", "relatedChangeRequest"); + NodeIterator it = rdfModel.listObjectsOfProperty(prop); + + //go through the list + while (it.hasNext()) { + RDFNode node = it.next(); + Resource res = (Resource)node; + String s = res.toString(); + // If there is a issue link in response, which corresponds to given issue link, + // skip adding current link (found == true). + if (s.compareTo(issueLinkToAdd) == 0) { + found = true; + break; + } + } + } + catch (Exception ex) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + ex.getMessage()); + ex.printStackTrace(); + responseCode = 500; + responsePhrase = "Error in parsin object from remote application!"; + return; + } + } + else { + //getting remote app object failed + return; + } + } + } + catch (ClientProtocolException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + catch (IOException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + + if (found == false && rdfModel != null) { + //given link was NOT found, so add it to model + try { + //add link to model - single property to existing main resource + Resource res = rdfModel.getResource(url); + Property addProp = rdfModel.createProperty("http://open-services.net/ns/cm#", "relatedChangeRequest"); + Resource link = rdfModel.createResource(issueLinkToAdd); + + Statement statement = rdfModel.createStatement(res, addProp, link); + rdfModel.add(statement); + + + if (projectName != null) { + Property extProjectProp = rdfModel.createProperty("http://open-services.net#", "extProject"); + res.addProperty(extProjectProp, projectName); + } + + //add link to model - details + Resource res2 = rdfModel.createResource(); + + Property subject = rdfModel.createProperty("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "subject"); + Resource subjectRes = rdfModel.createResource(url); + + Property predicate = rdfModel.createProperty("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "predicate"); + Resource predicateRes = rdfModel.createResource("http://open-services.net/ns/cm#relatedChangeRequest"); + + Property object = rdfModel.createProperty("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "object"); + Resource objectRes = rdfModel.createResource(issueLinkToAdd); + + Property type = rdfModel.createProperty("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "type"); + Resource typeRes = rdfModel.createResource("http://www.w3.org/1999/02/22-rdf-syntax-ns#Statement"); + + Property title = rdfModel.createProperty("http://purl.org/dc/terms/title"); + Literal titleLit = rdfModel.createLiteral(issueKey); + + res2.addProperty(subject, subjectRes); + res2.addProperty(predicate, predicateRes); + res2.addProperty(object, objectRes); + res2.addProperty(type, typeRes); + res2.addProperty(title, titleLit); + + //write model back + ByteArrayOutputStream os = new ByteArrayOutputStream(); + rdfModel.write(os); + os.flush(); + + //send updated model back to remote application (same link) + HttpResponse resp = HTTP.put(url, os.toString(), "application/rdf+xml", "application/rdf+xml", + null, null, accessor, JiraConstants.RELATED_CHANGE_REQUEST_URL_APPENDIX, + OAuthPhases.OAUTH_PHASE_3); + + if (resp != null) { + responseCode = resp.getStatusLine().getStatusCode(); + responsePhrase = resp.getStatusLine().getReasonPhrase(); + } + } + catch (ClientProtocolException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + catch (IOException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + } + else { + //given link was found, so it is (probably) already linked in remote application + responseCode = 404; + responsePhrase = "Link to add already exists in remote application!"; + } + } + + /** + * Method removes link to Jira issue from remote application. + * + * @param issueKey issue key (project-id), which represent issue + * @param projectId project id, to which issue belongs + * @param url link to object in remote application, where issue link should be stored + */ + public void removeLinkFromRemoteURL(String issueKey, Long projectId, String url, OAuthAccessor accessor) { + String currentMethod = "removeLinkFromRemoteURL"; + + // Construct link to issue. The very same link should be also in object in remote + // application. + String issueLinkToRemove = + ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL) + + "/rest/jirarestresource/1.0/" + projectId.toString() + "/changeRequests/" + issueKey; + + Model rdfModel = null; + boolean found = false; + + try { + // First get object from remote application. Response is expected to be in rdf+xml format + // and should contains list of jira issue links (relateChangeRequest). + + //OAuth needed + HttpResponse resp = HTTP.get( + url, "application/rdf+xml", null, null, null, + accessor, null, OAuthPhases.OAUTH_PHASE_3, null); + + responseCode = resp.getStatusLine().getStatusCode(); + responsePhrase = resp.getStatusLine().getReasonPhrase(); + + if (responseCode == 200) { + try { + //get response body + InputStream is = resp.getEntity().getContent(); + String body = getResponseBody(is); + + //initiate rdf model from response body + rdfModel = ModelFactory.createDefaultModel(); + rdfModel.read(new ByteArrayInputStream(body.getBytes()), null); + + //find all occurences of "relatedChangeRequest" witin the response, resp. rdf model + Property prop = rdfModel.createProperty("http://open-services.net/ns/cm#", "relatedChangeRequest"); + NodeIterator it = rdfModel.listObjectsOfProperty(prop); + + //go through the list + while (it.hasNext()) { + RDFNode node = it.next(); + Resource res = (Resource)node; + String s = res.toString(); + // If there is a issue link in response, which corresponds to given issue link, + // it is removed from model. + if (s.compareTo(issueLinkToRemove) == 0) { + rdfModel.removeAll(null, prop, node); + found = true; + break; + } + } + } + catch (Exception ex) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + ex.getMessage()); + ex.printStackTrace(); + responseCode = 500; + responsePhrase = "Error in parsing object from remote application!"; + return; + } + } + else + return; //http failed + } + catch (ClientProtocolException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + responsePhrase = e.getMessage(); + } + catch (IOException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + responseCode = 500; + //responsePhrase = "Connection to remote application failed!"; + responsePhrase = e.getMessage(); + return; + } + + if (found == true && rdfModel != null) { + //given link was found and removed from model + try { + //write model back + ByteArrayOutputStream os = new ByteArrayOutputStream(); + rdfModel.write(os); + os.flush(); + + //send updated model back to remote application (same link) + HttpResponse resp = HTTP.put(url, os.toString(), "application/rdf+xml", "application/rdf+xml", + null, null, accessor, JiraConstants.RELATED_CHANGE_REQUEST_URL_APPENDIX, + OAuthPhases.OAUTH_PHASE_3); + + responseCode = resp.getStatusLine().getStatusCode(); + responsePhrase = resp.getStatusLine().getReasonPhrase(); + } + catch (IOException e) { + log.error(CURRENT_CLASS + "." + currentMethod + " Exception: " + e.getMessage()); + e.printStackTrace(); + + responseCode = 500; + responsePhrase = "Connection to remote application failed!"; + return; + } + } + else { + //given link wasn't found, so it (probably) doesn't exist in object in remote application + responseCode = 404; + responsePhrase = "Link to remove was not found!"; + } + } + + + /** + * Get a OAuth request token from remote server defined by requestTokenURL + * @param requestTokenURL the URL for getting OAuth request token + * @param accessor OAuth accessor + * @return OAuth request token + */ + private String [] oAuthGetRequestToken(String requestTokenURL, OAuthAccessor accessor) { + + try { + HttpResponse response = HTTP.post( + requestTokenURL, "", null, null, null, null, accessor, null, OAuthPhases.OAUTH_PHASE_1); + + if (response != null) { + responseCode = response.getStatusLine().getStatusCode(); + responsePhrase = response.getStatusLine().getReasonPhrase(); + + if (responseCode == 200) { + String body = getResponseBody(response.getEntity().getContent()); + + String [] result = new String[2]; + String [] elements = body.split("&"); + int idx = elements[0].indexOf("="); + result[0] = elements[0].substring(idx+1, elements[0].length()); + idx = elements[1].indexOf("="); + result[1] = elements[1].substring(idx+1, elements[1].length()); + return result; + } + } + } + catch (ClientProtocolException e) { + log.error(CURRENT_CLASS + ".oAuthGetRequestToken Exception: " + e.getMessage()); + e.printStackTrace(); + return null; + } + catch (IOException e) { + log.error(CURRENT_CLASS + ".oAuthGetRequestToken Exception: " + e.getMessage()); + e.printStackTrace(); + return null; + } + + return null; + } + + /** + * Get a OAuth access token from remote server defined by accessTokenURL + * @param accessTokenURL the URL for getting OAuth access token + * @param accessor OAuth accessor + * @param verifier verification code + * @return OAuth access token + */ + private String [] oAuthGetAccessToken(String accessTokenURL, OAuthAccessor accessor, String verifier) { + try { + HttpResponse response = HTTP.get( + accessTokenURL, null, "text/xml", null, null, accessor, verifier, OAuthPhases.OAUTH_PHASE_2, null); + + if (response != null) { + responseCode = response.getStatusLine().getStatusCode(); + responsePhrase = response.getStatusLine().getReasonPhrase(); + + if (responseCode == 200) { + String body = getResponseBody(response.getEntity().getContent()); + + String [] result = new String[2]; + String [] elements = body.split("&"); + int idx = elements[0].indexOf("="); + result[0] = elements[0].substring(idx+1, elements[0].length()); + idx = elements[1].indexOf("="); + result[1] = elements[1].substring(idx+1, elements[1].length()); + return result; + } + } + } + catch (ClientProtocolException e) { + log.error(CURRENT_CLASS + ".oAuthGetAccessToken Exception: " + e.getMessage()); + e.printStackTrace(); + } + catch (IOException e) { + log.error(CURRENT_CLASS + ".oAuthGetAccessToken Exception: " + e.getMessage()); + e.printStackTrace(); + } + + return null; + } + + /** + * Methods starts 1st phase of OAuth dance - getting request token from provider. + * Note: After method gets request token back in response, OAuth dance continue on callback url + * (@see com.ericsson.jira.oslc.services.OAuthServices#authorizationcallbackDialog). + * @param consumer object with consumer information (consumer key, consumer secret, ...) + * @param callbackURL link, where provider responses after request token is sent + * @return accessor object with request token (used in next phase) + */ + public OAuthAccessor oAuthDancePhase1(OAuthConsumer consumer, String callbackURL) { + OAuthServiceProvider provider = new OAuthServiceProvider(null, null, null); + OAuthConsumer c = new OAuthConsumer(callbackURL, consumer.consumerKey, consumer.consumerSecret, provider); + OAuthAccessor a = new OAuthAccessor(c); + + String [] request = this.oAuthGetRequestToken(consumer.serviceProvider.requestTokenURL, a); + + if (request != null) { + a.requestToken = request[0]; + a.tokenSecret = request[1]; + } + + return a; + } + + /** + * Methods performs 2nd phase of OAuth dance - getting access token. + * Access token is stored to accessor. + * + * @param consumer consumer object with consumer information (consumer key, consumer secret, ...) + * @param accessor accessor object with request token (from previous phase) + */ + public void oAuthDancePhase2(OAuthConsumer consumer, OAuthAccessor accessor, String verifier) { + String [] access = this.oAuthGetAccessToken( + consumer.serviceProvider.accessTokenURL, accessor, verifier); + + if (access != null) { + //put access key to accessor + + accessor.accessToken = access[0]; + accessor.tokenSecret = access[1]; + } + } + + /** + * Updates the remote resource + * @param uri the URI of remote resource + * @param body the body of PUT request. + * @param username the user name of the user who will update the remote resource + * @param password the password of the user who will update the remote resource + * @param headers HTTP header of PUT request + * @return the response of the request + * @throws ClientProtocolException + * @throws IOException + */ + public HttpResponse updateRemoteResource(String uri, String body, String username, String password, Map headers) throws ClientProtocolException, IOException { + HttpResponse resp = null; + log.debug("Client.updateRemoteResource"); + + resp = HTTP.put(uri, body, "application/rdf+xml", "application/rdf+xml", username, password, null, null, null, headers); + + return resp; + } + + public HttpResponse getRemoteResource(String uri, String username, String password, Map headers ) throws ClientProtocolException, IOException { + HttpResponse resp = null; + log.debug("Client.getRemoteResource for uri: " + uri); + resp = HTTP.get(uri, "application/rdf+xml", username, password, headers); + + return resp; + } + +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcRdfJsonArrayProvider.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcRdfJsonArrayProvider.java new file mode 100644 index 0000000..2c3b254 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcRdfJsonArrayProvider.java @@ -0,0 +1,150 @@ +package com.ericsson.jira.oslc.provider; + +/******************************************************************************* + * Copyright (c) 2012 IBM Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * + * Russell Boykin - initial API and implementation + * Alberto Giammaria - initial API and implementation + * Chris Peters - initial API and implementation + * Gianluca Bernardini - initial API and implementation + *******************************************************************************/ + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; +import org.eclipse.lyo.oslc4j.provider.json4j.AbstractOslcRdfJsonProvider; + +@Provider +@Produces(OslcMediaType.APPLICATION_JSON) +@Consumes(OslcMediaType.APPLICATION_JSON) +public class OslcRdfJsonArrayProvider + extends AbstractOslcRdfJsonProvider + implements MessageBodyReader, + MessageBodyWriter> +{ + public OslcRdfJsonArrayProvider() + { + super(); + } + + @Override + public long getSize(final ResponseArrayWrapper objects, + final Class type, + final Type genericType, + final Annotation[] annotation, + final MediaType mediaType) + { + return -1; + } + + @Override + public boolean isWriteable(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) + { + if(ResponseArrayWrapper.class.isAssignableFrom(type)){ + return + (isWriteable( + annotations, + OslcMediaType.APPLICATION_JSON_TYPE, + mediaType)); + } + return false; + } + + protected static boolean isWriteable(final Annotation[] annotations, final MediaType requiredMediaType, final MediaType actualMediaType) { + + // When handling "recursive" writing of an OSLC Error object, we get a + // zero-length array of annotations + if ((annotations != null) && ((annotations.length > 0))) { + for (final Annotation annotation : annotations) { + if (annotation instanceof Produces) { + final Produces producesAnnotation = (Produces) annotation; + + for (final String value : producesAnnotation.value()) { + if (requiredMediaType.isCompatible(MediaType.valueOf(value))) { + return true; + } + } + } + } + + return false; + } + + // We do not have annotations when running from the non-web client. + return requiredMediaType.isCompatible(actualMediaType); + + } + + @Override + public void writeTo(final ResponseArrayWrapper objects, + final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap map, + final OutputStream outputStream) + throws IOException, + WebApplicationException + { + + Object[] resources =objects.getResource(); + writeTo(true, + resources, + mediaType, + map, + outputStream); + } + @Override + public boolean isReadable(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) + { + return (type.isArray()) && + (isReadable(type.getComponentType(), + OslcMediaType.APPLICATION_JSON_TYPE, + mediaType)); + } + + @Override + public Object[] readFrom(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap map, + final InputStream inputStream) + throws IOException, + WebApplicationException + { + return readFrom(type.getComponentType(), + mediaType, + map, + inputStream); + } +} \ No newline at end of file diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcRdfJsonErrorProvider.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcRdfJsonErrorProvider.java new file mode 100644 index 0000000..45d4220 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcRdfJsonErrorProvider.java @@ -0,0 +1,70 @@ +package com.ericsson.jira.oslc.provider; + +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corporation. All rights reserved. This program + * and the accompanying materials are made available under the terms of the + * Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 which + * accompanies this distribution. The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html and the Eclipse Distribution + * License is available at http://www.eclipse.org/org/documents/edl-v10.php. + * Contributors: Russell Boykin - initial API and implementation Alberto + * Giammaria - initial API and implementation Chris Peters - initial API and + * implementation Gianluca Bernardini - initial API and implementation Steve + * Pitschke - Add support for FilteredResource and ResponseInfo + *******************************************************************************/ + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Map; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import org.eclipse.lyo.oslc4j.core.model.Error; +import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; +import org.eclipse.lyo.oslc4j.core.model.ResponseInfo; +import org.eclipse.lyo.oslc4j.provider.json4j.AbstractOslcRdfJsonProvider; + +@Provider +@Produces(OslcMediaType.APPLICATION_JSON) +@Consumes(OslcMediaType.APPLICATION_JSON) +public final class OslcRdfJsonErrorProvider extends AbstractOslcRdfJsonProvider implements MessageBodyWriter { + public OslcRdfJsonErrorProvider() { + super(); + } + + @Override + public long getSize(final Error object, final Class type, final Type genericType, final Annotation[] annotation, final MediaType mediaType) { + return -1; + } + + @Override + public boolean isWriteable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { + if (Error.class.isAssignableFrom(type)) { + return isWriteable(type, annotations, OslcMediaType.APPLICATION_JSON_TYPE, mediaType); + } + + return false; + } + + @Override + public void writeTo(final Error object, final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap map, final OutputStream outputStream) throws IOException, WebApplicationException { + Object[] objects; + Map properties = null; + String descriptionURI = null; + String responseInfoURI = null; + ResponseInfo responseInfo = null; + + objects = new Object[] { object }; + + writeTo(objects, mediaType, map, outputStream, properties, descriptionURI, responseInfoURI, responseInfo); + } + +} \ No newline at end of file diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcRdfJsonProvider.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcRdfJsonProvider.java new file mode 100644 index 0000000..04dc430 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcRdfJsonProvider.java @@ -0,0 +1,289 @@ +package com.ericsson.jira.oslc.provider; +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * + * Russell Boykin - initial API and implementation + * Alberto Giammaria - initial API and implementation + * Chris Peters - initial API and implementation + * Gianluca Bernardini - initial API and implementation + * Steve Pitschke - Add support for FilteredResource and + * ResponseInfo + *******************************************************************************/ + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.net.URI; +import java.util.Collection; +import java.util.Map; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import org.eclipse.lyo.oslc4j.core.OSLC4JConstants; +import org.eclipse.lyo.oslc4j.core.OSLC4JUtils; +import org.eclipse.lyo.oslc4j.core.model.AbstractResource; +import org.eclipse.lyo.oslc4j.core.model.FilteredResource; +import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; +import org.eclipse.lyo.oslc4j.core.model.ResponseInfo; +import org.eclipse.lyo.oslc4j.core.model.ResponseInfoArray; +import org.eclipse.lyo.oslc4j.core.model.ResponseInfoCollection; +import org.eclipse.lyo.oslc4j.provider.json4j.AbstractOslcRdfJsonProvider; + +@Provider +@Produces(OslcMediaType.APPLICATION_JSON) +@Consumes(OslcMediaType.APPLICATION_JSON) +public final class OslcRdfJsonProvider + extends AbstractOslcRdfJsonProvider + implements MessageBodyReader, + MessageBodyWriter +{ + public OslcRdfJsonProvider() + { + super(); + } + + @Override + public long getSize(final AbstractResource object, + final Class type, + final Type genericType, + final Annotation[] annotation, + final MediaType mediaType) + { + return -1; + } + + @Override + public boolean isWriteable(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) + { + Class actualType; + + if (FilteredResource.class.isAssignableFrom(type) && + (genericType instanceof ParameterizedType)) + { + ParameterizedType parameterizedType = (ParameterizedType)genericType; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + + if (actualTypeArguments.length != 1) + { + return false; + } + + if (actualTypeArguments[0] instanceof Class) + { + actualType = (Class)actualTypeArguments[0]; + } + else if (actualTypeArguments[0] instanceof ParameterizedType) + { + parameterizedType = (ParameterizedType)actualTypeArguments[0]; + actualTypeArguments = parameterizedType.getActualTypeArguments(); + + if (actualTypeArguments.length != 1 || + ! (actualTypeArguments[0] instanceof Class)) + { + return false; + } + + actualType = (Class)actualTypeArguments[0]; + } + else if (actualTypeArguments[0] instanceof GenericArrayType) + { + GenericArrayType genericArrayType = + (GenericArrayType)actualTypeArguments[0]; + Type componentType = genericArrayType.getGenericComponentType(); + + if (! (componentType instanceof Class)) + { + return false; + } + + actualType = (Class)componentType; + } + else + { + return false; + } + + Type rawType = parameterizedType.getRawType(); + if (URI.class.equals(actualType) + && (ResponseInfoCollection.class.equals(rawType) || ResponseInfoArray.class.equals(rawType))) + { + return true; + } + } + else + { + actualType = type; + } + + if (AbstractResource.class.isAssignableFrom(type)) { + return isWriteable(actualType, annotations, OslcMediaType.APPLICATION_JSON_TYPE, mediaType); + } + + return false; + } + + @Override + public void writeTo(final AbstractResource object, + final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap map, + final OutputStream outputStream) + throws IOException, + WebApplicationException + { + Object[] objects; + Map properties = null; + String descriptionURI = null; + String responseInfoURI = null; + ResponseInfo responseInfo = null; + + if (object instanceof FilteredResource) + { + final FilteredResource filteredResource = + (FilteredResource)object; + + properties = filteredResource.properties(); + + if (filteredResource instanceof ResponseInfo) + { + responseInfo = (ResponseInfo)filteredResource; + + String requestURI = OSLC4JUtils.resolveURI(httpServletRequest, true); + responseInfoURI = requestURI; + + FilteredResource container = responseInfo.getContainer(); + if (container != null) + { + URI containerAboutURI = container.getAbout(); + if (containerAboutURI != null) + { + descriptionURI = containerAboutURI.toString(); + } + } + if (null == descriptionURI) + { + descriptionURI = requestURI; + } + + final String queryString = httpServletRequest.getQueryString(); + + if ((queryString != null) && + (isOslcQuery(queryString))) + { + responseInfoURI += "?" + queryString; + } + + if (filteredResource instanceof ResponseInfoArray) + { + objects = ((ResponseInfoArray)filteredResource).array(); + } + else + { + Collection collection = + ((ResponseInfoCollection)filteredResource).collection(); + + objects = collection.toArray(new Object[collection.size()]); + } + } + else + { + Object nestedObject = filteredResource.resource(); + + if (nestedObject instanceof Object[]) + { + objects = (Object[])nestedObject; + } + else if (nestedObject instanceof Collection) + { + objects = ((Collection)nestedObject).toArray(); + } + else + { + objects = new Object[] { object }; + } + } + } + else + { + boolean isClientSide = false; + try { + httpServletRequest.getMethod(); + } catch (RuntimeException e) { + isClientSide = true; + } + properties = isClientSide ? null : (Map) httpServletRequest.getAttribute(OSLC4JConstants.OSLC4J_SELECTED_PROPERTIES); + + objects = new Object[] { object }; + } + + writeTo(objects, + mediaType, + map, + outputStream, + properties, + descriptionURI, + responseInfoURI, + responseInfo); + } + + @Override + public boolean isReadable(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) + { + return isReadable(type, + OslcMediaType.APPLICATION_JSON_TYPE, + mediaType); + } + + @Override + public AbstractResource readFrom(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap map, + final InputStream inputStream) + throws IOException, + WebApplicationException + { + final AbstractResource[] objects = (AbstractResource[])readFrom(type, + mediaType, + map, + inputStream); + + if ((objects != null) && + (objects.length > 0)) + { + return objects[0]; + } + + return null; + } +} \ No newline at end of file diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcXmlRdfArrayProvider.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcXmlRdfArrayProvider.java new file mode 100644 index 0000000..ef0531f --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcXmlRdfArrayProvider.java @@ -0,0 +1,167 @@ +package com.ericsson.jira.oslc.provider; + +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * + * Russell Boykin - initial API and implementation + * Alberto Giammaria - initial API and implementation + * Chris Peters - initial API and implementation + * Gianluca Bernardini - initial API and implementation + *******************************************************************************/ + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import org.eclipse.lyo.oslc4j.core.annotation.OslcNotQueryResult; +import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; +import org.eclipse.lyo.oslc4j.provider.jena.AbstractOslcRdfXmlProvider; + +@Provider +@Produces({OslcMediaType.APPLICATION_RDF_XML, OslcMediaType.APPLICATION_XML, OslcMediaType.TEXT_XML}) +@Consumes({OslcMediaType.APPLICATION_RDF_XML, OslcMediaType.APPLICATION_XML, OslcMediaType.TEXT_XML}) +public class OslcXmlRdfArrayProvider + extends AbstractOslcRdfXmlProvider + implements MessageBodyReader, + MessageBodyWriter> +{ + public OslcXmlRdfArrayProvider() + { + super(); + } + + @Override + public long getSize(final ResponseArrayWrapper objects, + final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) + { + return -1; + } + + @Override + public boolean isWriteable(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) + { + if(ResponseArrayWrapper.class.isAssignableFrom(type)){ + return + (isWriteable( + annotations, + mediaType, + OslcMediaType.APPLICATION_RDF_XML_TYPE, + OslcMediaType.APPLICATION_XML_TYPE, + OslcMediaType.TEXT_XML_TYPE, + OslcMediaType.TEXT_TURTLE_TYPE)); + } + return false; + } + + protected static boolean isWriteable(final Annotation[] annotations, final MediaType actualMediaType, final MediaType... requiredMediaTypes) { + + // When handling "recursive" writing of an OSLC Error object, we get a + // zero-length array of annotations + if ((annotations != null) && (annotations.length > 0)) { + for (final Annotation annotation : annotations) { + if (annotation instanceof Produces) { + final Produces producesAnnotation = (Produces) annotation; + + for (final String value : producesAnnotation.value()) { + for (final MediaType requiredMediaType : requiredMediaTypes) { + if (requiredMediaType.isCompatible(MediaType.valueOf(value))) { + return true; + } + } + } + + return false; + } + } + + return false; + } + + // We do not have annotations when running from the non-web client. + return isCompatible(actualMediaType, requiredMediaTypes); + } + + @Override + public void writeTo(final ResponseArrayWrapper objects, + final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap map, + final OutputStream outputStream) + throws IOException, + WebApplicationException + { + + OslcNotQueryResult notQueryResult = null; + if(type != null && type.getComponentType() != null){ + notQueryResult = type.getComponentType().getAnnotation(OslcNotQueryResult.class); + } + + Object[] resources =objects.getResource(); + + writeTo(notQueryResult != null && notQueryResult.value() ? false : true, + resources, + mediaType, + map, + outputStream); + } + + @Override + public boolean isReadable(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) + { + return (type.isArray()) && + (isReadable(type.getComponentType(), + mediaType, + OslcMediaType.APPLICATION_RDF_XML_TYPE, + OslcMediaType.APPLICATION_XML_TYPE, + OslcMediaType.TEXT_XML_TYPE, + OslcMediaType.TEXT_TURTLE_TYPE)); + } + + @Override + public Object[] readFrom(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap map, + final InputStream inputStream) + throws IOException, + WebApplicationException + { + return readFrom(type.getComponentType(), + mediaType, + map, + inputStream); + } +} \ No newline at end of file diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcXmlRdfErrorProvider.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcXmlRdfErrorProvider.java new file mode 100644 index 0000000..c492a0e --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcXmlRdfErrorProvider.java @@ -0,0 +1,68 @@ +package com.ericsson.jira.oslc.provider; + +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corporation. All rights reserved. This program + * and the accompanying materials are made available under the terms of the + * Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 which + * accompanies this distribution. The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html and the Eclipse Distribution + * License is available at http://www.eclipse.org/org/documents/edl-v10.php. + * Contributors: Russell Boykin - initial API and implementation Alberto + * Giammaria - initial API and implementation Chris Peters - initial API and + * implementation Gianluca Bernardini - initial API and implementation Steve + * Pitschke - Add support for FilteredResource and ResponseInfo + *******************************************************************************/ + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Map; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import org.eclipse.lyo.oslc4j.core.model.Error; +import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; +import org.eclipse.lyo.oslc4j.core.model.ResponseInfo; +import org.eclipse.lyo.oslc4j.provider.jena.AbstractOslcRdfXmlProvider; + +@Provider +@Produces({ OslcMediaType.APPLICATION_RDF_XML, OslcMediaType.APPLICATION_XML, OslcMediaType.TEXT_XML }) +@Consumes({ OslcMediaType.APPLICATION_RDF_XML, OslcMediaType.APPLICATION_XML, OslcMediaType.TEXT_XML }) +public class OslcXmlRdfErrorProvider extends AbstractOslcRdfXmlProvider implements MessageBodyWriter { + public OslcXmlRdfErrorProvider() { + super(); + } + + @Override + public long getSize(final Error object, final Class type, final Type genericType, final Annotation[] annotation, final MediaType mediaType) { + return -1; + } + + @Override + public boolean isWriteable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { + if (Error.class.isAssignableFrom(type)) { + return isWriteable(type, annotations, mediaType, OslcMediaType.APPLICATION_RDF_XML_TYPE, OslcMediaType.APPLICATION_XML_TYPE, OslcMediaType.TEXT_XML_TYPE, OslcMediaType.TEXT_TURTLE_TYPE); + } + return false; + } + + @Override + public void writeTo(final Error object, final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap map, final OutputStream outputStream) throws IOException, WebApplicationException { + Object[] objects; + Map properties = null; + String descriptionURI = null; + String responseInfoURI = null; + ResponseInfo responseInfo = null; + objects = new Object[] { object }; + + writeTo(objects, mediaType, map, outputStream, properties, descriptionURI, responseInfoURI, responseInfo); + } + +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcXmlRdfProvider.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcXmlRdfProvider.java new file mode 100644 index 0000000..2a98162 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/OslcXmlRdfProvider.java @@ -0,0 +1,289 @@ +package com.ericsson.jira.oslc.provider; + +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * + * Russell Boykin - initial API and implementation + * Alberto Giammaria - initial API and implementation + * Chris Peters - initial API and implementation + * Gianluca Bernardini - initial API and implementation + * Steve Pitschke - Add support for FilteredResource and + * ResponseInfo + *******************************************************************************/ + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Map; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import org.eclipse.lyo.oslc4j.core.OSLC4JConstants; +import org.eclipse.lyo.oslc4j.core.OSLC4JUtils; +import org.eclipse.lyo.oslc4j.core.model.AbstractResource; +import org.eclipse.lyo.oslc4j.core.model.FilteredResource; +import org.eclipse.lyo.oslc4j.core.model.OslcMediaType; +import org.eclipse.lyo.oslc4j.core.model.ResponseInfo; +import org.eclipse.lyo.oslc4j.core.model.ResponseInfoArray; +import org.eclipse.lyo.oslc4j.core.model.ResponseInfoCollection; +import org.eclipse.lyo.oslc4j.provider.jena.AbstractOslcRdfXmlProvider; + +@Provider +@Produces({OslcMediaType.APPLICATION_RDF_XML, OslcMediaType.APPLICATION_XML, OslcMediaType.TEXT_XML}) +@Consumes({OslcMediaType.APPLICATION_RDF_XML, OslcMediaType.APPLICATION_XML, OslcMediaType.TEXT_XML}) +public class OslcXmlRdfProvider + extends AbstractOslcRdfXmlProvider + implements MessageBodyReader, + MessageBodyWriter +{ + public OslcXmlRdfProvider() + { + super(); + } + + @Override + public long getSize(final AbstractResource object, + final Class type, + final Type genericType, + final Annotation[] annotation, + final MediaType mediaType) + { + return -1; + } + + @Override + public boolean isWriteable(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) + { + Class actualType; + + if (FilteredResource.class.isAssignableFrom(type) && + (genericType instanceof ParameterizedType)) + { + ParameterizedType parameterizedType = (ParameterizedType)genericType; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + + if (actualTypeArguments.length != 1) + { + return false; + } + + if (actualTypeArguments[0] instanceof Class) + { + actualType = (Class)actualTypeArguments[0]; + } + else if (actualTypeArguments[0] instanceof ParameterizedType) + { + parameterizedType = (ParameterizedType)actualTypeArguments[0]; + actualTypeArguments = parameterizedType.getActualTypeArguments(); + + if (actualTypeArguments.length != 1 || + ! (actualTypeArguments[0] instanceof Class)) + { + return false; + } + + actualType = (Class)actualTypeArguments[0]; + } + else if (actualTypeArguments[0] instanceof GenericArrayType) + { + GenericArrayType genericArrayType = + (GenericArrayType)actualTypeArguments[0]; + Type componentType = genericArrayType.getGenericComponentType(); + + if (! (componentType instanceof Class)) + { + return false; + } + + actualType = (Class)componentType; + } + else + { + return false; + } + } + else + { + actualType = type; + } + if(AbstractResource.class.isAssignableFrom(type)){ + return isWriteable(actualType, + annotations, + mediaType, + OslcMediaType.APPLICATION_RDF_XML_TYPE, + OslcMediaType.APPLICATION_XML_TYPE, + OslcMediaType.TEXT_XML_TYPE, + OslcMediaType.TEXT_TURTLE_TYPE); + } + return false; + } + + @Override + public void writeTo(final AbstractResource object, + final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap map, + final OutputStream outputStream) + throws IOException, + WebApplicationException + { + Object[] objects; + Map properties = null; + String descriptionURI = null; + String responseInfoURI = null; + ResponseInfo responseInfo = null; + + if (object instanceof FilteredResource) + { + final FilteredResource filteredResource = + (FilteredResource)object; + + properties = filteredResource.properties(); + + if (filteredResource instanceof ResponseInfo) + { + descriptionURI = OSLC4JUtils.resolveURI(httpServletRequest, true); + responseInfoURI = descriptionURI; + + final String queryString = httpServletRequest.getQueryString(); + + if ((queryString != null) && + (isOslcQuery(queryString))) + { + responseInfoURI += "?" + queryString; + } + + if (filteredResource instanceof ResponseInfoArray) + { + objects = ((ResponseInfoArray)filteredResource).array(); + } + else + { + Collection collection = + ((ResponseInfoCollection)filteredResource).collection(); + + objects = collection.toArray(new Object[collection.size()]); + } + + responseInfo = (ResponseInfo)filteredResource; + } + else + { + Object nestedObject = filteredResource.resource(); + + if (nestedObject instanceof Object[]) + { + objects = (Object[])nestedObject; + } + else if (nestedObject instanceof Collection) + { + objects = ((Collection)nestedObject).toArray(); + } + else + { + objects = new Object[] { object }; + } + } + } + else + { + objects = new Object[] { object }; + boolean isClientSide = false; + + try { + httpServletRequest.getMethod(); + } catch (RuntimeException e) { + isClientSide = true; + } + + properties = isClientSide ? null : (Map) httpServletRequest.getAttribute(OSLC4JConstants.OSLC4J_SELECTED_PROPERTIES); + } + + + + writeTo(objects, + mediaType, + map, + outputStream, + properties, + descriptionURI, + responseInfoURI, + responseInfo); + } + + @Override + public boolean isReadable(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) + { + return isReadable(type, + mediaType, + OslcMediaType.APPLICATION_RDF_XML_TYPE, + OslcMediaType.APPLICATION_XML_TYPE, + OslcMediaType.TEXT_XML_TYPE, + OslcMediaType.TEXT_TURTLE_TYPE); + } + + @Override + public AbstractResource readFrom(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap map, + final InputStream inputStream) + throws IOException, + WebApplicationException + { + final AbstractResource[] objects = (AbstractResource[])readFrom(type, + mediaType, + map, + inputStream); + + if ((objects != null) && + (objects.length > 0)) + { + // Fix for defect 412755 + if (OSLC4JUtils.useBeanClassForParsing() && objects.length > 1) { + throw new IOException("Object length should not be greater than 1."); + } + + return objects[0]; + } + + return null; + } + + @Override + public Object[] readFrom(Class type, MediaType mediaType, MultivaluedMap map, InputStream inputStream) throws WebApplicationException { + return super.readFrom(type, mediaType, map, inputStream); + } +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/ResponseArrayWrapper.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/ResponseArrayWrapper.java new file mode 100644 index 0000000..2aea68e --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/provider/ResponseArrayWrapper.java @@ -0,0 +1,48 @@ +package com.ericsson.jira.oslc.provider; + +/* +* Copyright (C) 2015 Ericsson AB. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import org.eclipse.lyo.oslc4j.core.model.AbstractResource; + +/** + * It's a wrapper for our resources which are used by RS MessageWriter. + * It's used for a array of Jira Change Requests + */ +public class ResponseArrayWrapper extends AbstractResource{ + private T[] resource; + + public T[] getResource() { + return resource; + } + + public void setResource(T[] resource) { + this.resource = resource; + } + + +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/resources/ChangeRequest.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/resources/ChangeRequest.java new file mode 100644 index 0000000..ddb26b3 --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/resources/ChangeRequest.java @@ -0,0 +1,871 @@ +/******************************************************************************* + * Copyright (c) 2012 IBM Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * + * Russell Boykin - initial API and implementation + * Alberto Giammaria - initial API and implementation + * Chris Peters - initial API and implementation + * Gianluca Bernardini - initial API and implementation + *******************************************************************************/ +package com.ericsson.jira.oslc.resources; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.eclipse.lyo.oslc4j.core.annotation.OslcAllowedValue; +import org.eclipse.lyo.oslc4j.core.annotation.OslcDescription; +import org.eclipse.lyo.oslc4j.core.annotation.OslcName; +import org.eclipse.lyo.oslc4j.core.annotation.OslcOccurs; +import org.eclipse.lyo.oslc4j.core.annotation.OslcPropertyDefinition; +import org.eclipse.lyo.oslc4j.core.annotation.OslcRange; +import org.eclipse.lyo.oslc4j.core.annotation.OslcReadOnly; +import org.eclipse.lyo.oslc4j.core.annotation.OslcRepresentation; +import org.eclipse.lyo.oslc4j.core.annotation.OslcTitle; +import org.eclipse.lyo.oslc4j.core.annotation.OslcValueType; +import org.eclipse.lyo.oslc4j.core.model.AbstractResource; +import org.eclipse.lyo.oslc4j.core.model.Link; +import org.eclipse.lyo.oslc4j.core.model.Occurs; +import org.eclipse.lyo.oslc4j.core.model.OslcConstants; +import org.eclipse.lyo.oslc4j.core.model.Representation; +import org.eclipse.lyo.oslc4j.core.model.ValueType; + +import com.ericsson.jira.oslc.Constants; + +/** + * It represents OSLC Change Request. It serves as data class which is sending between OSLC system + * It contains the basic parameters of OSLC resource + */ +public class ChangeRequest + extends AbstractResource +{ + private final Set affectedByDefects = new HashSet(); + private final Set affectsPlanItems = new HashSet(); + private final Set affectsRequirements = new HashSet(); + private final Set affectsTestResults = new HashSet(); + private final Set blocksTestExecutionRecords = new HashSet(); + private final List contributors = new ArrayList(); + private final Set dctermsTypes = new TreeSet(); + private final Set implementsRequirements = new HashSet(); + private final Set relatedChangeRequests = new HashSet(); + private final Set relatedResources = new HashSet(); + private final Set relatedTestCases = new HashSet(); + private final Set relatedTestExecutionRecords = new HashSet(); + private final Set relatedTestPlans = new HashSet(); + private final Set relatedTestScripts = new HashSet(); + private final Set subjects = new TreeSet(); + private final Set testedByTestCases = new HashSet(); + private final Set tracksChangeSets = new HashSet(); + private final Set tracksRequirements = new HashSet(); + private final Set rdfTypes = new TreeSet(); + + private Boolean approved; + private Boolean closed; + private Date closeDate; + private Date created; + private String description; + private URI discussedBy; + private Boolean fixed; + private String identifier; + private Boolean inProgress; + private URI instanceShape; + private Date modified; + private Boolean reviewed; + private URI serviceProvider; + private Severity severity; + private String shortTitle; + private String status; + private String title; + private Boolean verified; + + public ChangeRequest() + throws URISyntaxException + { + super(); + + rdfTypes.add(new URI(Constants.TYPE_CHANGE_REQUEST)); + } + + public ChangeRequest(final URI about) + throws URISyntaxException + { + super(about); + + rdfTypes.add(new URI(Constants.TYPE_CHANGE_REQUEST)); + } + + public void addAffectedByDefect(final Link affectedByDefect) + { + this.affectedByDefects.add(affectedByDefect); + } + + public void addAffectsPlanItem(final Link affectsPlanItem) + { + this.affectsPlanItems.add(affectsPlanItem); + } + + public void addAffectsRequirement(final Link affectsRequirement) + { + this.affectsRequirements.add(affectsRequirement); + } + + public void addAffectsTestResult(final Link affectsTestResult) + { + this.affectsTestResults.add(affectsTestResult); + } + + public void addBlocksTestExecutionRecord(final Link blocksTestExecutionRecord) + { + this.blocksTestExecutionRecords.add(blocksTestExecutionRecord); + } + + public void addContributor(final Person contributor) + { + this.contributors.add(contributor); + } + + + public void addDctermsType(final String dctermsType) + { + this.dctermsTypes.add(Type.fromString(dctermsType)); + } + + public void addImplementsRequirement(final Link implementsRequirement) + { + this.implementsRequirements.add(implementsRequirement); + } + + public void addRdfType(final URI rdfType) + { + this.rdfTypes.add(rdfType); + } + + public void addRelatedChangeRequest(final Link relatedChangeRequest) + { + this.relatedChangeRequests.add(relatedChangeRequest); + } + + public void addRelatedResource(final Link relatedResource) + { + this.relatedResources.add(relatedResource); + } + + public void addRelatedTestCase(final Link relatedTestCase) + { + this.relatedTestCases.add(relatedTestCase); + } + + public void addRelatedTestExecutionRecord(final Link relatedTestExecutionRecord) + { + this.relatedTestExecutionRecords.add(relatedTestExecutionRecord); + } + + public void addRelatedTestPlan(final Link relatedTestPlan) + { + this.relatedTestPlans.add(relatedTestPlan); + } + + public void addRelatedTestScript(final Link relatedTestScript) + { + this.relatedTestScripts.add(relatedTestScript); + } + + public void addSubject(final String subject) + { + this.subjects.add(subject); + } + + public void addTestedByTestCase(final Link testedByTestCase) + { + this.testedByTestCases.add(testedByTestCase); + } + + public void addTracksChangeSet(final Link tracksChangeSet) + { + this.tracksChangeSets.add(tracksChangeSet); + } + + public void addTracksRequirement(final Link tracksRequirement) + { + this.tracksRequirements.add(tracksRequirement); + } + + @OslcDescription("Change request is affected by a reported defect.") + @OslcName("affectedByDefect") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "affectedByDefect") + @OslcRange(Constants.TYPE_CHANGE_REQUEST) + @OslcReadOnly(false) + @OslcTitle("Affected By Defects") + public Link[] getAffectedByDefects() + { + return affectedByDefects.toArray(new Link[affectedByDefects.size()]); + } + + @OslcDescription("Change request affects a plan item. ") + @OslcName("affectsPlanItem") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "affectsPlanItem") + @OslcRange(Constants.TYPE_CHANGE_REQUEST) + @OslcReadOnly(false) + @OslcTitle("Affects Plan Items") + public Link[] getAffectsPlanItems() + { + return affectsPlanItems.toArray(new Link[affectsPlanItems.size()]); + } + + @OslcDescription("Change request affecting a Requirement.") + @OslcName("affectsRequirement") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "affectsRequirement") + @OslcRange(Constants.TYPE_REQUIREMENT) + @OslcReadOnly(false) + @OslcTitle("Affects Requirements") + public Link[] getAffectsRequirements() + { + return affectsRequirements.toArray(new Link[affectsRequirements.size()]); + } + + @OslcDescription("Associated QM resource that is affected by this Change Request.") + @OslcName("affectsTestResult") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "affectsTestResult") + @OslcRange(Constants.TYPE_TEST_RESULT) + @OslcReadOnly(false) + @OslcTitle("Affects Test Results") + public Link[] getAffectsTestResults() + { + return affectsTestResults.toArray(new Link[affectsTestResults.size()]); + } + + @OslcDescription("Associated QM resource that is blocked by this Change Request.") + @OslcName("blocksTestExecutionRecord") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "blocksTestExecutionRecord") + @OslcRange(Constants.TYPE_TEST_EXECUTION_RECORD) + @OslcReadOnly(false) + @OslcTitle("Blocks Test Execution Records") + public Link[] getBlocksTestExecutionRecords() + { + return blocksTestExecutionRecords.toArray(new Link[blocksTestExecutionRecords.size()]); + } + + @OslcDescription("The date at which no further activity or work is intended to be conducted. ") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "closeDate") + @OslcReadOnly + @OslcTitle("Close Date") + public Date getCloseDate() + { + return closeDate; + } + + @OslcDescription("The person(s) who are responsible for the work needed to complete the change request.") + @OslcName("contributor") + @OslcPropertyDefinition(OslcConstants.DCTERMS_NAMESPACE + "contributor") + @OslcRepresentation(Representation.Inline) + @OslcValueType(ValueType.LocalResource) + @OslcRange(Constants.TYPE_PERSON) + @OslcTitle("Contributors") + public List getContributors() + { + return contributors; + } + + @OslcDescription("Timestamp of resource creation.") + @OslcPropertyDefinition(OslcConstants.DCTERMS_NAMESPACE + "created") + @OslcReadOnly + @OslcTitle("Created") + public Date getCreated() + { + return created; + } + + + @OslcAllowedValue({"Defect", "Task", "Story", "Bug Report", "Feature Request"}) + @OslcDescription("A short string representation for the type, example 'Defect'.") + @OslcName("type") + @OslcPropertyDefinition(OslcConstants.DCTERMS_NAMESPACE + "type") + @OslcTitle("Types") + public String[] getDctermsTypes() + { + final String[] result = new String[dctermsTypes.size()]; + + int index = 0; + + for (final Type type : dctermsTypes) + { + result[index++] = type.toString(); + } + + return result; + } + + @OslcDescription("Descriptive text (reference: Dublin Core) about resource represented as rich text in XHTML content.") + @OslcPropertyDefinition(OslcConstants.DCTERMS_NAMESPACE + "description") + @OslcTitle("Description") + @OslcValueType(ValueType.XMLLiteral) + public String getDescription() + { + return description; + } + + @OslcDescription("A series of notes and comments about this change request.") + @OslcPropertyDefinition(OslcConstants.OSLC_CORE_NAMESPACE + "discussedBy") + @OslcRange(Constants.TYPE_DISCUSSION) + @OslcTitle("Discussed By") + public URI getDiscussedBy() + { + return discussedBy; + } + + @OslcDescription("A unique identifier for a resource. Assigned by the service provider when a resource is created. Not intended for end-user display.") + @OslcOccurs(Occurs.ExactlyOne) + @OslcPropertyDefinition(OslcConstants.DCTERMS_NAMESPACE + "identifier") + @OslcReadOnly + @OslcTitle("Identifier") + public String getIdentifier() + { + return identifier; + } + + @OslcDescription("Implements associated Requirement.") + @OslcName("implementsRequirement") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "implementsRequirement") + @OslcRange(Constants.TYPE_REQUIREMENT) + @OslcReadOnly(false) + @OslcTitle("Implements Requirements") + public Link[] getImplementsRequirements() + { + return implementsRequirements.toArray(new Link[implementsRequirements.size()]); + } + + @OslcDescription("Resource Shape that provides hints as to resource property value-types and allowed values. ") + @OslcPropertyDefinition(OslcConstants.OSLC_CORE_NAMESPACE + "instanceShape") + @OslcRange(OslcConstants.TYPE_RESOURCE_SHAPE) + @OslcTitle("Instance Shape") + public URI getInstanceShape() + { + return instanceShape; + } + + @OslcDescription("Timestamp last latest resource modification.") + @OslcPropertyDefinition(OslcConstants.DCTERMS_NAMESPACE + "modified") + @OslcReadOnly + @OslcTitle("Modified") + public Date getModified() + { + return modified; + } + + @OslcDescription("The resource type URIs.") + @OslcName("type") + @OslcPropertyDefinition(OslcConstants.RDF_NAMESPACE + "type") + @OslcTitle("Types") + public URI[] getRdfTypes() + { + return rdfTypes.toArray(new URI[rdfTypes.size()]); + } + + @OslcDescription("This relationship is loosely coupled and has no specific meaning.") + @OslcName("relatedChangeRequest") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "relatedChangeRequest") + @OslcRange(Constants.TYPE_CHANGE_REQUEST) + @OslcReadOnly(false) + @OslcTitle("Related Change Requests") + public Link[] getRelatedChangeRequests() + { + return relatedChangeRequests.toArray(new Link[relatedChangeRequests.size()]); + } + + @OslcDescription("Related OSLC resources of any type.") + @OslcName("relatedResource") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "relatedResource") + @OslcTitle("Related Resources") + public Link[] getRelatedResources() + { + return relatedResources.toArray(new Link[relatedResources.size()]); + } + + @OslcDescription("Related QM test case resource.") + @OslcName("relatedTestCase") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "relatedTestCase") + @OslcRange(Constants.TYPE_TEST_CASE) + @OslcReadOnly(false) + @OslcTitle("Related Test Cases") + public Link[] getRelatedTestCases() + { + return relatedTestCases.toArray(new Link[relatedTestCases.size()]); + } + + @OslcDescription("Related to a QM test execution resource.") + @OslcName("relatedTestExecutionRecord") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "relatedTestExecutionRecord") + @OslcRange(Constants.TYPE_TEST_EXECUTION_RECORD) + @OslcReadOnly(false) + @OslcTitle("Related Test Execution Records") + public Link[] getRelatedTestExecutionRecords() + { + return relatedTestExecutionRecords.toArray(new Link[relatedTestExecutionRecords.size()]); + } + + @OslcDescription("Related QM test plan resource.") + @OslcName("relatedTestPlan") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "relatedTestPlan") + @OslcRange(Constants.TYPE_TEST_PLAN) + @OslcReadOnly(false) + @OslcTitle("Related Test Plans") + public Link[] getRelatedTestPlans() + { + return relatedTestPlans.toArray(new Link[relatedTestPlans.size()]); + } + + @OslcDescription("Related QM test script resource.") + @OslcName("relatedTestScript") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "relatedTestScript") + @OslcRange(Constants.TYPE_TEST_SCRIPT) + @OslcReadOnly(false) + @OslcTitle("Related Test Scripts") + public Link[] getRelatedTestScripts() + { + return relatedTestScripts.toArray(new Link[relatedTestScripts.size()]); + } + + @OslcDescription("The scope of a resource is a URI for the resource's OSLC Service Provider.") + @OslcPropertyDefinition(OslcConstants.OSLC_CORE_NAMESPACE + "serviceProvider") + @OslcRange(OslcConstants.TYPE_SERVICE_PROVIDER) + @OslcTitle("Service Provider") + public URI getServiceProvider() + { + return serviceProvider; + } + + @OslcAllowedValue({"Unclassified", "Minor", "Normal", "Major", "Critical", "Blocker"}) + @OslcDescription("Severity of change request.") + @OslcOccurs(Occurs.ExactlyOne) + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "severity") + @OslcTitle("Severity") + public String getSeverity() + { + return (severity == null)?null:severity.toString(); + } + + @OslcDescription("Short name identifying a resource, often used as an abbreviated identifier for presentation to end-users.") + @OslcPropertyDefinition(OslcConstants.OSLC_CORE_NAMESPACE + "shortTitle") + @OslcTitle("Short Title") + @OslcValueType(ValueType.XMLLiteral) + public String getShortTitle() + { + return shortTitle; + } + + @OslcDescription("Used to indicate the status of the change request based on values defined by the service provider. Most often a read-only property. Some possible values may include: 'Submitted', 'Done', 'InProgress', etc.") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "status") + @OslcTitle("Status") + public String getStatus() + { + return status; + } + + @OslcDescription("Tag or keyword for a resource. Each occurrence of a dcterms:subject property denotes an additional tag for the resource.") + @OslcName("subject") + @OslcPropertyDefinition(OslcConstants.DCTERMS_NAMESPACE + "subject") + @OslcReadOnly(false) + @OslcTitle("Subjects") + public String[] getSubjects() + { + return subjects.toArray(new String[subjects.size()]); + } + + @OslcDescription("Test case by which this change request is tested.") + @OslcName("testedByTestCase") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "testedByTestCase") + @OslcRange(Constants.TYPE_TEST_CASE) + @OslcReadOnly(false) + @OslcTitle("Tested by Test Cases") + public Link[] getTestedByTestCases() + { + return testedByTestCases.toArray(new Link[testedByTestCases.size()]); + } + + @OslcDescription("Title (reference: Dublin Core) or often a single line summary of the resource represented as rich text in XHTML content.") + @OslcOccurs(Occurs.ExactlyOne) + @OslcPropertyDefinition(OslcConstants.DCTERMS_NAMESPACE + "title") + @OslcTitle("Title") + @OslcValueType(ValueType.XMLLiteral) + public String getTitle() + { + return title; + } + + @OslcDescription("Tracks SCM change set resource.") + @OslcName("tracksChangeSet") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "tracksChangeSet") + @OslcRange(Constants.TYPE_CHANGE_SET) + @OslcReadOnly(false) + @OslcTitle("Tracks Change Sets") + public Link[] getTracksChangeSets() + { + return tracksChangeSets.toArray(new Link[tracksChangeSets.size()]); + } + + @OslcDescription("Tracks the associated Requirement or Requirement ChangeSet resources.") + @OslcName("tracksRequirement") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "tracksRequirement") + @OslcRange(Constants.TYPE_REQUIREMENT) + @OslcReadOnly(false) + @OslcTitle("Tracks Requirements") + public Link[] getTracksRequirements() + { + return tracksRequirements.toArray(new Link[tracksRequirements.size()]); + } + + @OslcDescription("Whether or not the Change Request has been approved.") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "approved") + @OslcReadOnly + @OslcTitle("Approved") + public Boolean isApproved() + { + return approved; + } + + @OslcDescription("Whether or not the Change Request is completely done, no further fixes or fix verification is needed.") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "closed") + @OslcReadOnly + @OslcTitle("Closed") + public Boolean isClosed() + { + return closed; + } + + @OslcDescription("Whether or not the Change Request has been fixed.") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "fixed") + @OslcReadOnly + @OslcTitle("Fixed") + public Boolean isFixed() + { + return fixed; + } + + @OslcDescription("Whether or not the Change Request in a state indicating that active work is occurring. If oslc_cm:inprogress is true, then oslc_cm:fixed and oslc_cm:closed must also be false.") + @OslcName("inprogress") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "inprogress") + @OslcReadOnly + @OslcTitle("In Progress") + public Boolean isInProgress() + { + return inProgress; + } + + @OslcDescription("Whether or not the Change Request has been reviewed.") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "reviewed") + @OslcReadOnly + @OslcTitle("Reviewed") + public Boolean isReviewed() + { + return reviewed; + } + + @OslcDescription("Whether or not the resolution or fix of the Change Request has been verified.") + @OslcPropertyDefinition(Constants.CHANGE_MANAGEMENT_NAMESPACE + "verified") + @OslcReadOnly + @OslcTitle("Verified") + public Boolean isVerified() + { + return verified; + } + + public void setAffectedByDefects(final Link[] affectedByDefects) + { + this.affectedByDefects.clear(); + + if (affectedByDefects != null) + { + this.affectedByDefects.addAll(Arrays.asList(affectedByDefects)); + } + } + + public void setAffectsPlanItems(final Link[] affectsPlanItems) + { + this.affectsPlanItems.clear(); + + if (affectsPlanItems != null) + { + this.affectsPlanItems.addAll(Arrays.asList(affectsPlanItems)); + } + } + + public void setAffectsRequirements(final Link[] affectsRequirements) + { + this.affectsRequirements.clear(); + + if (affectsRequirements != null) + { + this.affectsRequirements.addAll(Arrays.asList(affectsRequirements)); + } + } + + public void setAffectsTestResults(final Link[] affectsTestResults) + { + this.affectsTestResults.clear(); + + if (affectsTestResults != null) + { + this.affectsTestResults.addAll(Arrays.asList(affectsTestResults)); + } + } + + public void setApproved(final Boolean approved) + { + this.approved = approved; + } + + public void setBlocksTestExecutionRecords(final Link[] blocksTestExecutionRecords) + { + this.blocksTestExecutionRecords.clear(); + + if (blocksTestExecutionRecords != null) + { + this.blocksTestExecutionRecords.addAll(Arrays.asList(blocksTestExecutionRecords)); + } + } + + public void setClosed(final Boolean closed) + { + this.closed = closed; + } + + public void setCloseDate(final Date closeDate) + { + this.closeDate = closeDate; + } + + public void setContributors(final List contributors) + { + this.contributors.clear(); + + if (contributors != null) + { + this.contributors.addAll(contributors); + } + } + + public void setCreated(final Date created) + { + this.created = created; + } + + + public void setDctermsTypes(final String[] dctermsTypes) + { + this.dctermsTypes.clear(); + + if (dctermsTypes != null) + { + for (final String type : dctermsTypes) + { + this.dctermsTypes.add(Type.fromString(type)); + } + } + } + + public void setDescription(final String description) + { + this.description = description; + } + + public void setDiscussedBy(final URI discussedBy) + { + this.discussedBy = discussedBy; + } + + public void setFixed(final Boolean fixed) + { + this.fixed = fixed; + } + + public void setIdentifier(final String identifier) + { + this.identifier = identifier; + } + + public void setImplementsRequirements(final Link[] implementsRequirements) + { + this.implementsRequirements.clear(); + + if (implementsRequirements != null) + { + this.implementsRequirements.addAll(Arrays.asList(implementsRequirements)); + } + } + + public void setInProgress(final Boolean inProgress) + { + this.inProgress = inProgress; + } + + public void setInstanceShape(final URI instanceShape) + { + this.instanceShape = instanceShape; + } + + public void setModified(final Date modified) + { + this.modified = modified; + } + + public void setRdfTypes(final URI[] rdfTypes) + { + this.rdfTypes.clear(); + + if (rdfTypes != null) + { + this.rdfTypes.addAll(Arrays.asList(rdfTypes)); + } + } + + public void setRelatedChangeRequests(final Link[] relatedChangeRequests) + { + this.relatedChangeRequests.clear(); + + if (relatedChangeRequests != null) + { + this.relatedChangeRequests.addAll(Arrays.asList(relatedChangeRequests)); + } + } + + public void setRelatedResources(final Link[] relatedResources) + { + this.relatedResources.clear(); + + if (relatedResources != null) + { + this.relatedResources.addAll(Arrays.asList(relatedResources)); + } + } + + public void setRelatedTestCases(final Link[] relatedTestCases) + { + this.relatedTestCases.clear(); + + if (relatedTestCases != null) + { + this.relatedTestCases.addAll(Arrays.asList(relatedTestCases)); + } + } + + public void setRelatedTestExecutionRecords(final Link[] relatedTestExecutionRecords) + { + this.relatedTestExecutionRecords.clear(); + + if (relatedTestExecutionRecords != null) + { + this.relatedTestExecutionRecords.addAll(Arrays.asList(relatedTestExecutionRecords)); + } + } + + public void setRelatedTestPlans(final Link[] relatedTestPlans) + { + this.relatedTestPlans.clear(); + + if (relatedTestPlans != null) + { + this.relatedTestPlans.addAll(Arrays.asList(relatedTestPlans)); + } + } + + public void setRelatedTestScripts(final Link[] relatedTestScripts) + { + this.relatedTestScripts.clear(); + + if (relatedTestScripts != null) + { + this.relatedTestScripts.addAll(Arrays.asList(relatedTestScripts)); + } + } + + public void setReviewed(final Boolean reviewed) + { + this.reviewed = reviewed; + } + + public void setServiceProvider(final URI serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + public void setSeverity(final String severity) + { + this.severity = Severity.valueOf(severity); + } + + public void setShortTitle(final String shortTitle) + { + this.shortTitle = shortTitle; + } + + public void setStatus(final String status) + { + this.status = status; + } + + public void setSubjects(final String[] subjects) + { + this.subjects.clear(); + + if (subjects != null) + { + this.subjects.addAll(Arrays.asList(subjects)); + } + } + + public void setTestedByTestCases(final Link[] testedByTestCases) + { + this.testedByTestCases.clear(); + + if (testedByTestCases != null) + { + this.testedByTestCases.addAll(Arrays.asList(testedByTestCases)); + } + } + + public void setTitle(final String title) + { + this.title = title; + } + + public void setTracksChangeSets(final Link[] tracksChangeSets) + { + this.tracksChangeSets.clear(); + + if (tracksChangeSets != null) + { + this.tracksChangeSets.addAll(Arrays.asList(tracksChangeSets)); + } + } + + public void setTracksRequirements(final Link[] tracksRequirements) + { + this.tracksRequirements.clear(); + + if (tracksRequirements != null) + { + this.tracksRequirements.addAll(Arrays.asList(tracksRequirements)); + } + } + + public void setVerified(final Boolean verified) + { + this.verified = verified; + } + +} diff --git a/oslcjira/src/main/java/com/ericsson/jira/oslc/resources/JiraChangeRequest.java b/oslcjira/src/main/java/com/ericsson/jira/oslc/resources/JiraChangeRequest.java new file mode 100644 index 0000000..d93e8ff --- /dev/null +++ b/oslcjira/src/main/java/com/ericsson/jira/oslc/resources/JiraChangeRequest.java @@ -0,0 +1,1077 @@ +/******************************************************************************* + * Copyright (c) 2012 IBM Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * + * Sam Padgett - initial API and implementation + * Michael Fiedler - adapted for OSLC4J + * + *******************************************************************************/ +package com.ericsson.jira.oslc.resources; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.lyo.oslc4j.core.annotation.OslcDescription; +import org.eclipse.lyo.oslc4j.core.annotation.OslcName; +import org.eclipse.lyo.oslc4j.core.annotation.OslcNamespace; +import org.eclipse.lyo.oslc4j.core.annotation.OslcOccurs; +import org.eclipse.lyo.oslc4j.core.annotation.OslcPropertyDefinition; +import org.eclipse.lyo.oslc4j.core.annotation.OslcReadOnly; +import org.eclipse.lyo.oslc4j.core.annotation.OslcResourceShape; +import org.eclipse.lyo.oslc4j.core.annotation.OslcTitle; +import org.eclipse.lyo.oslc4j.core.model.Link; +import org.eclipse.lyo.oslc4j.core.model.Occurs; +import org.eclipse.lyo.oslc4j.core.model.OslcConstants; + +import com.atlassian.jira.bc.project.component.ProjectComponent; +import com.atlassian.jira.component.ComponentAccessor; +import com.atlassian.jira.issue.CustomFieldManager; +import com.atlassian.jira.issue.Issue; +import com.atlassian.jira.issue.MutableIssue; +import com.atlassian.jira.issue.comments.Comment; +import com.atlassian.jira.issue.comments.CommentManager; +import com.atlassian.jira.issue.fields.CustomField; +import com.atlassian.jira.issue.issuetype.IssueType; +import com.atlassian.jira.issue.label.Label; +import com.atlassian.jira.issue.link.IssueLink; +import com.atlassian.jira.issue.link.IssueLinkManager; +import com.atlassian.jira.issue.link.IssueLinkType; +import com.atlassian.jira.issue.link.RemoteIssueLink; +import com.atlassian.jira.issue.link.RemoteIssueLinkManager; +import com.atlassian.jira.issue.priority.Priority; +import com.atlassian.jira.issue.resolution.Resolution; +import com.atlassian.jira.issue.status.Status; +import com.atlassian.jira.issue.vote.VoteManager; +import com.atlassian.jira.issue.watchers.WatcherManager; +import com.atlassian.jira.issue.worklog.Worklog; +import com.atlassian.jira.issue.worklog.WorklogManager; +import com.atlassian.jira.project.Project; +import com.atlassian.jira.project.version.Version; +import com.atlassian.jira.user.ApplicationUser; +import com.ericsson.eif.leansync.mapping.data.ActionType; +import com.ericsson.eif.leansync.mapping.data.SyncConfiguration; +import com.ericsson.jira.oslc.Constants; +import com.ericsson.jira.oslc.constants.JiraConstants; +import com.ericsson.jira.oslc.managers.FieldManager; +import com.ericsson.jira.oslc.managers.JiraManager; +import com.ericsson.jira.oslc.sync.InboundSyncUtils; +import com.ericsson.jira.oslc.utils.AppLink; +import com.ericsson.jira.oslc.utils.AppLinksRepository; +import com.ericsson.jira.oslc.utils.JiraIssueInputParameters; +import com.ericsson.jira.oslc.utils.OSLCUtils; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * It represents OSLC JIRA Change Request. It serves as data class which is sending between OSLC system + * It extended attributes beyond OSLC base ChangeRequest + */ +@OslcNamespace(Constants.CHANGE_MANAGEMENT_NAMESPACE) +@OslcName(Constants.CHANGE_REQUEST) +@OslcResourceShape(title = "Change Request Resource Shape", describes = Constants.TYPE_CHANGE_REQUEST) +public final class JiraChangeRequest extends ChangeRequest { + private String project = null; + private List components = new ArrayList(); + private List affects_versions = new ArrayList(); + private List fix_versions = new ArrayList(); + private JiraIssuePriority priority = null; + private JiraIssueResolution resolution = null; + private String environment = null; + private String reporter = null; + private String assignee = null; + private String creator = null; + private Long projectId = null; + private Date resolutionDate = null; + private JiraIssueType issueType = null; + private String dueDate = null; + private Long originalEstimate; + private Long remainingEstimate; + private Long loggedHours; + private Set labels = new HashSet(); + private List subTasks = new ArrayList(); + private List comments = new ArrayList(); + private List worklogs = new ArrayList(); + private List voters = new ArrayList(); + private List watchers = new ArrayList(); + private JiraIssueHistory history = null; + private List insideLinks = new ArrayList(); + private List outsideLinks = new ArrayList(); + private JiraIssueStatus jiraStatus; + private List customFields = new ArrayList(); + + public JiraChangeRequest() throws URISyntaxException { + super(); + } + + public JiraChangeRequest(URI about) throws URISyntaxException { + super(about); + } + + // issue type + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "issueType") + @OslcName("issueType") + @OslcTitle("Issue Type") + public JiraIssueType getIssueType() { + return issueType; + } + + public void setIssueType(JiraIssueType issueType) { + this.issueType = issueType; + } + + //comments + @OslcDescription("The Jira product definition for this change request.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "component") + @OslcName("component") + @OslcReadOnly(false) + @OslcTitle("Jira Issue Components") + public List getComponents() { + return this.components; + } + + public void setComponents(List components) { + this.components = components; + } + + @OslcDescription("The Jira affects version for this change request.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "affectsVersion") + @OslcName("affectsVersion") + @OslcReadOnly(false) + @OslcTitle("AffectsVersion") + public List getAffectsVersions() { + return affects_versions; + } + + public void setAffectsVersions(List versions) { + this.affects_versions = versions; + } + + //fix version + @OslcDescription("The Jira fix version for this change request.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "fixVersion") + @OslcName("fixVersion") + @OslcReadOnly(false) + @OslcTitle("FixVersion") + public List getFixVersions() { + return fix_versions; + } + + public void setFixVersions(List versions) { + this.fix_versions = versions; + } + + //priority + @OslcDescription("The Jira priority for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "issuePriority") + @OslcName("issuePriority") + @OslcTitle("Jira priority") + public JiraIssuePriority getPriority() { + return priority; + } + + public void setPriority(JiraIssuePriority priority) { + this.priority = priority; + } + + //resolution + @OslcDescription("The Jira resolution for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "resolution") + @OslcTitle("Resolution") + public JiraIssueResolution getResolution() { + return resolution; + } + + public void setResolution(JiraIssueResolution resolution) { + this.resolution = resolution; + } + + //reporter + @OslcDescription("The Jira reporter for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "reporter") + @OslcTitle("Reporter") + public String getReporter() { + return reporter; + } + + public void setReporter(String reporter) { + this.reporter = reporter; + } + + //assignee + @OslcDescription("The Jira assignee for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "assignee") + @OslcTitle("Assignee") + public String getAssignee() { + return assignee; + } + + public void setAssignee(String assignee) { + this.assignee = assignee; + } + + //creator + @OslcDescription("The Jira creator for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcReadOnly + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "creator") + @OslcTitle("Creator") + public String getCreator() { + return creator; + } + + public void setCreator(String creator) { + this.creator = creator; + } + + //project + @OslcDescription("The Jira project for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcReadOnly + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "project") + @OslcTitle("Project") + public String getProject() { + return project; + } + + public void setProject(String project) { + this.project = project; + } + + //environment + @OslcDescription("The Jira environment for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcReadOnly + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "environment") + @OslcTitle("Environment") + public String getEnvironment() { + return environment; + } + + public void setEnvironment(String environment) { + this.environment = environment; + } + + //project id + @OslcDescription("The Jira project ID for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcReadOnly + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "projectId") + @OslcTitle("Project ID") + public Long getProjectId() { + return projectId; + } + + public void setProjectId(Long projectId) { + this.projectId = projectId; + } + + //resolution date + @OslcDescription("Date when the issue has been resolved") + @OslcPropertyDefinition(OslcConstants.DCTERMS_NAMESPACE + "resolutionDate") + @OslcReadOnly + @OslcTitle("Resolution date") + public Date getResolutionDate() { + return resolutionDate; + } + + public void setResolutionDate(Date resolutionDate) { + this.resolutionDate = resolutionDate; + } + + //due date + @OslcDescription("Date when the issue should be resolved") + @OslcPropertyDefinition(OslcConstants.DCTERMS_NAMESPACE + "dueDate") + @OslcReadOnly + @OslcTitle("Due date") + public String getDueDate() { + return dueDate; + } + + public void setDueDate(String dueDate) { + this.dueDate = dueDate; + } + + //original estimate + @OslcDescription("The Jira original estimate for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcReadOnly + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "originalEstimate") + @OslcTitle("Original estimate") + public Long getOriginalEstimate() { + return this.originalEstimate; + } + + public void setOriginalEstimate(Long value) { + this.originalEstimate = value; + } + + //remaining estimate + @OslcDescription("The Jira original estimate for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcReadOnly(false) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "remainingEstimate") + @OslcTitle("Remaining estimate") + public Long getRemainingEstimate() { + return this.remainingEstimate; + } + + public void setRemainingEstimate(Long value) { + this.remainingEstimate = value; + } + + //logged time + @OslcDescription("The Jira time spent for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcReadOnly + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "timeSpent") + @OslcTitle("Time spent") + public Long getTimeSpent() { + return this.loggedHours; + } + + public void setTimeSpent(Long value) { + this.loggedHours = value; + } + + //labels + @OslcDescription("The Jira label for this change request.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "label") + @OslcName("label") + @OslcTitle("Label") + public Set getLabels() { + return this.labels; + } + + public void setLabels(Set labels) { + this.labels = labels; + } + + //sub-tasks (just links to sub-tasks) + @OslcDescription("The Jira sub-task for this change request.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "subTask") + @OslcName("subTask") + @OslcTitle("Sub-Task") + public List getSubTasks() { + return this.subTasks; + } + + public void setSubTasks(List subtask) { + this.subTasks = subtask; + } + + //comments + @OslcDescription("The Jira comment for this change request.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "issueComment") + @OslcName("issueComment") + @OslcTitle("Comment") + public List getIssueComments() { + return this.comments; + } + + public void setIssueComments(List comments) { + this.comments = comments; + } + + //worklogs + @OslcDescription("The Jira worklog for this change request.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "issueWorklog") + @OslcName("issueWorklog") + @OslcTitle("Worklog") + public List getIssueWorklogs() { + return this.worklogs; + } + + public void setIssueWorklogs(List worklogs) { + this.worklogs = worklogs; + } + + //voting + @OslcDescription("The voters for this change request.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "voter") + @OslcName("voter") + @OslcTitle("Voter") + public List getVoters() { + return this.voters; + } + + public void setVoters(List voters) { + this.voters = voters; + } + + //watchers + @OslcDescription("The watchers for this change request.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "watcher") + @OslcName("watcher") + @OslcTitle("Watcher") + public List getWatchers() { + return this.watchers; + } + + public void setWatchers(List watchers) { + this.watchers = watchers; + } + + @OslcDescription("The Jira history item for this change request.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "history") + @OslcName("history") + @OslcTitle("Issue history") + public JiraIssueHistory getIssueHistory() { + return this.history; + } + + public void setIssueHistory(JiraIssueHistory history) { + this.history = history; + } + + //jira classin links (inside, outside) + @OslcDescription("Link to another issue inside Jira.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "jiraInsideLink") + @OslcName("jiraInsideLink") + @OslcTitle("Jira inside link") + public List getJiraInsideLinks() { + return this.insideLinks; + } + + public void setJiraInsideLinks(List links) { + this.insideLinks = links; + } + + @OslcDescription("Web link outside of Jira.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "jiraOutsideLink") + @OslcName("jiraOutsideLink") + @OslcTitle("Jira outside link") + public List getJiraOutsideLinks() { + return this.outsideLinks; + } + + public void setJiraOutsideLinks(List links) { + this.outsideLinks = links; + } + + //status + @OslcDescription("The Jira status for this change request.") + @OslcOccurs(Occurs.ZeroOrOne) + @OslcPropertyDefinition(Constants.JIRA_NAMESPACE + "issueStatus") + @OslcName("issueStatus") + @OslcTitle("Jira issue status") + public JiraIssueStatus getIssueStatus() { + return this.jiraStatus; + } + + public void setIssueStatus(JiraIssueStatus status) { + this.jiraStatus = status; + } + + //custom fields + @OslcDescription("The Jira custom field for this change request.") + @OslcOccurs(Occurs.ZeroOrMany) + @OslcPropertyDefinition(Constants.JIRA_TYPE_CUSTOM_FIELD) + @OslcName("customField") + @OslcTitle("Custom field") + public List getCustomFields() { + return this.customFields; + } + + public void setCustomFields(List customFields) { + this.customFields = customFields; + } + + /** + * Converts a {@link Issue} to an OSLC-CM JiraChangeRequest. + * + * @param Issue + * @return the ChangeRequest to be serialized + * @throws URISyntaxException + * on errors setting the bug URI + * @throws UnsupportedEncodingException + */ + public static JiraChangeRequest fromJiraIssue(MutableIssue issue) throws URISyntaxException { + JiraChangeRequest jcr = new JiraChangeRequest(); + jcr.setIdentifier(issue.getKey()); + + jcr.setTitle(issue.getSummary()); + jcr.setShortTitle(issue.getKey()); + jcr.setDescription(issue.getDescription()); + + //status + Status status = issue.getStatusObject(); + if (status != null){ + JiraIssueStatus jStatus = new JiraIssueStatus(); + jStatus.setAbout( + new URI( + JiraManager.getRestUrl() + JiraConstants.ISSUE_STATUS_PATH + issue.getKey() + + "/" + status.getId())); + jcr.setIssueStatus(jStatus); + } + + //issue type + IssueType issueType = issue.getIssueTypeObject(); + if(issueType != null){ + String id = issueType.getId(); + JiraIssueType jiraIssueType = new JiraIssueType(); + jiraIssueType.setAbout(new URI(JiraManager.getRestUrl() + JiraConstants.ISSUE_TYPE_PATH + id)); + JiraIssueType[] issueTypes = new JiraIssueType[1]; + issueTypes[0] = jiraIssueType; + jcr.setIssueType(issueTypes[0]); + } + + //created date + jcr.setCreated(issue.getCreated()); + + //modified date + jcr.setModified(issue.getUpdated()); + + //resolution date + jcr.setResolutionDate(issue.getResolutionDate()); + + //resolution + Resolution resolution = issue.getResolutionObject(); + if (resolution == null) { + jcr.setResolution(null); + } + else { + JiraIssueResolution jir = new JiraIssueResolution(); + jir.setAbout(new URI(JiraManager.getRestUrl() + JiraConstants.ISSUE_RESOLUTION_PATH + resolution.getId())); + jcr.setResolution(jir); + } + + //reporter + jcr.setReporter(issue.getReporterId()); + + //assignee + jcr.setAssignee(issue.getAssigneeId()); + + //creator + jcr.setCreator(issue.getCreatorId()); + + //project + Project project = issue.getProjectObject(); + if(project != null){ + String projectKey = project.getKey(); + jcr.setProject(projectKey); + jcr.setProjectId(project.getId()); + } + + //environment + jcr.setEnvironment(issue.getEnvironment()); + + //external (OSLC) links -> oslc_cm:relatedChangeRequest + //Note: field for related change requests is in base class ChangeRequest + CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager(); + CustomField customField = customFieldManager.getCustomFieldObjectByName(JiraConstants.OSLC_CUSTOM_FIELD_NAME); + if (customField != null) { + String value = (String) customField.getValue(issue); + Link[] links = OSLCUtils.convertToLinks(value); + jcr.setRelatedChangeRequests(links); + } + + //priority + Priority priority = issue.getPriorityObject(); + if (priority != null) { + JiraIssuePriority jiraIssuePriority = new JiraIssuePriority(); + jiraIssuePriority.setAbout( + new URI(JiraManager.getRestUrl() + JiraConstants.ISSUE_PRIORITY_PATH + priority.getId())); + jcr.setPriority(jiraIssuePriority); + } + + //components + Collection cc = issue.getComponentObjects(); + List components = new ArrayList(); + for (ProjectComponent pc : cc) { + components.add(pc.getName()); + } + jcr.setComponents(components); + + //affects versions + Collection avc = issue.getAffectedVersions(); + List avl = new ArrayList(); + for (Version v : avc) { + avl.add(v.getName()); + } + jcr.setAffectsVersions(avl); + + //fix versions + Collection fvc = issue.getFixVersions(); + List fvl = new ArrayList(); + for (Version v : fvc) { + fvl.add(v.getName()); + } + jcr.setFixVersions(fvl); + + //duedate + Date dd = issue.getDueDate(); + SimpleDateFormat formatter = new SimpleDateFormat("d/MMM/yy"); + if (dd != null) { + jcr.setDueDate(formatter.format(dd)); + } + + //original estimate + Long oe = issue.getOriginalEstimate(); //seconds !!! + if (oe != null) { + oe /= 60; //to minutes + } + jcr.setOriginalEstimate(oe); + + //remaining time + Long re = issue.getEstimate(); //seconds !!! + if (re != null) { + re /= 60; //to minutes + } + jcr.setRemainingEstimate(re); + + //logged hours + Long lh = issue.getTimeSpent(); //seconds !!! + if (lh != null) { + lh /= 60; //to minutes + } + jcr.setTimeSpent(lh); + + //labels + Set