├── LICENSE
├── Procfile
├── README.markdown
├── pom.xml
└── src
├── bindings
└── custombinding.xml
├── main
├── java
│ ├── Main.java
│ ├── canvas
│ │ ├── CanvasClient.java
│ │ ├── CanvasContext.java
│ │ ├── CanvasEnvironmentContext.java
│ │ ├── CanvasLinkContext.java
│ │ ├── CanvasOrganizationContext.java
│ │ ├── CanvasRequest.java
│ │ ├── CanvasUserContext.java
│ │ ├── SignedRequest.java
│ │ └── ToolingAPI.java
│ └── servlets
│ │ ├── OAuthServlet.java
│ │ └── ProxyServlet.java
└── webapp
│ ├── WEB-INF
│ └── web.xml
│ ├── canvas.jsp
│ ├── icon.png
│ ├── images
│ ├── salesforce.png
│ └── share.png
│ ├── index.html
│ ├── logo.png
│ ├── scripts
│ ├── chatter-talk.js
│ └── json2.js
│ ├── sdk
│ ├── callback.html
│ ├── css
│ │ └── canvas.css
│ └── js
│ │ ├── canvas-all.js
│ │ ├── canvas.js
│ │ ├── client.js
│ │ ├── cookies.js
│ │ ├── oauth.js
│ │ └── xd.js
│ ├── signed-request.jsp
│ └── welcome.jsp
└── resources
└── toolingapi.wsdl
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c), Andrew Fawcett
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | - Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 | - Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 | - Neither the name of the Andrew Fawcett, nor the names of its contributors
13 | may be used to endorse or promote products derived from this software without
14 | specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
19 | THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: sh target/bin/webapp
2 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Apex Code Analysis Tool using Tooling API and Canvas
2 | ====================================================
3 |
4 | This tool utilises the new **Spring'13 Tooling API** and the **Canvas** technology to fill a gap in the Apex developers toolkit. That being the ability to perform analysis on the Apex code they are developing. This initial prototype shows how the SymbolTable information can be used to list unused Apex methods. More information and background to this can be found at my blog [here](http://andrewfawcett.wordpress.com/2013/02/02/spring13-clean-apex-code-with-the-tooling-api). For more information on how to run and deploy this sample, please refer to the [Force.com Canvas Developers Guide](http://www.salesforce.com/us/developer/docs/platform_connect/index.htm).
5 |
6 | 
7 |
8 | For more information see my blog entry [Spring'13 Clean Apex Code with the Tooling API](http://andrewfawcett.wordpress.com/2013/02/02/spring13-clean-apex-code-with-the-tooling-api).
9 |
10 | Generating the Tooling API Client Proxies (with JAX-WS) and Maven
11 | -----------------------------------------------------------------
12 |
13 | In order to consume the Tooling API in Java I chose the SOAP protocol, since Java is a type safe language and interacting with the data types and operations of the API is much easier using Java's native features. These are the steps I took to accomplish this in Maven, you may want to use a different tool or approach.
14 |
15 | - Downloaded the Tooling API WSDL (from the Setup > Develop > API page)
16 | - Edited my **pom.xml** to plugin the **JAX-WS xsimport** [tool](http://jax-ws-commons.java.net/jaxws-maven-plugin/wsimport-mojo.html) and configure it.
17 | - As Salesforce schema types use the minOccurs="0" and nillable="true" convention, this resulted in less desirable (but techncially accurate) generated code to support this convention. As such I created a **/bindings/bindings.xml** file to customise the generated code to produce more typical get/set methods.
18 | - The WSDL available (pre-spring'13 launch) did appear to have some issues / missing elements.
19 | - I needed to add the SessionHeader soap header declaration to some of the operations I wanted (others had it).
20 | - I also needed to remove 'id' element declarations that appeared to be duplicates of what would be inherited from the SObject base schema type in the WSDL. This caused the generated code to stop generating get/set methods and fall back to name/value pair convention.
21 |
22 | I am not sure if the Salesforce WSC compiler tool would have had these issues, thats something I want to look into. However for now the JAX-WS tool does work great inside a Maven build project once the above is sorted.
23 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
36 | * Some information within the context depends on what oauth scopes are allowed 37 | * on the canvas application. Some/all items may be null if the oauth scope is 38 | * not set accordingly. 39 | * 40 | */ 41 | @JsonIgnoreProperties(ignoreUnknown=true) 42 | public class CanvasContext { 43 | 44 | private CanvasUserContext userContext = null; 45 | private CanvasOrganizationContext orgContext = null; 46 | private CanvasLinkContext linkContext = null; 47 | private CanvasEnvironmentContext envContext = null; 48 | 49 | 50 | /** 51 | * Provides the context about the current user. 52 | * 53 | * @return The current user context, or null if the oauth scope 54 | * will not allow. 55 | */ 56 | @JsonProperty("user") 57 | public CanvasUserContext getUserContext() { 58 | return this.userContext; 59 | } 60 | 61 | /** 62 | * Sets the context about the current user. 63 | */ 64 | @JsonProperty("user") 65 | public void setUserContext(CanvasUserContext userContext) 66 | { 67 | this.userContext = userContext; 68 | } 69 | 70 | /** 71 | * Provides the context about the current organization. 72 | * 73 | * @return The current organization context, or null if the oauth scope 74 | * will not allow. 75 | */ 76 | @JsonProperty("organization") 77 | public CanvasOrganizationContext getOrganizationContext() { 78 | return orgContext; 79 | } 80 | 81 | /** 82 | * Sets the context about the current organization. 83 | */ 84 | @JsonProperty("organization") 85 | public void setOrganizationContext(CanvasOrganizationContext orgContext) 86 | { 87 | this.orgContext = orgContext; 88 | } 89 | 90 | /** 91 | * Provides the context about the current environment (page, url, etc). 92 | */ 93 | @JsonProperty("environment") 94 | public CanvasEnvironmentContext getEnvironmentContext() { 95 | if (null == this.envContext){ 96 | return new CanvasEnvironmentContext(); 97 | } 98 | return envContext; 99 | } 100 | 101 | @JsonProperty("environment") 102 | public void setEnvironmentContext(CanvasEnvironmentContext envContext){ 103 | this.envContext = envContext; 104 | } 105 | 106 | /** 107 | * Provides links to external resources within sfdc. 108 | */ 109 | @JsonProperty("links") 110 | public CanvasLinkContext getLinkContext() { 111 | return linkContext; 112 | } 113 | 114 | /** 115 | * Sets the link context for this request. 116 | * @param linkContext 117 | */ 118 | @JsonProperty("links") 119 | public void setLinkContext(CanvasLinkContext linkContext) 120 | { 121 | this.linkContext = linkContext; 122 | } 123 | 124 | @Override 125 | public String toString() 126 | { 127 | return String.format("Canvas Context:\n\t" + 128 | "User Context:\n\t\t%s\n\t"+ 129 | "Org Context:\n\t\t%s\n\t"+ 130 | "Environment Context:\n\t\t%s\n\t"+ 131 | "Link Context:\n\t\t%s\n", 132 | null != userContext?userContext.toString():"null", 133 | null != orgContext?orgContext.toString():"null", 134 | null != envContext?envContext.toString():"null", 135 | null != linkContext?linkContext.toString():"null"); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/canvas/CanvasEnvironmentContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, salesforce.com, inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided 6 | * that the following conditions are met: 7 | * 8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the 9 | * following disclaimer. 10 | * 11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 12 | * the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | * 14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or 15 | * promote products derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package canvas; 28 | 29 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 30 | import org.codehaus.jackson.annotate.JsonProperty; 31 | 32 | @JsonIgnoreProperties(ignoreUnknown=true) 33 | public class CanvasEnvironmentContext { 34 | 35 | private String locationUrl; 36 | private String uiTheme; 37 | private Dimensions dimensions; 38 | private SystemVersion version; 39 | 40 | /** 41 | * Returns the url of the current location. 42 | */ 43 | @JsonProperty("locationUrl") 44 | public String getLocationUrl() { 45 | return this.locationUrl; 46 | } 47 | 48 | public void setLocationUrl(String referrerUrl) { 49 | this.locationUrl = referrerUrl; 50 | } 51 | 52 | /** 53 | * Returns the value Theme2 if the user is using the newer user interface theme of the online application, labeled 54 | * \u201cSalesforce.\u201d Returns Theme1 if the user is using the older user interface theme, labeled 55 | * \u201cSalesforce Classic.\u201d 56 | */ 57 | @JsonProperty("uiTheme") 58 | public String getUiTheme() { 59 | return this.uiTheme; 60 | } 61 | 62 | public void setUiTheme(String uiTheme) { 63 | this.uiTheme = uiTheme; 64 | } 65 | 66 | @JsonProperty("dimensions") 67 | public Dimensions getDimensions() { 68 | return this.dimensions; 69 | } 70 | 71 | @JsonProperty("dimensions") 72 | public void setDimensions(Dimensions dimensions) { 73 | this.dimensions = dimensions; 74 | } 75 | 76 | @JsonProperty("version") 77 | public SystemVersion getSystemVersion() { 78 | return this.version; 79 | } 80 | 81 | @JsonProperty("version") 82 | public void setSystemVersion(SystemVersion systemVersion) { 83 | this.version = systemVersion; 84 | } 85 | 86 | @Override 87 | public String toString() 88 | { 89 | return locationUrl + ", " + 90 | uiTheme + "," + 91 | dimensions.toString() + "," + 92 | version.toString(); 93 | } 94 | 95 | @JsonIgnoreProperties(ignoreUnknown=true) 96 | public static class Dimensions{ 97 | /** 98 | * The width of the iframe 99 | */ 100 | private String width; 101 | /** 102 | * The height of the iframe. 103 | */ 104 | private String height; 105 | 106 | @JsonProperty("width") 107 | public String getWidth() { 108 | return this.width; 109 | } 110 | @JsonProperty("width") 111 | public void setWidth(String width) { 112 | this.width = width; 113 | } 114 | 115 | @JsonProperty("height") 116 | public String getHeight() { 117 | return this.height; 118 | } 119 | @JsonProperty("height") 120 | public void setHeight(String height) { 121 | this.height = height; 122 | } 123 | 124 | @Override 125 | public String toString(){ 126 | return String.format("(w:%s,h:%s)",width,height); 127 | } 128 | } 129 | 130 | @JsonIgnoreProperties(ignoreUnknown=true) 131 | public static class SystemVersion{ 132 | 133 | private String api; 134 | private String season; 135 | 136 | // Needs default ctor for Jackson to construct. 137 | public SystemVersion(){ 138 | } 139 | 140 | @JsonProperty("api") 141 | public String getApiVersion() { 142 | return this.api; 143 | } 144 | 145 | @JsonProperty("api") 146 | public void setApiVersion(String apiVersion) { 147 | this.api = apiVersion; 148 | } 149 | 150 | @JsonProperty("season") 151 | public String getSeason() { 152 | return this.season; 153 | } 154 | 155 | @JsonProperty("season") 156 | public void setSeason(String season) { 157 | this.season = season; 158 | } 159 | 160 | @Override 161 | public String toString(){ 162 | return String.format("%s - %s",api,season); 163 | } 164 | 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/canvas/CanvasLinkContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, salesforce.com, inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided 6 | * that the following conditions are met: 7 | * 8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the 9 | * following disclaimer. 10 | * 11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 12 | * the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | * 14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or 15 | * promote products derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package canvas; 28 | 29 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 30 | import org.codehaus.jackson.annotate.JsonProperty; 31 | 32 | /** 33 | * Describes all contextual information around external references, or links to external resources. 34 | */ 35 | @JsonIgnoreProperties(ignoreUnknown=true) 36 | public class CanvasLinkContext { 37 | private String enterpriseUrl; 38 | private String metadataUrl; 39 | private String partnerUrl; 40 | private String restUrl; 41 | private String sobjectUrl; 42 | private String searchUrl; 43 | private String queryUrl; 44 | private String recentItemsUrl; 45 | private String userProfileUrl; 46 | private String chatterFeedsUrl; 47 | private String chatterGroupsUrl; 48 | private String chatterUsersUrl; 49 | private String chatterFeedItemsUrl; 50 | 51 | /** 52 | * Provide the url for enterprise-wide external clients. 53 | */ 54 | @JsonProperty("enterpriseUrl") 55 | public String getEnterpriseUrl() { 56 | return this.enterpriseUrl; 57 | } 58 | 59 | public void setEnterpriseUrl(String enterpriseUrl) { 60 | this.enterpriseUrl = enterpriseUrl; 61 | } 62 | 63 | /** 64 | * Provide the base url for the metadata api to access information about custom objects, apex classes, etc. 65 | */ 66 | @JsonProperty("metadataUrl") 67 | public String getMetadataUrl() { 68 | return this.metadataUrl; 69 | } 70 | 71 | public void setMetadataUrl(String metadataUrl) { 72 | this.metadataUrl = metadataUrl; 73 | } 74 | 75 | /** 76 | * Access to the partner api for developing client applications for multiple organizations. 77 | */ 78 | @JsonProperty("partnerUrl") 79 | public String getPartnerUrl() { 80 | return this.partnerUrl; 81 | } 82 | 83 | public void setPartnerUrl(String partnerUrl) { 84 | this.partnerUrl = partnerUrl; 85 | } 86 | 87 | /** 88 | * Access to the base url for RESTful services. 89 | */ 90 | @JsonProperty("restUrl") 91 | public String getRestUrl() { 92 | return this.restUrl; 93 | } 94 | 95 | public void setRestUrl(String restUrl) { 96 | this.restUrl = restUrl; 97 | } 98 | 99 | /** 100 | * Access to custom sobject definitions. 101 | */ 102 | @JsonProperty("sobjectUrl") 103 | public String getSobjectUrl() { 104 | return this.sobjectUrl; 105 | } 106 | 107 | public void setSobjectUrl(String sobjectUrl) { 108 | this.sobjectUrl = sobjectUrl; 109 | } 110 | 111 | /** 112 | * Access to search api. 113 | */ 114 | @JsonProperty("searchUrl") 115 | public String getSearchUrl() { 116 | return this.searchUrl; 117 | } 118 | 119 | public void setSearchUrl(String searchUrl) { 120 | this.searchUrl = searchUrl; 121 | } 122 | 123 | /** 124 | * Access to the SOQL query api. 125 | */ 126 | @JsonProperty("queryUrl") 127 | public String getQueryUrl() { 128 | return this.queryUrl; 129 | } 130 | 131 | public void setQueryUrl(String queryUrl) { 132 | this.queryUrl = queryUrl; 133 | } 134 | 135 | /** 136 | * Access to the recent items feed. 137 | */ 138 | @JsonProperty("recentItemsUrl") 139 | public String getRecentItemsUrl() { 140 | return this.recentItemsUrl; 141 | } 142 | 143 | public void setRecentItemsUrl(String recentItemsUrl) { 144 | this.recentItemsUrl = recentItemsUrl; 145 | } 146 | 147 | /** 148 | * Retrieve more information about the current user. 149 | */ 150 | @JsonProperty("userUrl") 151 | public String getUserUrl() { 152 | return this.userProfileUrl; 153 | } 154 | 155 | public void setUserUrl(String userProfileUrl) { 156 | this.userProfileUrl = userProfileUrl; 157 | } 158 | 159 | /** 160 | * Access to Chatter Feeds. Note: Requires user profile permissions, otherwise this will be null. 161 | */ 162 | @JsonProperty("chatterFeedsUrl") 163 | public String getChatterFeedsUrl() { 164 | return this.chatterFeedsUrl; 165 | } 166 | 167 | public void setChatterFeedsUrl(String chatterFeedsUrl) { 168 | this.chatterFeedsUrl = chatterFeedsUrl; 169 | } 170 | 171 | /** 172 | * Access to Chatter Groups. Note: Requires user profile permissions, otherwise this will be null. 173 | */ 174 | @JsonProperty("chatterGroupsUrl") 175 | public String getChatterGroupsUrl() { 176 | return this.chatterGroupsUrl; 177 | } 178 | 179 | public void setChatterGroupsUrl(String chatterGroupsUrl) { 180 | this.chatterGroupsUrl = chatterGroupsUrl; 181 | } 182 | 183 | /** 184 | * Access to Chatter Users. Note: Requires user profile permissions, otherwise this will be null. 185 | */ 186 | @JsonProperty("chatterUsersUrl") 187 | public String getChatterUsersUrl() { 188 | return this.chatterUsersUrl; 189 | } 190 | 191 | public void setChatterUsersUrl(String chatterUsersUrl) { 192 | this.chatterUsersUrl = chatterUsersUrl; 193 | } 194 | 195 | /** 196 | * Access to individual Chatter Feed items. Note: Requires user profile permissions, otherwise this will be null. 197 | */ 198 | @JsonProperty("chatterFeedItemsUrl") 199 | public String getChatterFeedItemsUrl() { 200 | return this.chatterFeedItemsUrl; 201 | } 202 | 203 | public void setChatterFeedItemsUrl(String chatterFeedItemsUrl) { 204 | this.chatterFeedItemsUrl = chatterFeedItemsUrl; 205 | } 206 | 207 | @Override 208 | public String toString() 209 | { 210 | return enterpriseUrl+ ", " + 211 | metadataUrl+ ", " + 212 | partnerUrl+ ", " + 213 | restUrl+ ", " + 214 | sobjectUrl+ ", " + 215 | searchUrl+ ", " + 216 | queryUrl+ ", " + 217 | recentItemsUrl+ ", " + 218 | userProfileUrl+ ", " + 219 | chatterFeedsUrl+ ", " + 220 | chatterGroupsUrl+ ", " + 221 | chatterUsersUrl+ ", " + 222 | chatterFeedItemsUrl; 223 | } 224 | 225 | } 226 | -------------------------------------------------------------------------------- /src/main/java/canvas/CanvasOrganizationContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, salesforce.com, inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided 6 | * that the following conditions are met: 7 | * 8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the 9 | * following disclaimer. 10 | * 11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 12 | * the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | * 14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or 15 | * promote products derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package canvas; 28 | 29 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 30 | import org.codehaus.jackson.annotate.JsonProperty; 31 | 32 | /** 33 | * Describes contextual information about the current organization/company. 34 | */ 35 | @JsonIgnoreProperties(ignoreUnknown=true) 36 | public class CanvasOrganizationContext { 37 | private String organizationId; 38 | private String name; 39 | private boolean multicurrencyEnabled; 40 | private String currencyISOCode; 41 | 42 | /** 43 | * The organization id of the organization. 44 | */ 45 | @JsonProperty("organizationId") 46 | public String getOrganizationId() { 47 | return this.organizationId; 48 | } 49 | 50 | public void setOrganizationId(String organizationId) { 51 | this.organizationId = organizationId; 52 | } 53 | 54 | /** 55 | * The name of the company or organization. 56 | */ 57 | @JsonProperty("name") 58 | public String getName() { 59 | return this.name; 60 | } 61 | 62 | public void setName(String name) { 63 | this.name = name; 64 | } 65 | 66 | /** 67 | * Indicates whether the user\u2019s organization uses multiple currencies (true) or not (false). 68 | */ 69 | @JsonProperty("multicurrencyEnabled") 70 | public boolean isMulticurrencyEnabled() { 71 | return this.multicurrencyEnabled; 72 | } 73 | 74 | public void setMulticurrencyEnabled(boolean multicurrencyEnabled) { 75 | this.multicurrencyEnabled = multicurrencyEnabled; 76 | } 77 | 78 | /** 79 | * Current company's default currency ISO code (applies only if multi-currency is disabled for the org). 80 | */ 81 | @JsonProperty("currencyIsoCode") 82 | public String getCurrencyISOCode() { 83 | return this.currencyISOCode; 84 | } 85 | 86 | public void setCurrencyISOCode(String currencyISOCode) { 87 | this.currencyISOCode = currencyISOCode; 88 | } 89 | 90 | @Override 91 | public String toString() 92 | { 93 | return organizationId + ","+ 94 | name + ","+ 95 | multicurrencyEnabled + ","+ 96 | currencyISOCode; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/canvas/CanvasRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, salesforce.com, inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided 6 | * that the following conditions are met: 7 | * 8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the 9 | * following disclaimer. 10 | * 11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 12 | * the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | * 14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or 15 | * promote products derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package canvas; 28 | 29 | import java.util.HashMap; 30 | import java.util.Map; 31 | 32 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 33 | import org.codehaus.jackson.annotate.JsonProperty; 34 | 35 | /** 36 | * 37 | * The canvas request is what is sent to the client on the very first request. In this canvas 38 | * request is information for authenticating and context about the user, organization and environment. 39 | *
40 | * This class is serialized into JSON on then signed by the signature service to prevent tampering. 41 | * 42 | */ 43 | @JsonIgnoreProperties(ignoreUnknown=true) 44 | public class CanvasRequest { 45 | 46 | private String algorithm; 47 | private Integer issuedAt; 48 | private String userId; 49 | private CanvasContext canvasContext; 50 | private CanvasClient client; 51 | 52 | /** 53 | * The algorithm used to sign the request. typically HMAC-SHA256 54 | * @see platform.connect.service.SignRequestService.ALGORITHM 55 | */ 56 | @JsonProperty("algorithm") 57 | public String getAlgorithm() { 58 | return algorithm; 59 | } 60 | 61 | @JsonProperty("algorithm") 62 | public void setAlgorithm(String algorithm) { 63 | this.algorithm = algorithm; 64 | } 65 | 66 | /** 67 | * The unix time this request was issued at. 68 | */ 69 | @JsonProperty("issuedAt") 70 | public Integer getIssuedAt() { 71 | return issuedAt; 72 | } 73 | 74 | @JsonProperty("issuedAt") 75 | public void setIssuedAt(Integer issuedAt) { 76 | this.issuedAt = issuedAt; 77 | } 78 | 79 | /** 80 | * The Salesforce unique id for this user. 81 | * @return 82 | */ 83 | @JsonProperty("userId") 84 | public String getUserId() { 85 | return userId; 86 | } 87 | 88 | @JsonProperty("userId") 89 | public void setUserId(String userId) { 90 | this.userId = userId; 91 | } 92 | 93 | /** 94 | * Context information about the user, org and environment. 95 | */ 96 | @JsonProperty("context") 97 | public CanvasContext getContext() { 98 | return canvasContext; 99 | } 100 | 101 | /** 102 | * Client instance information required while using the Sfdc.canvas.client JavaScript API. 103 | */ 104 | @JsonProperty("context") 105 | public void setContext(CanvasContext canvasContext) { 106 | this.canvasContext = canvasContext; 107 | } 108 | 109 | /** 110 | * Unique information about this client (including oauth token). This information (in JSON) format needs to be 111 | * included on all client side SDK calls. 112 | */ 113 | @JsonProperty("client") 114 | public CanvasClient getClient() { 115 | return client; 116 | } 117 | 118 | @JsonProperty("client") 119 | public void setClient(CanvasClient client) { 120 | this.client = client; 121 | } 122 | } 123 | 124 | -------------------------------------------------------------------------------- /src/main/java/canvas/CanvasUserContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, salesforce.com, inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided 6 | * that the following conditions are met: 7 | * 8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the 9 | * following disclaimer. 10 | * 11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 12 | * the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | * 14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or 15 | * promote products derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package canvas; 28 | 29 | import java.util.Locale; 30 | 31 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 32 | import org.codehaus.jackson.annotate.JsonProperty; 33 | 34 | /** 35 | * Describes contextual information about the current user. 36 | */ 37 | @JsonIgnoreProperties(ignoreUnknown=true) 38 | public class CanvasUserContext{ 39 | 40 | private String userId; 41 | private String userName; 42 | private String firstName; 43 | private String lastName; 44 | private String email; 45 | private String fullName; 46 | private Locale locale; 47 | private Locale language; 48 | private String timeZone; 49 | private String profileId; 50 | private String roleId; 51 | private String userType; 52 | private String currencyISOCode; 53 | private boolean accessibilityMode; 54 | private String profilePhotoUrl; 55 | private String profileThumbnailUrl; 56 | 57 | /** 58 | * The Salesforce user identifier. 59 | */ 60 | @JsonProperty("userId") 61 | public String getUserId(){ 62 | return this.userId; 63 | } 64 | 65 | public void setUserId(String userId){ 66 | this.userId = userId; 67 | } 68 | 69 | /** 70 | * The Salesforce username. 71 | */ 72 | @JsonProperty("userName") 73 | public String getUserName(){ 74 | return this.userName; 75 | } 76 | 77 | public void setUserName(String username){ 78 | this.userName = username; 79 | } 80 | 81 | /** 82 | * User's first name. 83 | */ 84 | @JsonProperty("firstName") 85 | public String getFirstName(){ 86 | return this.firstName; 87 | } 88 | 89 | public void setFirstName(String firstName){ 90 | this.firstName = firstName; 91 | } 92 | 93 | /** 94 | * User's last name. 95 | */ 96 | @JsonProperty("lastName") 97 | public String getLastName(){ 98 | return this.lastName; 99 | } 100 | 101 | public void setLastName(String lastName){ 102 | this.lastName = lastName; 103 | } 104 | 105 | /** 106 | * Indicates whether user interface modifications for the visually impaired are on (true) or off (false). 107 | */ 108 | @JsonProperty("accessibilityModeEnabled") 109 | public boolean isAccessibilityMode(){ 110 | return this.accessibilityMode; 111 | } 112 | 113 | public void setAccessibilityMode(boolean accessibilityMode){ 114 | this.accessibilityMode = accessibilityMode; 115 | } 116 | 117 | /** 118 | * The user's email address. 119 | */ 120 | @JsonProperty("email") 121 | public String getEmail(){ 122 | return this.email; 123 | } 124 | 125 | public void setEmail(String email){ 126 | this.email = email; 127 | } 128 | 129 | /** 130 | * User's full name. 131 | */ 132 | @JsonProperty("fullName") 133 | public String getFullName(){ 134 | return this.fullName; 135 | } 136 | 137 | public void setFullName(String fullName){ 138 | this.fullName = fullName; 139 | } 140 | 141 | /** 142 | * User\u2019s locale, which controls the formatting of dates and choice of symbols for currency. 143 | */ 144 | @JsonProperty("locale") 145 | public Locale getLocale(){ 146 | return this.locale; 147 | } 148 | 149 | public void setLocale(Locale locale){ 150 | this.locale = locale; 151 | } 152 | 153 | /** 154 | * User's language, which controls the language for labels displayed in an application. 155 | */ 156 | @JsonProperty("language") 157 | public Locale getLanguage(){ 158 | return this.language; 159 | } 160 | 161 | public void setLanguage(Locale language){ 162 | this.language = language; 163 | } 164 | 165 | /** 166 | * The user's configured timezone. 167 | */ 168 | @JsonProperty("timeZone") 169 | public String getTimeZone(){ 170 | return this.timeZone; 171 | } 172 | 173 | public void setTimeZone(String timezone){ 174 | this.timeZone = timezone; 175 | } 176 | 177 | /** 178 | * Information about the user's profile identifier. 179 | */ 180 | @JsonProperty("profileId") 181 | public String getProfileId(){ 182 | return this.profileId; 183 | } 184 | 185 | public void setProfileId(String profileId){ 186 | this.profileId = profileId; 187 | } 188 | 189 | /** 190 | * Role ID of the role currently assigned to the user. 191 | */ 192 | @JsonProperty("roleId") 193 | public String getRoleId(){ 194 | return this.roleId; 195 | } 196 | 197 | public void setRoleId(String roleId){ 198 | this.roleId = roleId; 199 | } 200 | 201 | /** 202 | * Current user's license type in label form. 203 | */ 204 | public String getUserType(){ 205 | return this.userType; 206 | } 207 | 208 | public void setUserType(String userType){ 209 | this.userType = userType; 210 | } 211 | 212 | /** 213 | * Current user's default currency ISO code (applies only if multi-currency is enabled for the org). 214 | */ 215 | public String getCurrencyISOCode(){ 216 | return this.currencyISOCode; 217 | } 218 | 219 | public void setCurrencyISOCode(String currencyISOCode){ 220 | this.currencyISOCode = currencyISOCode; 221 | } 222 | 223 | /** 224 | * Returns the full profile photo of the current user. 225 | */ 226 | public String getProfilePhotoUrl(){ 227 | return this.profilePhotoUrl; 228 | } 229 | 230 | public void setProfilePhotoUrl(String profilePhotoUrl){ 231 | this.profilePhotoUrl = profilePhotoUrl; 232 | } 233 | 234 | /** 235 | * Returns the thumbnail photo of the current user. 236 | */ 237 | public String getProfileThumbnailUrl(){ 238 | return this.profileThumbnailUrl; 239 | } 240 | 241 | public void setProfileThumbnailUrl(String profileThumbnailUrl){ 242 | this.profileThumbnailUrl = profileThumbnailUrl; 243 | } 244 | 245 | @Override 246 | public String toString() 247 | { 248 | return userId+ ","+ 249 | userName+ ","+ 250 | firstName+","+ 251 | lastName+ ","+ 252 | email+ ","+ 253 | fullName+ ","+ 254 | locale+ ","+ 255 | language+ ","+ 256 | timeZone+ ","+ 257 | profileId+ ","+ 258 | roleId+ ","+ 259 | userType+ ","+ 260 | currencyISOCode+ ","+ 261 | accessibilityMode+ ","+ 262 | profilePhotoUrl+","+ 263 | profileThumbnailUrl; 264 | 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/main/java/canvas/SignedRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, salesforce.com, inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided 6 | * that the following conditions are met: 7 | * 8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the 9 | * following disclaimer. 10 | * 11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 12 | * the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | * 14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or 15 | * promote products derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package canvas; 28 | 29 | import org.apache.commons.codec.binary.Base64; 30 | import org.codehaus.jackson.map.ObjectMapper; 31 | import org.codehaus.jackson.map.ObjectReader; 32 | import org.codehaus.jackson.type.TypeReference; 33 | 34 | import javax.crypto.Mac; 35 | import javax.crypto.SecretKey; 36 | import javax.crypto.spec.SecretKeySpec; 37 | import java.io.IOException; 38 | import java.io.StringWriter; 39 | import java.security.InvalidKeyException; 40 | import java.security.NoSuchAlgorithmException; 41 | import java.util.Arrays; 42 | import java.util.HashMap; 43 | 44 | /** 45 | * 46 | * The utility method can be used to validate/verify the signed request. In this case, 47 | * the signed request is verified that it is from Salesforce and that it has not been tampered with. 48 | *
49 | * This utility class has two methods. One verifies and decodes the request as a Java object the
50 | * other as a JSON String.
51 | *
52 | */
53 | public class SignedRequest {
54 |
55 | public static CanvasRequest verifyAndDecode(String input, String secret) throws SecurityException {
56 |
57 | String[] split = getParts(input);
58 |
59 | String encodedSig = split[0];
60 | String encodedEnvelope = split[1];
61 |
62 | // Deserialize the json body
63 | String json_envelope = new String(new Base64(true).decode(encodedEnvelope));
64 | ObjectMapper mapper = new ObjectMapper();
65 | ObjectReader reader = mapper.reader(CanvasRequest.class);
66 | CanvasRequest canvasRequest;
67 | String algorithm;
68 | try {
69 | canvasRequest = reader.readValue(json_envelope);
70 | algorithm = canvasRequest.getAlgorithm() == null ? "HMACSHA256" : canvasRequest.getAlgorithm();
71 | } catch (IOException e) {
72 | throw new SecurityException(String.format("Error [%s] deserializing JSON to Object [%s]", e.getMessage(), CanvasRequest.class.getName()), e);
73 | }
74 |
75 | verify(secret, algorithm, encodedEnvelope, encodedSig);
76 |
77 | // If we got this far, then the request was not tampered with.
78 | // return the request as a Java object
79 | return canvasRequest;
80 | }
81 |
82 |
83 | public static String verifyAndDecodeAsJson(String input, String secret) throws SecurityException {
84 |
85 | String[] split = getParts(input);
86 |
87 | String encodedSig = split[0];
88 | String encodedEnvelope = split[1];
89 |
90 | String json_envelope = new String(new Base64(true).decode(encodedEnvelope));
91 | ObjectMapper mapper = new ObjectMapper();
92 |
93 | String algorithm;
94 | StringWriter writer;
95 | TypeReference This function does one of three things This function does one of three things ");
160 | for(String methodName : unusedMethods)
161 | sb.append("
");
163 | return sb.toString();
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/main/java/servlets/OAuthServlet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2011, salesforce.com, inc.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided
6 | * that the following conditions are met:
7 | *
8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the
9 | * following disclaimer.
10 | *
11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
12 | * the following disclaimer in the documentation and/or other materials provided with the distribution.
13 | *
14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
15 | * promote products derived from this software without specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | * POSSIBILITY OF SUCH DAMAGE.
25 | */
26 |
27 | package servlets;
28 |
29 | import org.apache.commons.httpclient.HttpClient;
30 | import org.apache.commons.httpclient.HttpException;
31 | import org.apache.commons.httpclient.methods.PostMethod;
32 | import org.json.JSONException;
33 | import org.json.JSONObject;
34 | import org.json.JSONTokener;
35 |
36 | import javax.servlet.ServletException;
37 | import javax.servlet.http.HttpServlet;
38 | import javax.servlet.http.HttpServletRequest;
39 | import javax.servlet.http.HttpServletResponse;
40 | import java.io.IOException;
41 | import java.io.InputStreamReader;
42 | import java.io.UnsupportedEncodingException;
43 | import java.net.URLEncoder;
44 |
45 | /**
46 | * A Servlet for handeling OAuth flow.
47 | * This OAuth Servlet is only provided as an example and is provided as-is
48 | */
49 | public class OAuthServlet extends HttpServlet {
50 | private static final long serialVersionUID = 1L;
51 |
52 | public static final String ACCESS_TOKEN = "ACCESS_TOKEN";
53 | public static final String INSTANCE_URL = "INSTANCE_URL";
54 |
55 | private String clientId = null;
56 | private String clientSecret = null;
57 | private String redirectUri = null;
58 | private String authUrl = null;
59 | private String tokenUrl = null;
60 |
61 | public void init() throws ServletException {
62 |
63 | String environment;
64 |
65 | clientId = this.getInitParameter("clientId");
66 | clientSecret = this.getInitParameter("clientSecret");
67 | redirectUri = this.getInitParameter("redirectUri"); // https://canvas.herokuapp.com/oauth/_callback
68 | environment = this.getInitParameter("environment"); // https://login.salesforce.com
69 |
70 | try {
71 | authUrl = environment
72 | + "/services/oauth2/authorize?response_type=code&client_id="
73 | + clientId + "&redirect_uri="
74 | + URLEncoder.encode(redirectUri, "UTF-8");
75 |
76 | // Nots: &scope=email,read_chatter,... would be added here for oauth scope
77 |
78 | } catch (UnsupportedEncodingException e) {
79 | throw new ServletException(e);
80 | }
81 |
82 | tokenUrl = environment + "/services/oauth2/token";
83 | }
84 |
85 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
86 |
87 | System.out.println("Begin OAuth");
88 |
89 | String accessToken = (String) request.getSession().getAttribute(ACCESS_TOKEN);
90 |
91 | if (accessToken == null) {
92 |
93 | String instanceUrl = null;
94 |
95 | if (request.getRequestURI().endsWith("oauth")) {
96 | // we need to send the user to authorize
97 | response.sendRedirect(authUrl);
98 | return;
99 | } else {
100 | System.out.println("Auth successful - got callback");
101 |
102 | String code = request.getParameter("code");
103 |
104 | HttpClient httpclient = new HttpClient();
105 |
106 | PostMethod post = new PostMethod(tokenUrl);
107 | post.addParameter("code", code);
108 | post.addParameter("grant_type", "authorization_code");
109 | post.addParameter("client_id", clientId);
110 | post.addParameter("client_secret", clientSecret);
111 | post.addParameter("redirect_uri", redirectUri);
112 |
113 | try {
114 | httpclient.executeMethod(post);
115 |
116 | try {
117 | JSONObject authResponse = new JSONObject(
118 | new JSONTokener(new InputStreamReader(
119 | post.getResponseBodyAsStream())));
120 | System.out.println("xAuth response: "
121 | + authResponse.toString(2));
122 |
123 | accessToken = authResponse.getString("access_token");
124 |
125 | // Instance URL is Salesforce specific.
126 | instanceUrl = authResponse.getString("instance_url");
127 |
128 | System.out.println("Got access token: " + accessToken);
129 | } catch (JSONException e) {
130 | e.printStackTrace();
131 | throw new ServletException(e);
132 | }
133 | } catch (HttpException e) {
134 | e.printStackTrace();
135 | throw new ServletException(e);
136 | }
137 | finally {
138 | post.releaseConnection();
139 | }
140 | }
141 |
142 | // Set a session attribute so that other servlets can get the access
143 | // token
144 | request.getSession().setAttribute(ACCESS_TOKEN, accessToken);
145 |
146 | // We also get the instance URL from the OAuth response, so set it
147 | // in the session too
148 | request.getSession().setAttribute(INSTANCE_URL, instanceUrl);
149 | }
150 |
151 | response.sendRedirect(request.getContextPath() + "/index.html");
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/main/java/servlets/ProxyServlet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2011, salesforce.com, inc.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided
6 | * that the following conditions are met:
7 | *
8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the
9 | * following disclaimer.
10 | *
11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
12 | * the following disclaimer in the documentation and/or other materials provided with the distribution.
13 | *
14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
15 | * promote products derived from this software without specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | * POSSIBILITY OF SUCH DAMAGE.
25 | */
26 |
27 | package servlets;
28 |
29 | import javax.servlet.ServletConfig;
30 | import javax.servlet.ServletException;
31 | import javax.servlet.http.HttpServlet;
32 | import javax.servlet.http.HttpServletRequest;
33 | import javax.servlet.http.HttpServletResponse;
34 | import java.io.IOException;
35 | import java.io.InputStream;
36 | import java.io.OutputStream;
37 | import java.net.HttpURLConnection;
38 | import java.net.URL;
39 | import java.util.Enumeration;
40 | import java.util.List;
41 | import java.util.Map;
42 |
43 | /**
44 | * ServerSide proxy for proxying request to remote server to get around cross domain issues.
45 | * This Proxy is only provided as an example and is provided as-is.
46 | */
47 | public class ProxyServlet extends HttpServlet {
48 |
49 | // web.xml config parameter names
50 | private static final String INIT_PARAM_CONNECTION_TIMEOUT = "connection_timeout";
51 | private static final String INIT_PARAM_FOLLOW_REDIRECTS = "follow_redirects";
52 | private static final String INIT_PARAM_READ_TIMEOUT = "read_timeout";
53 | private static final String INIT_PARAM_BUFFER_SIZE = "buffer_size";
54 | private static final String INIT_PARAM_REMOTE_HOST = "remote_host";
55 |
56 | // Configurable variables in web.xml
57 | private int connectionTimeout;
58 | private boolean followRedirects;
59 | private int readTimeout;
60 | private int bufferSize;
61 | private String remoteHost;
62 |
63 | @Override
64 | public void init(ServletConfig config) throws ServletException {
65 | super.init(config);
66 | connectionTimeout = getConfigParam(INIT_PARAM_CONNECTION_TIMEOUT, 5000);
67 | followRedirects = getConfigParam(INIT_PARAM_FOLLOW_REDIRECTS, true);
68 | readTimeout = getConfigParam(INIT_PARAM_READ_TIMEOUT, 0);
69 | bufferSize = getConfigParam(INIT_PARAM_BUFFER_SIZE, 8 * 1024);
70 | remoteHost = getConfigParam(INIT_PARAM_REMOTE_HOST, null);
71 | }
72 |
73 | @Override
74 | protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
75 | if ("PATCH".equals(request.getMethod())) {
76 | invoke("PATCH", request, response);
77 | }
78 | else {
79 | super.service(request, response);
80 | }
81 | }
82 |
83 | @Override
84 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
85 | invoke("GET", req, resp);
86 | }
87 |
88 | @Override
89 | protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
90 | invoke("PUT", req, resp);
91 | }
92 |
93 | @Override
94 | protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
95 | invoke("DELETE", req, resp);
96 | }
97 |
98 | @Override
99 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
100 | invoke("POST", req, resp);
101 | }
102 |
103 | @Override
104 | protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
105 | invoke("HEAD", req, resp);
106 | }
107 |
108 | @Override
109 | protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
110 | invoke("OPTIONS", req, resp);
111 | }
112 |
113 | protected void invoke(String verb, HttpServletRequest request, HttpServletResponse response) throws IOException
114 | {
115 | if (remoteHost == null) {
116 | response.sendError(HttpServletResponse.SC_BAD_REQUEST, "remote_host not configured");
117 | return;
118 | }
119 |
120 | String uri = request.getRequestURI();
121 | String remoteUrl = remoteHost + uri;
122 |
123 | if (request == null || remoteUrl == null || remoteUrl.equalsIgnoreCase(request.getRequestURL().toString())) {
124 | String.format("Bad preconditions remoteUrl [%s] request.getRequestURL() [%s]", remoteUrl, request.getRequestURL().toString());
125 | return;
126 | }
127 |
128 | // Good to Go....
129 |
130 | URL url;
131 | HttpURLConnection urlConnection = null;
132 | InputStream inputStream = null;
133 | try
134 | {
135 | //System.out.println("Producer URL: " + remoteUrl);
136 |
137 | url = new URL(remoteUrl);
138 | urlConnection = (HttpURLConnection)url.openConnection();
139 |
140 | urlConnection.setRequestMethod(verb);
141 | urlConnection.setInstanceFollowRedirects(followRedirects);
142 | urlConnection.setConnectTimeout(connectionTimeout);
143 | urlConnection.setDoInput(true);
144 | urlConnection.setDoOutput(true);
145 | if (readTimeout != 0) urlConnection.setReadTimeout(readTimeout);
146 |
147 | copyRequestHeaders(request, urlConnection);
148 |
149 | if (verb.equals("PUT") || verb.equals("PATCH") || verb.equals("POST") ) {
150 | copyBody(urlConnection, request.getInputStream());
151 | }
152 |
153 | response.setContentType(urlConnection.getContentType());
154 | if (! "gzip".equalsIgnoreCase(urlConnection.getContentEncoding())) {
155 | response.setCharacterEncoding(urlConnection.getContentEncoding());
156 | }
157 |
158 | int responseCode = urlConnection.getResponseCode();
159 | copyResponseHeaders(response, urlConnection);
160 |
161 | response.setStatus(responseCode);
162 |
163 | int contentLength = urlConnection.getContentLength();
164 | response.setContentLength(contentLength);
165 |
166 | if (responseCode != HttpURLConnection.HTTP_NOT_MODIFIED) {
167 | OutputStream outputStream = null;
168 | try
169 | {
170 | inputStream = urlConnection.getInputStream();
171 | outputStream = response.getOutputStream();
172 | byte buffer[] = new byte[bufferSize];
173 | while (true) {
174 | int len = inputStream.read(buffer, 0, bufferSize);
175 | if (len < 0) {
176 | break;
177 | }
178 | outputStream.write(buffer, 0, len);
179 | }
180 | } finally {
181 | try { if (outputStream != null) outputStream.flush(); } catch (IOException e) {}
182 | try { if (outputStream != null) outputStream.close(); } catch (IOException e) {}
183 | }
184 | }
185 |
186 | }
187 | finally {
188 | try { if (inputStream != null) inputStream.close(); } catch (IOException e) {}
189 | try { if (urlConnection != null) urlConnection.disconnect(); } catch (Exception e) {}
190 | }
191 | }
192 |
193 | protected void copyBody(HttpURLConnection httpURLConnection, InputStream body) throws IOException
194 | {
195 | OutputStream outputStream = null;
196 | try {
197 | outputStream = httpURLConnection.getOutputStream();
198 | int val;
199 | while ((val = body.read()) != -1) {
200 | outputStream.write(val);
201 | }
202 | }
203 | finally {
204 | try { if (body != null) body.close(); } catch (IOException e) {}
205 | try { if (outputStream != null) outputStream.flush(); } catch (IOException e) {}
206 | try { if (outputStream != null) outputStream.close(); } catch (IOException e) {}
207 | }
208 | }
209 |
210 | protected void copyRequestHeaders(HttpServletRequest request, HttpURLConnection connection) {
211 |
212 | Enumeration headerNames = request.getHeaderNames();
213 | while (headerNames.hasMoreElements()) {
214 |
215 | String headerName = (String) headerNames.nextElement();
216 | Enumeration headerValues = request.getHeaders(headerName);
217 | while (headerValues.hasMoreElements()) {
218 |
219 | String headerValue = (String) headerValues.nextElement();
220 | if (headerValue != null) {
221 | connection.addRequestProperty(headerName, headerValue);
222 | }
223 | }
224 | }
225 | }
226 |
227 | protected void copyResponseHeaders(HttpServletResponse response, HttpURLConnection connection) {
228 |
229 | Map ");
162 | sb.append("" + methodName + " Welcome to Force.com Canvas on Java
120 |
123 |
126 | For more information on Force.com Canvas Framework, please visit
127 | our developer documentation.
128 |
129 |
124 | Your sample Java application is up and running.
125 | true
if the property exists for the object itself and is not inherited, otherwise false
56 | */
57 | hasOwn: function (obj, prop) {
58 | return oproto.hasOwnProperty.call(obj, prop);
59 | },
60 |
61 | /**
62 | * @description Checks whether an object is currently undefined.
63 | * @param {Object} value The object to check
64 | * @returns {Boolean} true
if the object or value is of type undefined, otherwise false
65 | */
66 | isUndefined: function (value) {
67 | var undef;
68 | return value === undef;
69 | },
70 |
71 | /**
72 | * @description Checks whether object is undefined, null, or an empty string.
73 | * @param {Object} value The object to check
74 | * @returns {Boolean} true
if the object or value is of type undefined, otherwise false
75 | */
76 | isNil: function (value) {
77 | return $.isUndefined(value) || value === null || value === "";
78 | },
79 |
80 | /**
81 | * @description Checks whether a value is a number. This function doesn't resolve strings to numbers.
82 | * @param {Object} value Object to check
83 | * @returns {Boolean} true
if the object or value is a number, otherwise false
84 | */
85 | isNumber: function (value) {
86 | return !!(value === 0 || (value && value.toExponential && value.toFixed));
87 | },
88 |
89 | /**
90 | * @description Checks whether an object is a function.
91 | * @param {Object} value Object to check
92 | * @returns {Boolean} true
if the object or value is a function, otherwise false
93 | */
94 | isFunction: function (value) {
95 | return !!(value && value.constructor && value.call && value.apply);
96 | },
97 |
98 | /**
99 | * @description Checks whether an object is an array.
100 | * @param {Object} value The object to check
101 | * @function
102 | * @returns {Boolean} true
if the object or value is of type array, otherwise false
103 | */
104 | isArray: Array.isArray || function (value) {
105 | return oproto.toString.call(value) === '[object Array]';
106 | },
107 |
108 | /**
109 | * @description Checks whether an object is the argument set for a function
110 | * @param {Object} value The object to check
111 | * @returns {Boolean} true
if the object or value is the argument set for a function, otherwise false
112 | */
113 | isArguments: function (value) {
114 | return !!(value && $.hasOwn(value, 'callee'));
115 | },
116 |
117 | /**
118 | * @description Checks whether the value is of type 'object' and is not null.
119 | * @param {Object} value The object to check
120 | * @returns {Boolean} true
if the object or value is of type Object, otherwise false
121 | */
122 | isObject: function (value) {
123 | return value !== null && typeof value === 'object';
124 | },
125 |
126 | // common functions
127 | //-----------------
128 |
129 | /**
130 | * @description An empty or blank function.
131 | */
132 | nop: function () {
133 | /* no-op */
134 | },
135 |
136 | /**
137 | * @description This function runs the function that is passed to it.
138 | * @param {Function} fn The function to run
139 | */
140 | invoker: function (fn) {
141 | if ($.isFunction(fn)) {
142 | fn();
143 | }
144 | },
145 |
146 | /**
147 | * @description This function always returns the argument.
148 | * @param {Object} obj The object to return, untouched.
149 | * @returns {Object} The argument used for this function call.
150 | */
151 | identity: function (obj) {
152 | return obj;
153 | },
154 |
155 | // @todo consider additional tests for: null, boolean, string, nan, element, regexp... as needed
156 | /**
157 | * @description Calls a defined function for each element in an object
158 | * @param {Object} obj The object to loop through.
159 | It can be an array, an array like object or a map of properties
160 | * @param {Function} it The callback function to run for each element.
161 | * @param {Object} [ctx] The context object to be used for the callback function.
162 | Defaults to the original object if not provided.
163 | */
164 | each: function (obj, it, ctx) {
165 | if ($.isNil(obj)) {
166 | return;
167 | }
168 | var nativ = aproto.forEach, i = 0, l, key;
169 | l = obj.length;
170 | ctx = ctx || obj;
171 | // @todo: looks like native method will not break on return false; maybe throw breaker {}
172 | if (nativ && nativ === obj.forEach) {
173 | obj.forEach(it, ctx);
174 | }
175 | else if ($.isNumber(l)) { // obj is an array-like object
176 | while (i < l) {
177 | if (it.call(ctx, obj[i], i, obj) === false) {
178 | return;
179 | }
180 | i += 1;
181 | }
182 | }
183 | else {
184 | for (key in obj) {
185 | if ($.hasOwn(obj, key) && it.call(ctx, obj[key], key, obj) === false) {
186 | return;
187 | }
188 | }
189 | }
190 | },
191 |
192 | /**
193 | * @description Creates a new array with the results of calling the
194 | function on each element in the object.
195 | * @param {Object} obj The object to use.
196 | * @param {Function} it The callback function to run for each element.
197 | * @param {Object} [ctx] The context object to be used for the callback function.
198 | Defaults to the original object if not provided.
199 | * @returns {Array} The array that results when calling the function on each
200 | element in the object.
201 | */
202 | map: function (obj, it, ctx) {
203 | var results = [], nativ = aproto.map;
204 | if ($.isNil(obj)) {
205 | return results;
206 | }
207 | if (nativ && obj.map === nativ) {
208 | return obj.map(it, ctx);
209 | }
210 | ctx = ctx || obj;
211 | $.each(obj, function (value, i, list) {
212 | results.push(it.call(ctx, value, i, list));
213 | });
214 | return results;
215 | },
216 |
217 | /**
218 | * @description Creates an array containing all the elements of the given object
219 | * @param {Object} obj The object the use in creating the array
220 | * @returns {Array} An array containing all the elements in the object.
221 | */
222 | values: function (obj) {
223 | return $.map(obj, $.identity);
224 | },
225 |
226 | /**
227 | * @description Creates a new array containing the selected elements of the given array.
228 | * @param {Array} array The array to subset.
229 | * @param {Integer} [begin=0] The index that specifies where to start the selection.
230 | * @param {Integer} [end = array.length] The index that specifies where to end the selection.
231 | * @returns {Array} A new array that contains the selected elements.
232 | */
233 | slice: function (array, begin, end) {
234 | /* FF doesn't like undefined args for slice so ensure we call with args */
235 | return aproto.slice.call(array, $.isUndefined(begin) ? 0 : begin, $.isUndefined(end) ? array.length : end);
236 | },
237 |
238 | /**
239 | * @description Creates an array from an object.
240 | * @param {Object} iterable The object to use in creating the array.
241 | * @returns {Array} The new array created from the object.
242 | */
243 | toArray: function (iterable) {
244 | if (!iterable) {
245 | return [];
246 | }
247 | if (iterable.toArray) {
248 | return iterable.toArray;
249 | }
250 | if ($.isArray(iterable)) {
251 | return iterable;
252 | }
253 | if ($.isArguments(iterable)) {
254 | return $.slice(iterable);
255 | }
256 | return $.values(iterable);
257 | },
258 |
259 | /**
260 | * @description Calculates the number of elements in an object
261 | * @param {Object} obj The object to size.
262 | * @returns {Integer} The number of elements in the object.
263 | */
264 | size: function (obj) {
265 | return $.toArray(obj).length;
266 | },
267 |
268 | /**
269 | * @description Calculates the location of an element in an array.
270 | * @param {Array} array The array to check.
271 | * @param {Object} item The item to search for within the array.
272 | * @returns {Integer} The index of the element within the array.
273 | Returns -1 if the element is not found.
274 | */
275 | indexOf: function (array, item) {
276 | var nativ = aproto.indexOf, i, l;
277 | if (!array) {
278 | return -1;
279 | }
280 | if (nativ && array.indexOf === nativ) {
281 | return array.indexOf(item);
282 | }
283 | for (i = 0, l = array.length; i < l; i += 1) {
284 | if (array[i] === item) {
285 | return i;
286 | }
287 | }
288 | return -1;
289 | },
290 |
291 | /**
292 | * @description Removes an element from an array.
293 | * @param {Array} array The array to modify.
294 | * @param {Object} item The element to remove from the array.
295 | */
296 | remove: function (array, item) {
297 | var i = $.indexOf(array, item);
298 | if (i >= 0) {
299 | array.splice(i, 1);
300 | }
301 | },
302 |
303 | /**
304 | * @description Serializes an object into a string that can be used as a URL query string.
305 | * @param {Object|Array} a The array or object to serialize.
306 | * @param {Boolean} [encode=false] Indicates that the string should be encoded.
307 | * @returns {String} A string representing the object as a URL query string.
308 | */
309 | param: function (a, encode) {
310 | var s = [];
311 |
312 | encode = encode || false;
313 |
314 | function add( key, value ) {
315 |
316 | if ($.isNil(value)) {return;}
317 | value = $.isFunction(value) ? value() : value;
318 | if ($.isArray(value)) {
319 | $.each( value, function(v, n) {
320 | add( key, v );
321 | });
322 | }
323 | else {
324 | if (encode) {
325 | s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value);
326 | }
327 | else {
328 | s[ s.length ] = key + "=" + value;
329 | }
330 | }
331 | }
332 |
333 | if ( $.isArray(a)) {
334 | $.each( a, function(v, n) {
335 | add( n, v );
336 | });
337 | } else {
338 | for ( var p in a ) {
339 | if ($.hasOwn(a, p)) {
340 | add( p, a[p]);
341 | }
342 | }
343 | }
344 | return s.join("&").replace(/%20/g, "+");
345 | },
346 |
347 | // strings
348 | //--------
349 | /**
350 | * @description Adds the contents of 2 or more objets to
351 | a destination object.
352 | * @param {Object} dest The destination object to modify.
353 | * @param {Object} mixin1-n An unlimited number of objects to add to the destination.
354 | * @returns {Object} The modified destination object.
355 | */
356 | extend: function (dest /*, mixin1, mixin2, ... */) {
357 | $.each($.slice(arguments, 1), function (mixin, i) {
358 | $.each(mixin, function (value, key) {
359 | dest[key] = value;
360 | });
361 | });
362 | return dest;
363 | },
364 |
365 | /**
366 | * @name Sfdc.canvas.prototypeOf
367 | * @function
368 | * @description Returns the prototype of the specified object
369 | * @param {Object} obj The object for which to find the prototype.
370 | * @returns {Object} The object that is the prototype of the given object.
371 | */
372 | prototypeOf: function (obj) {
373 | var nativ = Object.getPrototypeOf,
374 | proto = '__proto__';
375 | if ($.isFunction(nativ)) {
376 | return nativ.call(Object, obj);
377 | }
378 | else {
379 | if (typeof {}[proto] === 'object') {
380 | return obj[proto];
381 | }
382 | else {
383 | return obj.constructor.prototype;
384 | }
385 | }
386 | },
387 |
388 | /**
389 | * @description Adds a module to the global.Sfdc.canvas object
390 | * @param {String} ns The namespace for the new module.
391 | * @decl {Object} The module to add.
392 | * @returns {Object} The global.Sfdc.canvas object with a new module added.
393 | */
394 | module: function(ns, decl) {
395 | var parts = ns.split('.'), parent = global.Sfdc.canvas, i, length;
396 |
397 | // strip redundant leading global
398 | if (parts[1] === 'canvas') {
399 | parts = parts.slice(2);
400 | }
401 |
402 | length = parts.length;
403 | for (i = 0; i < length; i += 1) {
404 | // create a property if it doesn't exist
405 | if ($.isUndefined(parent[parts[i]])) {
406 | parent[parts[i]] = {};
407 | }
408 | parent = parent[parts[i]];
409 | }
410 |
411 | if ($.isFunction(decl)) {
412 | decl = decl();
413 | }
414 | return $.extend(parent, decl);
415 | },
416 |
417 | // dom
418 | //----
419 | /**
420 | * @description Returns the DOM element in the current document with the given ID
421 | * @param {String} id The id to find in the DOM
422 | * @returns {DOMElement} The DOM element with the given ID, null if the element does not exist.
423 | */
424 | byId: function (id) {
425 | return doc.getElementById(id);
426 | },
427 | /**
428 | * @description Returns a set of DOM elements in the current document with the given class names
429 | * @param {String} clazz The class names to find in the DOM. Multiple
430 | classnames can be given, separated by whitespace
431 | * @returns {Array} Set of DOM elements that all have the given class name
432 | */
433 | byClass: function (clazz) {
434 | return doc.getElementsByClassName(clazz);
435 | },
436 | /**
437 | * @description Returns the value for the given attribute name on the given DOM element.
438 | * @param {DOMElement} el The element on which to check the attribute.
439 | * @param {String} name The name of the attribute for which to find a value
440 | * @returns {String} The given attribute's value.
441 | */
442 | attr : function(el, name) {
443 | var a = el.attributes, i;
444 | for (i = 0; i < a.length; i += 1) {
445 | if (name === a[i].name) {
446 | return a[i].value;
447 | }
448 | }
449 | }
450 | },
451 |
452 | readyHandlers = [],
453 |
454 | ready = function () {
455 | ready = $.nop;
456 | $.each(readyHandlers, $.invoker);
457 | readyHandlers = null;
458 | },
459 | /**
460 | * @description
461 | * @param {Function} cb The function to run when ready.
462 | */
463 | canvas = function (cb) {
464 | if ($.isFunction(cb)) {
465 | readyHandlers.push(cb);
466 | }
467 | };
468 |
469 | (function () {
470 | var ael = 'addEventListener',
471 | tryReady = function () {
472 | if (/loaded|complete/.test(doc.readyState)) {
473 | ready();
474 | }
475 | else if (readyHandlers) {
476 | setTimeout(tryReady, 30);
477 | }
478 | };
479 |
480 | if (doc[ael]) {
481 | doc[ael]('DOMContentLoaded', ready, false);
482 | }
483 |
484 | tryReady();
485 |
486 | if (global[ael]) {
487 | global[ael]('load', ready, false);
488 | }
489 | else if (global.attachEvent) {
490 | global.attachEvent('onload', ready);
491 | }
492 | }());
493 |
494 | $.each($, function (fn, name) {
495 | canvas[name] = fn;
496 | });
497 |
498 | if (!global.Sfdc) {
499 | global.Sfdc = {};
500 | }
501 |
502 | global.Sfdc.canvas = canvas;
503 |
504 |
505 | }(this));
--------------------------------------------------------------------------------
/src/main/webapp/sdk/js/client.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2011, salesforce.com, inc.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided
6 | * that the following conditions are met:
7 | *
8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the
9 | * following disclaimer.
10 | *
11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
12 | * the following disclaimer in the documentation and/or other materials provided with the distribution.
13 | *
14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
15 | * promote products derived from this software without specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | * POSSIBILITY OF SUCH DAMAGE.
25 | */
26 | /**
27 | *@namespace Sfdc.canvas.client
28 | *@name Sfdc.canvas.client
29 | */
30 | (function ($$) {
31 |
32 | "use strict";
33 |
34 |
35 | var module = (function() /**@lends module */ {
36 |
37 | var purl, cbs = {}, seq = 0;
38 | /**
39 | * @description
40 | * @function
41 | * @returns The url of the Parent Window
42 | */
43 | function getParentUrl() {
44 | // This relies on the parent passing it in. If it doesn't we can still recover.
45 | if (purl) {return purl;}
46 | var h = document.location.hash;
47 | if (h) {
48 | h = decodeURIComponent(h.replace(/^#/, ''));
49 | purl = (h.substring(0, 4) === "http") ? h : null;
50 | }
51 | return purl;
52 | }
53 |
54 | function callbacker(message) {
55 | if (message && message.data) {
56 | // If the server is telling us the access_token is invalid, wipe it clean.
57 | if (message.data.status === 401 &&
58 | $$.isArray(message.data.payload) &&
59 | message.data.payload[0].errorCode &&
60 | message.data.payload[0].errorCode === "INVALID_SESSION_ID") {
61 | // Session has expired logout.
62 | $$.oauth.logout();
63 | }
64 | if ($$.isFunction(cbs[message.data.seq])) {
65 | cbs[message.data.seq](message.data);
66 | }
67 | else {
68 | // This can happen when the user switches out canvas apps real quick,
69 | // before the request from the last canvas app have finish processing.
70 | // We will ignore any of these results as the canvas app is no longer active to
71 | // respond to the results.
72 | }
73 | }
74 | }
75 |
76 | function postit(clientscb, message) {
77 | // need to keep a mapping from request to callback, otherwise
78 | // wrong callbacks get called. Unfortunately, this is the only
79 | // way to handle this as postMessage acts more like topic/queue.
80 | // limit the sequencers to 100 avoid out of memory errors
81 | seq = (seq > 100) ? 0 : seq + 1;
82 | cbs[seq] = clientscb;
83 | var wrapped = {seq : seq, body : message};
84 | $$.xd.post(wrapped, getParentUrl(), parent);
85 | }
86 |
87 | /**
88 | * @description Get the context for the current user and organization
89 | * @public
90 | * @name Sfdc.canvas.client#ctx
91 | * @function
92 | * @param {Function} clientscb Callback function to run when the call to ctx is complete
93 | * @param {String} token OAuth token to send.
94 | * @example
95 | * // Gets context in the canvas app.
96 | *
97 | * function callback(msg) {
98 | * if (msg.status !== 200) {
99 | * alert("Error: " + msg.status);
100 | * return;
101 | * }
102 | * alert("Payload: ", msg.payload);
103 | * }
104 | * var ctxlink = connect.byId("ctxlink");
105 | * var oauthtoken = connect.oauth.token();
106 | * ctxlink.onclick=function() {
107 | * connect.client.ctx(callback, oauthtoken)};
108 | * }
109 | */
110 | function getContext(clientscb, token) {
111 | token = token || $$.oauth.token();
112 | postit(clientscb, {type : "ctx", accessToken : token});
113 | }
114 |
115 | /**
116 | * @description Perform a cross-domain, asynchronous HTTP request.
117 |
Note: this should not be used for same domain requests.
118 | * @param {String} url The URL to which the request is sent
119 | * @param {Object} settings A set of key/value pairs to configure the request.
120 |
The success setting is required at minimum and should be a callback function
121 | * @name Sfdc.canvas.client#ajax
122 | * @function
123 | * @throws illegalArgumentException if the URL is missing or the settings object does not contain a success callback function.
124 | * @example
125 | * //Posting To a Chatter Feed:
126 | * var sr = JSON.parse('<%=signedRequestJson%>');
127 | * var url = sr.context.links.chatterFeedsUrl+"/news/"
128 | * +sr.context.user.userId+"/feed-items";
129 | * var body = {body : {messageSegments : [{type: "Text", text: "Some Chatter Post"}]}};
130 | * connect.client.ajax(url,
131 | * {token : sr.oauthToken,
132 | * method: 'POST',
133 | * contentType: "application/json",
134 | * data: JSON.stringify(body),
135 | * success : function(data) {
136 | * if (201 === data.status) {
137 | * alert("Success"
138 | * }
139 | * }
140 | * });
141 | * @example
142 | * // Gets a List of Chatter Users:
143 | * // Paste the signed request string into a JavaScript object for easy access.
144 | * var sr = JSON.parse('<%=signedRequestJson%>');
145 | * // Reference the Chatter user's URL from Context.Links object.
146 | * var chatterUsersUrl = sr.context.links.chatterUsersUrl;
147 | *
148 | * // Make an XHR call back to salesforce through the supplied browser proxy.
149 | * connect.client.ajax(chatterUsersUrl,
150 | * {token : sr.oauthToken,
151 | * success : function(data){
152 | * // Make sure the status code is OK.
153 | * if (data.status === 200) {
154 | * // Alert with how many Chatter users were returned.
155 | * alert("Got back " + data.payload.users.length +
156 | * " users"); // Returned 2 users
157 | * }
158 | * })};
159 | */
160 | function ajax(url, settings) {
161 |
162 | var token = settings.token || $$.oauth.token();
163 | var config,
164 | defaults = {
165 | method: 'GET',
166 | async: true,
167 | contentType: "application/json",
168 | headers: {"Authorization" : "OAuth " + token,
169 | "Accept" : "application/json"},
170 | data: null
171 | };
172 |
173 | if (!url) {
174 | throw {name : "illegalArgumentException" , message : "url required"};
175 | }
176 | if (!settings || !$$.isFunction(settings.success)) {
177 | throw {name : "illegalArgumentException" , message : "setting.success missing."};
178 | }
179 |
180 | var ccb = settings.success;
181 | config = $$.extend(defaults, settings || {});
182 | // Remove any listeners as functions cannot get marshaled.
183 | config.success = undefined;
184 | config.failure = undefined;
185 | postit(ccb, {type : "ajax", accessToken : token, url : url, config : config});
186 | }
187 |
188 | function token(t) {
189 | $$.oauth.token(t);
190 | }
191 |
192 | $$.xd.receive(callbacker, getParentUrl());
193 |
194 | return {
195 | ctx : getContext,
196 | ajax : ajax,
197 | token : token
198 | };
199 | }());
200 |
201 | $$.module('Sfdc.canvas.client', module);
202 |
203 | }(Sfdc.canvas));
--------------------------------------------------------------------------------
/src/main/webapp/sdk/js/cookies.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2011, salesforce.com, inc.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided
6 | * that the following conditions are met:
7 | *
8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the
9 | * following disclaimer.
10 | *
11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
12 | * the following disclaimer in the documentation and/or other materials provided with the distribution.
13 | *
14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
15 | * promote products derived from this software without specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | * POSSIBILITY OF SUCH DAMAGE.
25 | */
26 | /**
27 | *@namespace Sfdc.canvas.cookies
28 | *@name Sfdc.canvas.cookies
29 | */
30 | (function ($$) {
31 |
32 | "use strict";
33 |
34 | var module = (function() {
35 |
36 | function isSecure()
37 | {
38 | return window.location.protocol === 'https:';
39 | }
40 |
41 | /**
42 | * @name Sfdc.canvas.cookies#set
43 | * @function
44 | * @description Create a cookie
45 | * @param {String} name Cookie name
46 | * @param {String} value Cookie value
47 | * @param {Integer} [days] Number of days for the cookie to remain active.
48 | If not provided, the cookie never expires
49 | */
50 | function set(name, value, days) {
51 | var expires = "", date;
52 | if (days) {
53 | date = new Date();
54 | date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
55 | expires = "; expires=" + date.toGMTString();
56 | }
57 | else {
58 | expires = "";
59 | }
60 | document.cookie = name + "=" + value + expires + "; path=/" + ((isSecure() === true) ? "; secure" : "");
61 | }
62 |
63 | /**
64 | * @name Sfdc.canvas.cookies#get
65 | * @function
66 | * @description Get the cookie with the specified name
67 | * @param {String} name The name of the cookie to retrieve
68 | * @returns The value of the cookie if the name is found, otherwise null
69 | */
70 | function get(name) {
71 | var nameEQ, ca, c, i;
72 |
73 | if ($$.isUndefined(name)) {
74 | return document.cookie.split(';');
75 | }
76 |
77 | nameEQ = name + "=";
78 | ca = document.cookie.split(';');
79 | for (i = 0; i < ca.length; i += 1) {
80 | c = ca[i];
81 | while (c.charAt(0) === ' ') {c = c.substring(1, c.length);}
82 | if (c.indexOf(nameEQ) === 0) {
83 | return c.substring(nameEQ.length, c.length);
84 | }
85 | }
86 | return null;
87 | }
88 |
89 | /**
90 | * @name Sfdc.canvas.cookies#remove
91 | * @function
92 | * @description Remove the specified cookie by setting the expiry date to one day ago
93 | * @param {String} name The name of the cookie to remove.
94 | */
95 | function remove(name) {
96 | set(name, "", -1);
97 | }
98 |
99 | return {
100 | set : set,
101 | get : get,
102 | remove : remove
103 | };
104 | }());
105 |
106 |
107 | $$.module('Sfdc.canvas.cookies', module);
108 |
109 | }(Sfdc.canvas));
--------------------------------------------------------------------------------
/src/main/webapp/sdk/js/oauth.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2011, salesforce.com, inc.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided
6 | * that the following conditions are met:
7 | *
8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the
9 | * following disclaimer.
10 | *
11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
12 | * the following disclaimer in the documentation and/or other materials provided with the distribution.
13 | *
14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
15 | * promote products derived from this software without specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | * POSSIBILITY OF SUCH DAMAGE.
25 | */
26 |
27 | /**
28 | *@namespace Sfdc.canvas.oauth
29 | *@name Sfdc.canvas.oauth
30 | */
31 | (function ($$) {
32 |
33 | "use strict";
34 |
35 | var module = (function() {
36 |
37 | var accessToken,
38 | instanceUrl,
39 | childWindow;
40 |
41 | function init() {
42 | // Get the access token from the cookie (needed to survive refresh),
43 | // and then remove the cookie per security's request.
44 | accessToken = $$.cookies.get("access_token");
45 | $$.cookies.remove("access_token");
46 | }
47 |
48 | function query(params) {
49 | var r = [], n;
50 | if (!$$.isUndefined(params)) {
51 | for (n in params) {
52 | if (params.hasOwnProperty(n)) {
53 | // probably should encode these
54 | r.push(n + "=" + params[n]);
55 | }
56 | }
57 | return "?" + r.join('&');
58 | }
59 | return '';
60 | }
61 | /**
62 | *@private
63 | */
64 | function refresh() {
65 | // Temporarily set the oauth token in a cookie and then remove it
66 | // after the refresh.
67 | $$.cookies.set("access_token", accessToken);
68 | self.location.reload();
69 | }
70 | /**
71 | * @name Sfdc.canvas.oauth#login
72 | * @function
73 | * @description Opens the OAuth popup window to retrieve an OAuth token
74 | * @param {Object} ctx Context object that contains the url, the response type, the client id and callback url
75 | * @docneedsimprovement
76 | * @example
77 | * function clickHandler(e)
78 | * {
79 | * var uri;
80 | * if (! connect.oauth.loggedin())
81 | * {
82 | * uri = connect.oauth.loginUrl();
83 | * connect.oauth.login(
84 | * {uri : uri,
85 | * params: {
86 | * response_type : "token",
87 | * client_id : "<%=consumerKey%>",
88 | * redirect_uri : encodeURIComponent("/sdk/callback.html")
89 | * }});
90 | * } else {
91 | * connect.oauth.logout();
92 | * }
93 | * return false;
94 | * }
95 | */
96 | function login(ctx) {
97 | var uri;
98 |
99 | ctx = ctx || {};
100 | uri = ctx.uri || "/rest/oauth2";
101 | ctx.params = ctx.params || {state : ""};
102 | ctx.params.state = ctx.params.state || ctx.callback || window.location.pathname; // @TODO REVIEW THIS
103 | ctx.params.display= ctx.params.display || 'popup';
104 | uri = uri + query(ctx.params);
105 | childWindow = window.open(uri, 'OAuth', 'status=0,toolbar=0,menubar=0,resizable=0,scrollbars=1,top=50,left=50,height=500,width=680');
106 | }
107 |
108 | /**
109 | * @name Sfdc.canvas.oauth#token
110 | * @function
111 | * @description Sets, gets or removes the access_token
from this JS object
112 |
113 | If the 't' parameter is not passed in, the current value for the access_token
value is returned.
114 | If the the 't' parameter is null, the access_token
value is removed.
115 | Note: for longer term storage of the OAuth token store it server side in the session, access tokens
116 | should never be stored in cookies.
117 | Otherwise the access_token
value is set to the 't' parameter and then returned.
118 | * @param {String} [t] The oauth token to set as the access_token
value
119 | * @returns {String} The resulting access_token
value if set, otherwise null
120 | */
121 | function token(t) {
122 | if (arguments.length === 0) {
123 | if (!$$.isNil(accessToken)) {return accessToken;}
124 | }
125 | else {
126 | accessToken = t;
127 | }
128 |
129 | return accessToken;
130 | }
131 |
132 | /**
133 | * @name Sfdc.canvas.oauth#instance
134 | * @function
135 | * @description Sets, gets or removes the instance_url
cookie
136 |
137 | If the 'i' parameter is not passed in, the current value for the instance_url
cookie is returned.
138 | If the 'i' parameter is null, the instance_url
cookie is removed.
139 | Otherwise the instance_url
cookie value is set to the 'i' parameter and then returned.
140 | * @param {String} [i] The value to set as the instance_url
cookie
141 | * @returns {String} The resulting instance_url
cookie value if set, otherwise null
142 | */
143 | function instance(i) {
144 | if (arguments.length === 0) {
145 | if (!$$.isNil(instanceUrl)) {return instanceUrl;}
146 | instanceUrl = $$.cookies.get("instance_url");
147 | }
148 | else if (i === null) {
149 | $$.cookies.remove("instance_url");
150 | instanceUrl = null;
151 | }
152 | else {
153 | $$.cookies.set("instance_url", i);
154 | instanceUrl = i;
155 | }
156 | return instanceUrl;
157 | }
158 |
159 | /**
160 | *@private
161 | */
162 | // Example Results of tha hash....
163 | // Name [access_token] Value [00DU0000000Xthw!ARUAQMdYg9ScuUXB5zPLpVyfYQr9qXFO7RPbKf5HyU6kAmbeKlO3jJ93gETlJxvpUDsz3mqMRL51N1E.eYFykHpoda8dPg_z]
164 | // Name [instance_url] Value [https://na12.salesforce.com]
165 | // Name [id] Value [https://login.salesforce.com/id/00DU0000000XthwMAC/005U0000000e6PoIAI]
166 | // Name [issued_at] Value [1331000888967]
167 | // Name [signature] Value [LOSzVZIF9dpKvPU07icIDOf8glCFeyd4vNGdj1dhW50]
168 | // Name [state] Value [/crazyrefresh.html]
169 | function parseHash(hash) {
170 | var i, nv, nvp, n, v;
171 |
172 | if (! $$.isNil(hash)) {
173 | if (hash.indexOf('#') === 0) {
174 | hash = hash.substr(1);
175 | }
176 | nvp = hash.split("&");
177 |
178 | for (i = 0; i < nvp.length; i += 1) {
179 | nv = nvp[i].split("=");
180 | n = nv[0];
181 | v = decodeURIComponent(nv[1]);
182 | if ("access_token" === n) {
183 | token(v);
184 | }
185 | else if ("instance_url" === n) {
186 | instance(v);
187 | }
188 | }
189 | }
190 | }
191 |
192 | /**
193 | * @name Sfdc.canvas.oauth#checkChildWindowStatus
194 | * @function
195 | * @description Refreshes the parent window only if the child window is closed.
196 | */
197 | function checkChildWindowStatus() {
198 | if (!childWindow || childWindow.closed) {
199 | refresh();
200 | }
201 | }
202 |
203 | /**
204 | * @name Sfdc.canvas.oauth#childWindowUnloadNotification
205 | * @function
206 | * @description Parses the hash value that is passed in and sets the
207 | access_token
and instance_url
cookies if they exist. Use during
208 | User-Agent OAuth Authentication Flow to pass the OAuth token
209 | * @param {String} hash Typically a string of key-value pairs delimited by
210 | the ampersand character.
211 | * @example
212 | * Sfdc.canvas.oauth.childWindowUnloadNotification(self.location.hash);
213 | */
214 | function childWindowUnloadNotification(hash) {
215 | // Here we get notification from child window. Here we can decide if such notification is
216 | // raised because user closed child window, or because user is playing with F5 key.
217 | // NOTE: We can not trust on "onUnload" event of child window, because if user reload or refresh
218 | // such window in fact he is not closing child. (However "onUnload" event is raised!)
219 | //checkChildWindowStatus();
220 | parseHash(hash);
221 | setTimeout(window.Sfdc.canvas.oauth.checkChildWindowStatus, 50);
222 | }
223 |
224 | /**
225 | * @name Sfdc.canvas.oauth#logout
226 | * @function
227 | * @description Removes the access_token
oauth token from this object.
228 | */
229 | function logout() {
230 | // Remove the oauth token and refresh the browser
231 | token(null);
232 | var home = $$.cookies.get("home");
233 | window.location = home || window.location;
234 | }
235 |
236 | /**
237 | * @name Sfdc.canvas.oauth#loggedin
238 | * @function
239 | * @description Returns the login state
240 | * @returns {Boolean} true
if the access_token
is available in this JS object.
241 | * Note: access tokens
(i.e. OAuth tokens) should be stored server side for more durability.
242 | * Never store OAuth tokens in cookies as this can lead to a security risk.
243 | */
244 | function loggedin() {
245 | return !$$.isNil(token());
246 | }
247 |
248 | /**
249 | * @name Sfdc.canvas.oauth#loginUrl
250 | * @function
251 | * @description Calculates and returns the url for the OAuth authorization service
252 | * @returns {String} The url for the OAuth authorization service or null if there is
253 | * not a value for loginUrl in the current url's query string.
254 | */
255 | function loginUrl() {
256 | var i, nvs, nv, q = self.location.search;
257 |
258 | if (q) {
259 | q = q.substring(1);
260 | nvs = q.split("&");
261 | for (i = 0; i < nvs.length; i += 1)
262 | {
263 | nv = nvs[i].split("=");
264 | if ("loginUrl" === nv[0]) {
265 | return decodeURIComponent(nv[1]) + "/services/oauth2/authorize";
266 | }
267 | }
268 | }
269 | // Maybe throw exception here, otherwise default to something better
270 | return null;
271 | }
272 |
273 | return {
274 | init : init,
275 | login : login,
276 | logout : logout,
277 | loggedin : loggedin,
278 | loginUrl : loginUrl,
279 | token : token,
280 | instance : instance,
281 | checkChildWindowStatus : checkChildWindowStatus,
282 | childWindowUnloadNotification: childWindowUnloadNotification
283 | };
284 | }());
285 |
286 | $$.module('Sfdc.canvas.oauth', module);
287 |
288 | $$.oauth.init();
289 |
290 | }(Sfdc.canvas));
--------------------------------------------------------------------------------
/src/main/webapp/sdk/js/xd.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2011, salesforce.com, inc.
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided
6 | * that the following conditions are met:
7 | *
8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the
9 | * following disclaimer.
10 | *
11 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
12 | * the following disclaimer in the documentation and/or other materials provided with the distribution.
13 | *
14 | * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
15 | * promote products derived from this software without specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
18 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | * POSSIBILITY OF SUCH DAMAGE.
25 | */
26 | /**
27 | *@namespace Sfdc.canvas.xd
28 | *@name Sfdc.canvas.xd
29 | */
30 | (function ($$, window) {
31 |
32 | "use strict";
33 |
34 | var module = (function() {
35 |
36 | var internalCallback;
37 | /**
38 | * @lends Sfdc.canvas.xd
39 | */
40 |
41 | /**
42 | * @name Sfdc.canvas.xd#post
43 | * @function
44 | * @description Pass a message to the target url
45 | * @param {String} message The message to send
46 | * @param {String} target_url Specifies what the origin of the target must be for the event to be dispatched.
47 | * @param {String} [target] The window that is the message's target. Defaults to the parent of the current window.
48 | */
49 | function postMessage(message, target_url, target) {
50 |
51 | // strip out just the {scheme}://{host}:{port} - remove any path and query string information
52 | var otherWindow = (target_url) ? target_url.replace( /([^:]+:\/\/[^\/]+).*/, '$1') : "*";
53 | target = target || parent; // default to parent
54 | if (window.postMessage) {
55 | // the browser supports window.postMessage, so call it with a targetOrigin
56 | // set appropriately, based on the target_url parameter.
57 | target.postMessage(message, otherWindow);
58 | }
59 | }
60 |
61 | /**
62 | * @name Sfdc.canvas.xd#receive
63 | * @function Runs the callback function when the message event is received.
64 | * @param {Function} callback Function to run when the message event is received
65 | if the event origin is acceptable.
66 | * @param {String} source_origin The origin of the desired events
67 | */
68 | function receiveMessage(callback, source_origin) {
69 |
70 | // browser supports window.postMessage (if not not supported for pilot - removed per securities request)
71 | if (window.postMessage) {
72 | // bind the callback to the actual event associated with window.postMessage
73 | if (callback) {
74 | internalCallback = function(e) {
75 | if ((typeof source_origin === 'string' && e.origin !== source_origin)
76 | || ($$.isFunction(source_origin) && source_origin(e.origin) === false)) {
77 | return false;
78 | }
79 | callback(e);
80 | };
81 | }
82 | if (window.addEventListener) {
83 | window.addEventListener('message', internalCallback, false);
84 | } else {
85 | window.attachEvent('onmessage', internalCallback);
86 | }
87 | }
88 | }
89 |
90 | /**
91 | * @name Sfdc.canvas.xd#remove
92 | * @function
93 | * @description Removes the message event listener
94 | * @public
95 | */
96 | function removeListener() {
97 |
98 | // browser supports window.postMessage
99 | if (window.postMessage) {
100 | if (window.removeEventListener) {
101 | window.removeEventListener('message', internalCallback, false);
102 | } else {
103 | window.detachEvent('onmessage', internalCallback);
104 | }
105 | }
106 | }
107 |
108 | return {
109 | post : postMessage,
110 | receive : receiveMessage,
111 | remove : removeListener
112 | };
113 | }());
114 |
115 | $$.module('Sfdc.canvas.xd', module);
116 |
117 | }(Sfdc.canvas, this));
--------------------------------------------------------------------------------
/src/main/webapp/signed-request.jsp:
--------------------------------------------------------------------------------
1 | <%--
2 | Copyright (c) 2011, salesforce.com, inc.
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided
6 | that the following conditions are met:
7 |
8 | Redistributions of source code must retain the above copyright notice, this list of conditions and the
9 | following disclaimer.
10 |
11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
12 | the following disclaimer in the documentation and/or other materials provided with the distribution.
13 |
14 | Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
15 | promote products derived from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
18 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
19 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | POSSIBILITY OF SUCH DAMAGE.
25 | --%>
26 |
27 | <%@ page import="canvas.SignedRequest" %>
28 | <%@ page import="canvas.ToolingAPI" %>
29 | <%@ page import="java.util.Map" %>
30 | <%
31 | // Pull the signed request out of the request body and verify/decode it.
32 | MapHello Apex Developer!
72 | Force.com Canvas Tooling API Demo - Unused Apex Methods
73 | You may have some unused Apex Methods in this Org
77 | Below is a list of unused methods calculated via the Tooling API:
78 |
80 |
89 |
81 |
84 | Unused Methods:
82 | <%=ToolingAPI.getUnusedApexMethods(signedRequest[0], yourConsumerSecret)%>
83 |
85 |
88 |
86 | Note: References made by Visualforce component bindings are not currently checked.
87 | Canvas Request
94 | Below is some information received in the Canvas Request:
95 |
97 |
107 |
98 |
102 |
99 | First Name:
100 | Last Name:
101 |
103 |
106 |
104 | Company:
105 | Congratulations!
40 |
43 |
If you are seeing this page inside of an app within Salesforce,
48 | you must set the "Access Method" for the application to "Signed Request (POST)".
49 |
50 |
44 | Your Heroku Java application is up and running.
45 | To see Force.com Canvas Framework specific functionality, please use this app in a Force.com Canvas
46 | enabled organization.
47 |