Java tutorial
/** * Copyright 2012, 2013 Golden Gekko * * This file is part of Meetr. * * Meetr is free software: you can use it, modify it and / or * redistribute it as is or with your changes under the terms of the * GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) * any later version. * * Meetr 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Meetr. If not, see <http://www.gnu.org/licenses />. */ package com.goldengekko.meetr.service.sugarcrm; import com.goldengekko.meetr.domain.DmContact; import com.goldengekko.meetr.service.ContactService; import com.wadpam.open.exceptions.BadRequestException; import com.wadpam.open.exceptions.RestException; import net.sf.mardao.core.CursorPage; import org.apache.commons.codec.binary.Hex; import org.codehaus.jackson.map.DeserializationConfig; import org.codehaus.jackson.map.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.client.RequestCallback; import org.springframework.web.client.ResponseExtractor; import org.springframework.web.client.RestTemplate; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; /** * Implements communication with a SugarCRM instance. * @author mlv */ // For SugarCRM we will try and use the same user for all requests regardless of the app user // We will get the user and password from the Spring Context and store the token as an instance // variable. // For SugarCRM it looks like the token is always the same for the same user. But the token seem // to expire after some time (a couple or hours) and you need to generate a new token. // The new token will be the same as before (guess the SugareCRM keeps a timestamp when the token was // generated and yells if it was too long ago) // Apps should always ask for a new token when they are started and the MW will request a token // from SugarCRM (even if one exists). This way there is bigger chance that the token will not expire. // Is the token expire during a get contacts request, the MW will silently requests a new token and // redo the get contacts request (will request will take longer timer). // Format of the request // http://sugarcrm.goldengekko.com/service/v4/rest.php? // method=login& // input_type=json& // response_type=json&rest_data={"user_auth":{"user_name":"admin","password":"36491b3476d3ed80d5a5ae6f4975579e","version":"1.0"},"application_name":"RestTest"} // NOTE! The response_type parameter must be URL encoded // NOTE! The parameters seem to be position dependent and MUST follow a specific order // A PHP example can be found here http://support.sugarcrm.com/04_Find_Answers/03_Developers/100Web_Services/100REST_API/100Examples/100PHP/Retrieving_a_List_of_Records_With_Related_Info_-_REST_and_PHP public class SugarCRMClient implements ContactService { static final Logger LOG = LoggerFactory.getLogger(SugarCRMClient.class); static final int ERR_MISSING_USER_PASSWORD = 500 + 1; static final int ERR_SUGAR_NOT_AVAILABLE = 500 + 2; static final int ERR_SUGAR_LOGIN_FAILED = 500 + 3; static final int ERR_SUGAR_GET_CONTACTS_FAILED = 500 + 4; static final int ERR_SUGAR_INVALID_TOKEN = 500 + 5; private String user; private String password; private String sugarCRMUrl; private String token; private final RestTemplate TEMPLATE; private final ObjectMapper MAPPER; static private final String PARAM_TEMPLATE = "?method={method}&input_type={input_type}&response_type={response_type}&rest_data={rest_data}"; public SugarCRMClient() { this.TEMPLATE = new RestTemplate(); MAPPER = new ObjectMapper(); MAPPER.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); } // @Override public String getToken(String user, String password) { LOG.debug("Get SugarCRM token"); if (null == user || null == password || user.isEmpty() || password.isEmpty()) { LOG.info("User and password must be provided when creating token"); throw new BadRequestException(ERR_MISSING_USER_PASSWORD, "User and password must be provided when creating token"); } String data = String.format( "{\"user_auth\":{\"user_name\":\"%s\",\"password\":\"%s\",\"version\":\"%s\"},\"application_name\":\"%s\"}", this.user, md5Hash(this.password), "1.0", "Meeter"); //LOG.debug("Send login with data:{}", data); this.token = TEMPLATE.execute(this.sugarCRMUrl + PARAM_TEMPLATE, HttpMethod.GET, new RequestCallback() { @Override public void doWithRequest(ClientHttpRequest clientHttpRequest) throws IOException { LOG.debug("Sending login request with url:{}", clientHttpRequest.getURI().toURL().toExternalForm()); } }, new ResponseExtractor<String>() { @Override public String extractData(ClientHttpResponse clientHttpResponse) throws IOException { LOG.debug("Response with http code:{}", clientHttpResponse.getStatusCode().value()); if (clientHttpResponse.getStatusCode() == HttpStatus.OK) { SugarCRMLoginResponse response = MAPPER.readValue(clientHttpResponse.getBody(), SugarCRMLoginResponse.class); LOG.debug("Response:{}", response); if (!response.hasError()) { return response.getId(); } else if (response.isInvalidCredentials()) { LOG.info("SugarCRM login failed with invalid credentials", new StringHttpMessageConverter().read(String.class, clientHttpResponse)); throw new RestException(ERR_SUGAR_LOGIN_FAILED, HttpStatus.FORBIDDEN, "SugarCRM login failed with invalid credentials"); } else { LOG.info("SugarCRM login failed with unknown reason:{}", new StringHttpMessageConverter().read(String.class, clientHttpResponse)); throw new RestException(ERR_SUGAR_LOGIN_FAILED, HttpStatus.FORBIDDEN, "SugarCRM login failed with unknown reason"); } } else { // If the SugarCRM does not respond with 200 throw http 503 LOG.warn("SugarCRM is responding with http code:{}", clientHttpResponse.getStatusCode().value()); throw new RestException(ERR_SUGAR_NOT_AVAILABLE, HttpStatus.SERVICE_UNAVAILABLE, "SugarCRM request failed"); } } }, "login", "json", "json", data); LOG.debug("Got token:{}", this.token); return this.token; } // md5 hash private String md5Hash(String string) { byte[] bytesOfMessage = new byte[0]; try { bytesOfMessage = string.getBytes("UTF-8"); } catch (UnsupportedEncodingException willNeverHappen) { } MessageDigest md = null; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException willNeverHappen) { } byte[] digest = md.digest(bytesOfMessage); return Hex.encodeHexString(digest); } @Override public DmContact get(String parentKeyString, String id) { throw new UnsupportedOperationException("Not supported yet."); } @Override public CursorPage<DmContact> getPage(int pageSize, String cursorKey) { LOG.debug("SugarCRM client, get contacts. Token:{}", token); // Check that we have a token if (null == this.token || null == token) { throw new RestException(ERR_SUGAR_INVALID_TOKEN, HttpStatus.FORBIDDEN, "Token missing, app must generate token first"); } // If the cursor is null start from the beginning if (null == cursorKey) { cursorKey = "0"; } // The request // {"session":"f9psqc1rgd2iuri76u3v17aul1","module_name":"Contacts","query":"","order_by":"","offset":1,"select_fields":["id","name"],"link_name_to_fields_array":[],"max_results":2,"deleted":0,"Favorites":0} String data = String.format( "{\"session\":\"%s\",\"module_name\":\"Contacts\",\"query\":\"\",\"order_by\":\"\",\"offset\":%s,\"select_fields\":[\"id\",\"first_name\",\"last_name\",\"email\",\"phone_work\",\"primary_address_street\",\"primary_address_city\",\"primary_address_country\",\"primary_address_postalcode\"],\"link_name_to_fields_array\":[],\"max_results\":%s,\"deleted\":0,\"Favorites\":0}", this.token, cursorKey.toString(), pageSize); LOG.debug("get contacts with data:{}", data); SugarCRMContactsResponse contacts = TEMPLATE.execute(this.sugarCRMUrl + PARAM_TEMPLATE, HttpMethod.GET, new RequestCallback() { @Override public void doWithRequest(ClientHttpRequest clientHttpRequest) throws IOException { LOG.debug("Sending get contact request with url:{}", clientHttpRequest.getURI().toURL().toExternalForm()); } }, new ResponseExtractor<SugarCRMContactsResponse>() { @Override public SugarCRMContactsResponse extractData(ClientHttpResponse clientHttpResponse) throws IOException { LOG.debug("Response with http code:{}", clientHttpResponse.getStatusCode().value()); if (clientHttpResponse.getStatusCode() == HttpStatus.OK) { SugarCRMContactsResponse response = MAPPER.readValue(clientHttpResponse.getBody(), SugarCRMContactsResponse.class); LOG.debug("Response:{}", response); if (!response.hasError()) { return response; } else if (response.isTokenInvalid()) { LOG.info("Get contacts failed, invalid token"); throw new RestException(ERR_SUGAR_INVALID_TOKEN, HttpStatus.FORBIDDEN, "SugarCRM get contacts failed, invalid token"); } else { LOG.info("SugarCRM get contacts failed with unknown reason:{}", new StringHttpMessageConverter().read(String.class, clientHttpResponse)); throw new RestException(ERR_SUGAR_GET_CONTACTS_FAILED, HttpStatus.SERVICE_UNAVAILABLE, "SugarCRM get contacts failed with unknown reason"); } } else { // If the SugarCRM does not respond with 200 throw http 503 LOG.warn("SugarCRM is responding with http code:{}", clientHttpResponse.getStatusCode().value()); throw new RestException(ERR_SUGAR_NOT_AVAILABLE, HttpStatus.SERVICE_UNAVAILABLE, "SugarCRM request failed"); } } }, "get_entry_list", "json", "json", data); LOG.debug("Got number of contacts:{}", contacts.getResult_count()); CursorPage<DmContact> page = convertToPage(contacts, pageSize); return page; } // Convert from SugarCRM response private CursorPage<DmContact> convertToPage(SugarCRMContactsResponse response, int pageSize) { if (null == response) { return null; } CursorPage<DmContact> page = new CursorPage<DmContact>(); page.setRequestedPageSize(pageSize); page.setCursorKey(Integer.toString(response.getNext_offset())); // "id","first_name","last_name","email","phone_work","primary_address_street","primary_address_city","primary_address_country","primary_address_postalcode" Collection<DmContact> contacts = new ArrayList<DmContact>(); for (SugarCRMEntryListResponse entry : response.getEntry_list()) { DmContact dmContact = new DmContact(); dmContact.setId(entry.getName_value_list().getId().getValue()); dmContact.setFirstName(entry.getName_value_list().getFirst_name().getValue()); dmContact.setLastName(entry.getName_value_list().getLast_name().getValue()); dmContact.setEmail(entry.getName_value_list().getEmail().getValue()); dmContact.setPhone(entry.getName_value_list().getPhone_work().getValue()); dmContact.setStreet(entry.getName_value_list().getPrimary_address_street().getValue()); dmContact.setCity(entry.getName_value_list().getPrimary_address_city().getValue()); dmContact.setCountry(entry.getName_value_list().getPrimary_address_country().getValue()); dmContact.setPostalCode(entry.getName_value_list().getPrimary_address_postalcode().getValue()); contacts.add(dmContact); } page.setItems(contacts); return page; } @Override public CursorPage<DmContact> searchContacts(String text, int pageSize, Serializable cursorKey) { throw new UnsupportedOperationException("Not supported yet."); } // Setters and getters public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getSugarCRMUrl() { return sugarCRMUrl; } public void setSugarCRMUrl(String sugarCRMUrl) { this.sugarCRMUrl = sugarCRMUrl; } @Override public DmContact createDomain() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public String create(DmContact domain) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public void delete(String parentKeyString, String id) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public void delete(String parentKeyString, Iterable<String> ids) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public void exportCsv(OutputStream out, Long startDate, Long endDate) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public Iterable<DmContact> getByPrimaryKeys(Iterable<String> ids) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public String getSimpleKey(DmContact domain) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public String getParentKeyString(DmContact domain) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public String getPrimaryKeyColumnName() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public Class getPrimaryKeyColumnClass() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public String getTableName() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public Map<String, Class> getTypeMap() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public String update(DmContact domain) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public List<String> upsert(Iterable<DmContact> domains) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public CursorPage<String> whatsChanged(Date since, String createdBy, String updatedBy, int pageSize, String cursorKey) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } private class LoginRequest { private String user; private String password; LoginRequest(String user, String password) { } } }