Java tutorial
/** * Copyright 2010 Vstra Gtalandsregionen * <p> * This library is free software; you can redistribute it and/or modify * it under the terms of version 2.1 of the GNU Lesser General Public * License as published by the Free Software Foundation. * <p> * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * <p> * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA */ package se.vgregion.usdservice; import com.ca.www.UnicenterServicePlus.ServiceDesk.ArrayOfString; import com.ca.www.UnicenterServicePlus.ServiceDesk.USDWebServiceSoap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; import org.springframework.util.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import se.vgregion.usdservice.domain.Issue; import se.vgregion.util.Attachment; import javax.activation.DataHandler; import javax.activation.DataSource; import javax.mail.util.ByteArrayDataSource; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.ws.Holder; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Properties; /** * @author David Rosell - Redpill-Linpro */ public class USDServiceImpl implements USDService { private URL endPoint; private String wsUser; private String wsPassword; private String wsAttachmentRepHandle; private USDWebServiceSoap ws; private static final Log log = LogFactory.getLog(USDServiceImpl.class); private Properties usdAppToGroupMappings; // Define which attributes to fetch when retrieving contact's issue list private static final List<String> ATTRIBUTE_NAMES = Arrays.asList("description", "summary", "status.sym", "ref_num", "web_url", "type"); private static final List<String> ATTRIBUTE_NAMES_CHG = Arrays.asList("description", "summary", "status.sym", "chg_ref_num", "web_url"); private static final String TYPE_CHANGE_ORDER = "C"; public USDServiceImpl(Properties p) { String sEndPoint = p.getProperty("endpoint"); try { this.endPoint = new URL(sEndPoint); } catch (MalformedURLException e) { throw new RuntimeException("URL not found:" + sEndPoint, e); } this.wsUser = p.getProperty("user"); this.wsPassword = p.getProperty("password"); this.wsAttachmentRepHandle = p.getProperty("repositoryHandle"); } public Properties getUsdAppToGroupMappings() { return usdAppToGroupMappings; } public void setUsdAppToGroupMappings(Properties appToGroupMappings) { this.usdAppToGroupMappings = appToGroupMappings; } /* * (non-Javadoc) * * @see se.vgregion.usdservice.USDService#lookupIssues(java.lang.String, java.lang.Integer) */ @Override public List<Issue> lookupIssues(String userId, int maxRows, boolean includeGroups) { int sessionID = 0; try { sessionID = getUSDWebService().login(wsUser, wsPassword); String contactHandle = lookupContactHandle(userId, sessionID); if (contactHandle == null) { // User not registered in USD, return null return null; } List<Issue> records = new ArrayList<Issue>(); // Change Orders - Assignee records.addAll(getChangeOrdersForAssignee(sessionID, contactHandle, maxRows)); // Incidents, Problems, Requests - Assignee records.addAll(getRequestForAssignee(sessionID, contactHandle, maxRows)); // Change Orders - Affected records.addAll(getChangeOrdersForContact(sessionID, contactHandle, maxRows)); // Incidents, Problems, Requests - Affected records.addAll(getRequestForContact(sessionID, contactHandle, maxRows)); // Group issues - not assigen to user if (includeGroups) { String groups = lookupUserGroups(sessionID, contactHandle); if (groups.length() > 0) { records.addAll(getChangeOrdersForContactByGroup(sessionID, contactHandle, groups, maxRows)); records.addAll(getRequestForContactByGroup(sessionID, contactHandle, groups, maxRows)); } } List<Issue> listWithoutDuplicates = new ArrayList<Issue>(); for (Issue record : records) { if (!listWithoutDuplicates.contains(record)) { listWithoutDuplicates.add(record); } } return listWithoutDuplicates; } catch (RemoteException e) { log.error("Failed to get issue list from USD Service for user=" + userId, e); throw new RuntimeException(e); } finally { getUSDWebService().logout(sessionID); } } /* TODO: Cannot upload attachments */ @Override public String createRequest(Properties requestParameters, String userId, Collection<Attachment> attachments) { int sessionID = 0; try { sessionID = getUSDWebService().login(wsUser, wsPassword); String contactHandle = lookupContactHandle(userId, sessionID); if (contactHandle == null) { // Use the wsUser as fallback if the user is unknown contactHandle = lookupContactHandle(wsUser, sessionID); } requestParameters.setProperty("customer", contactHandle); List<String> lAttributes = new ArrayList<String>(); List<String> lAttributeValues = new ArrayList<String>(); for (Enumeration<Object> e = requestParameters.keys(); e.hasMoreElements();) { String key = (String) e.nextElement(); lAttributes.add(key); lAttributeValues.add(key); lAttributeValues.add(requestParameters.getProperty(key)); } List<String> properties = Collections.<String>emptyList(); Holder<String> reqHandle = new Holder<String>(""); Holder<String> reqNumber = new Holder<String>(""); String template = ""; Holder<String> result = new Holder<String>(); getUSDWebService().createRequest(sessionID, contactHandle, toArrayOfString(lAttributeValues), toArrayOfString(properties), template, toArrayOfString(lAttributes), reqHandle, reqNumber, result); String handle = null; try { handle = extractHandle(result.toString()); } catch (Exception e) { throw new RuntimeException("Error parsing handle to USD incident from xml response...\n" + result, e); } if (!StringUtils.isEmpty(handle)) { for (Attachment attachment : attachments) { int i = 0; try { createAttachment(sessionID, wsAttachmentRepHandle, handle, "Attachment " + i, attachment); } catch (Exception e) { log.error("Failed to create attachment in USD [" + attachment.getFilename() + "]"); } i++; } } return result.toString(); } finally { getUSDWebService().logout(sessionID); } } /** * BOPSID is a temporary single signon id. * <p> * This has to be used rather quickly before it is invalidated. * * @param userId, the login name of the user to be signed in. * @return BOPSID. */ @Override public String getBopsId(String userId) { int sessionID = 0; try { sessionID = getUSDWebService().login(wsUser, wsPassword); return getUSDWebService().getBopsid(sessionID, userId); } finally { getUSDWebService().logout(sessionID); } } @Override public String getUSDGroupHandleForApplicationName(String appName) { String usdGroupName = usdAppToGroupMappings.getProperty(appName); String result = getGroupHandle(usdGroupName); if (result == null || result.length() == 0) { throw new RuntimeException("No group handle found for application name=" + appName); } return result; } /** * USD-WS lookup. */ private List<Issue> getRequestForContact(int sessionID, String contactHandle, int maxRows) throws RemoteException { // Build where clause String whereClause = String.format("customer = U'%1$s' AND assignee <> U'%1$s' AND active = 1", contactHandle); // Get list xml String listXml = getUSDWebService().doSelect(sessionID, "cr", whereClause, maxRows, toArrayOfString(ATTRIBUTE_NAMES)); // Parse xml to list return parseIssues(listXml, null, "U"); } /** * USD-WS lookup. */ private List<Issue> getRequestForAssignee(int sessionID, String contactHandle, int maxRows) throws RemoteException { // Build where clause String whereClause = String.format("assignee = U'%s' AND active = 1", contactHandle); // Get list xml String listXml = getUSDWebService().doSelect(sessionID, "cr", whereClause, maxRows, toArrayOfString(ATTRIBUTE_NAMES)); // Parse xml to list return parseIssues(listXml, null, "A"); } /** * USD-WS lookup. */ private List<Issue> getRequestForContactByGroup(int sessionID, String contactHandle, String groups, int maxRows) throws RemoteException { // Build where clause String whereClause = String.format( "group in (%1$s) AND customer <> U'%2$s' AND assignee <> U'%2$s'" + " AND active = 1", groups, contactHandle); // Get list xml String listXml = getUSDWebService().doSelect(sessionID, "cr", whereClause, maxRows, toArrayOfString(ATTRIBUTE_NAMES)); // Parse xml to list return parseIssues(listXml, null, "G"); } /** * USD-WS lookup. */ private List<Issue> getChangeOrdersForContact(int sessionID, String contactHandle, int maxRows) throws RemoteException { // Build where clause String whereClause = String.format("affected_contact = U'%1$s' AND active = 1", contactHandle); // Get list xml String listXml = getUSDWebService().doSelect(sessionID, "chg", whereClause, maxRows, toArrayOfString(ATTRIBUTE_NAMES_CHG)); // Parse xml to list return parseIssues(listXml, TYPE_CHANGE_ORDER, "U"); } private List<Issue> getChangeOrdersForAssignee(int sessionID, String contactHandle, int maxRows) throws RemoteException { // Build where clause String whereClause = String.format("assignee = U'%s' AND active = 1", contactHandle); // Get list xml String listXml = getUSDWebService().doSelect(sessionID, "chg", whereClause, maxRows, toArrayOfString(ATTRIBUTE_NAMES_CHG)); // Parse xml to list return parseIssues(listXml, TYPE_CHANGE_ORDER, "A"); } /** * USD-WS lookup. */ private List<Issue> getChangeOrdersForContactByGroup(int sessionID, String contactHandle, String groups, int maxRows) throws RemoteException { // Build where clause String whereClause = String.format( "group in (%1$s) AND affected_contact <> U'%2$s' AND assignee <> U'%2$s'" + " AND active = 1", groups, contactHandle); // Get list xml String listXml = getUSDWebService().doSelect(sessionID, "chg", whereClause, maxRows, toArrayOfString(ATTRIBUTE_NAMES_CHG)); // Parse xml to list return parseIssues(listXml, TYPE_CHANGE_ORDER, "G"); } /** * Lookup the groups the user is member of. * * @param sessionID - USD session id. * @param contactHandle - handle to the user. * @return - a comma separated string of group handles. */ private String lookupUserGroups(int sessionID, String contactHandle) { // Build where clause String whereClause = String.format("member = U'%s'", contactHandle); // Get list xml String listXml = getUSDWebService().doSelect(sessionID, "grpmem", whereClause, -1, toArrayOfString(Arrays.asList("group"))); return parseUserGroups(listXml); } /** * Parse group id's from from a USD-query. * * @param xml - standard USD query response. * @return - a formated (comma separated) string of group handles. */ private String parseUserGroups(String xml) { String groups = ""; try { // Parse the XML to get a DOM to query Document doc = parseXml(xml); // Extract USDObject's String xPath = "/UDSObjectList/UDSObject"; NodeList udsObjects = evaluate(xPath, doc, XPathConstants.NODESET); // Iterate over USDObject's to create Issue's for (int i = 1; i < udsObjects.getLength() + 1; i++) { if (groups.length() > 0) groups += ","; groups += "U'" + extractAttribute(i, "group", XPathConstants.STRING, doc) + "'"; } return groups; } catch (Exception e) { String msg = "Error when parsing group handles from XML"; log.error(msg); throw new RuntimeException(msg, e); } } protected static List<Issue> parseIssues(String xml, String fallbackType, String associated) throws RuntimeException { List<Issue> issueList = new ArrayList<Issue>(); try { // Parse the XML to get a DOM to query Document doc = parseXml(xml); // Extract USDObject's String xPath = "/UDSObjectList/UDSObject"; NodeList udsObjects = evaluate(xPath, doc, XPathConstants.NODESET); // Iterate over USDObject's to create Issue's for (int i = 1; i < udsObjects.getLength() + 1; i++) { // Get ref_num String refNum = null; if (TYPE_CHANGE_ORDER.equals(fallbackType)) { refNum = extractAttribute(i, "chg_ref_num", XPathConstants.STRING, doc); } else { refNum = extractAttribute(i, "ref_num", XPathConstants.STRING, doc); } // A Issue has to have refNum to be valid if (StringUtils.isEmpty(refNum)) { continue; } Issue issue = resolveIssue(refNum, i, fallbackType, associated, doc); // Add Issue object to list issueList.add(issue); } return issueList; } catch (Exception e) { log.error("Error when trying to parse issue list from XML", e); throw new RuntimeException("Error when trying to parse issue list from XML", e); } } private static Issue resolveIssue(String refNum, int i, String fallbackType, String associated, Document doc) throws XPathExpressionException { Issue issue = new Issue(); issue.setRefNum(refNum); // Get summary String summary = extractAttribute(i, "summary", XPathConstants.STRING, doc); issue.setSummary(summary); // Get description String description = extractAttribute(i, "description", XPathConstants.STRING, doc); issue.setDescription(description); // Get status String statusSym = extractAttribute(i, "status.sym", XPathConstants.STRING, doc); issue.setStatus(statusSym); // Get web_url String webUrl = extractAttribute(i, "web_url", XPathConstants.STRING, doc); webUrl = webUrl.replaceFirst("vgms0005", "vgrusd.vgregion.se"); issue.setUrl(webUrl); // Get type String type = extractAttribute(i, "type", XPathConstants.STRING, doc); if (StringUtils.isEmpty(type)) { type = fallbackType; } issue.setType(type); issue.setAssociated(associated); return issue; } private static String extractAttribute(int cnt, String attrName, QName attrType, Document source) throws XPathExpressionException { String exprTemplate = "/UDSObjectList/UDSObject[%s]/Attributes/Attribute[AttrName='%s']/AttrValue"; String xPath = String.format(exprTemplate, cnt, attrName); return evaluate(xPath, source, XPathConstants.STRING); } /** * USD-WS lookup. */ private String lookupContactHandle(String userId, int sessionID) { String contactHandle = null; try { contactHandle = getUSDWebService().getHandleForUserid(sessionID, userId); // Rid object type from handle contactHandle = contactHandle.replaceFirst("cnt:", ""); } catch (Throwable e) { log.error("Could not get handle for user with userId " + userId); } return contactHandle; } /** * USD-WS lookup. */ private String getGroupHandle(String groupName) { int sessionID = 0; String whereClause = String.format("type = 2308 and delete_flag = 0 and last_name = '%s'", groupName); try { sessionID = getUSDWebService().login(wsUser, wsPassword); String resultXml = getUSDWebService().doSelect(sessionID, "cnt", whereClause, -1, toArrayOfString(Arrays.<String>asList("last_name"))); return extractHandle(resultXml); } catch (RemoteException e) { throw new RuntimeException("Error when getting group handle", e); } catch (Exception e) { throw new RuntimeException("Error when parsing xml response when searching for a group", e); } } /** * USD-WS lookup. */ private void createAttachment(int sid, String repHandle, String objectHandle, String description, Attachment attachment) throws Exception { DataHandler dhandler = createDataHandler(attachment); // Affix DIME type header to attachment before sending // ((javax.xml.rpc.Stub) getWebService())._setProperty(org.apache.axis.client.Call.ATTACHMENT_ENCAPSULATION_FORMAT, // Call.ATTACHMENT_ENCAPSULATION_FORMAT_DIME); // ((org.apache.axis.client.Stub) getWebService()).addAttachment(dhandler); // Create attachment // getWebService().createAttachment(sid, repHandle, objectHandle, description, attachment.getFilename()); getUSDWebService().createAttachment(sid, repHandle, objectHandle, description, attachment.getFilename()); } private DataHandler createDataHandler(Attachment attachment) { DataSource source; try { source = new ByteArrayDataSource(attachment.getData(), "application/octet-stream"); } catch (IOException e) { throw new RuntimeException(e); } return new DataHandler(source); } protected String extractHandle(String xml) throws Exception { Document doc = parseXml(xml); NodeList handles = doc.getElementsByTagName("Handle"); if (handles.getLength() > 0) { return handles.item(0).getFirstChild().getNodeValue(); } return ""; } /** * Convenience method. * * @param xml, the xml to be parsed * @return a DOM Document * @throws ParserConfigurationException * @throws SAXException * @throws IOException */ private static Document parseXml(String xml) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); dbfactory.setNamespaceAware(true); dbfactory.setXIncludeAware(true); DocumentBuilder parser = dbfactory.newDocumentBuilder(); // To avoid illegal xml character references xml = xml.replace("&", "&"); ByteArrayInputStream bais = new ByteArrayInputStream(xml.getBytes("UTF-8")); return parser.parse(bais); } /** * Convenience method. * <p/> * Neither XPathFactory nor XPath are thread safe. Further more XPath is not re-entrant, * so a new instance has to be created for every evaluation. * The generic marker make this more convenient to use - however QName has to match the expected return type. * * @param xPath, the xPath to be evaluated. * @param source, the source document to evaluate on. * @param qName, the node type the evaluation returns. * @param <T>, matching return type. * @return the evaluated result. * @throws XPathExpressionException */ private static <T> T evaluate(String xPath, Document source, QName qName) throws XPathExpressionException { XPathFactory factory = XPathFactory.newInstance(); XPath processor = factory.newXPath(); return (T) processor.evaluate(xPath, source, qName); } private ArrayOfString toArrayOfString(List<String> source) { ArrayOfString target = new ArrayOfString(); for (String val : source) { target.getString().add(val); } return target; } private USDWebServiceSoap getUSDWebService() { if (ws == null) { QName qName = new QName("http://www.ca.com/UnicenterServicePlus/ServiceDesk", "USD_WebService"); // This initialization method uses the conduit configuration if such configuration exists. JaxWsProxyFactoryBean proxyFactoryBean = new JaxWsProxyFactoryBean(); proxyFactoryBean.setServiceClass(USDWebServiceSoap.class); proxyFactoryBean.setAddress(endPoint.toString()); ws = (USDWebServiceSoap) proxyFactoryBean.create(); } return ws; } }