├── 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 | ![Screenshot](http://andrewfawcett.files.wordpress.com/2013/02/apexanalysis.png) 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 | 5 | 4.0.0 6 | sfdc 7 | 1.0-SNAPSHOT 8 | Salesforce Canvas Java Template 9 | sfdc-canvas-java-template 10 | 11 | 12 | 1.6 13 | UTF-8 14 | 15 | 16 | 17 | 18 | 19 | javax.servlet 20 | servlet-api 21 | 2.5 22 | 23 | 24 | org.mortbay.jetty 25 | jsp-2.1-glassfish 26 | 2.1.v20100127 27 | 28 | 29 | 30 | org.codehaus.jackson 31 | jackson-mapper-asl 32 | 1.9.0 33 | 34 | 35 | 36 | org.eclipse.jetty 37 | jetty-webapp 38 | 7.6.0.v20120127 39 | 40 | 41 | 42 | asm 43 | asm 44 | 3.2 45 | 46 | 47 | 48 | 49 | org.json 50 | json 51 | 20080701 52 | 53 | 54 | 55 | 56 | commons-httpclient 57 | commons-httpclient 58 | 3.1 59 | 60 | 61 | commons-codec 62 | commons-codec 63 | 64 | 65 | 66 | 67 | 68 | 69 | commons-codec 70 | commons-codec 71 | 1.4 72 | 73 | 74 | 75 | com.sun.xml.ws 76 | jaxws-rt 77 | 2.2.6 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-compiler-plugin 87 | 2.3.2 88 | 89 | ${java.version} 90 | ${java.version} 91 | 92 | 93 | 94 | 95 | org.codehaus.mojo 96 | appassembler-maven-plugin 97 | 1.1.1 98 | 99 | target 100 | 101 | 102 | Main 103 | webapp 104 | 105 | 106 | 107 | 108 | 109 | execution1 110 | package 111 | 112 | assemble 113 | 114 | 115 | 116 | 117 | 118 | org.jvnet.jax-ws-commons 119 | jaxws-maven-plugin 120 | 2.2 121 | 122 | 123 | 124 | wsimport 125 | 126 | 127 | src/bindings 128 | src/resources 129 | true 130 | 131 | 132 | 133 | 134 | 135 | com.sun.xml.ws 136 | jaxws-tools 137 | 2.1.7 138 | 139 | 140 | org.jvnet.staxex 141 | stax-ex 142 | 143 | 144 | 145 | 146 | org.jvnet.staxex 147 | stax-ex 148 | 1.2 149 | 150 | 151 | javax.xml.stream 152 | stax-api 153 | 154 | 155 | 156 | 157 | 158 | 159 | org.apache.maven.plugins 160 | maven-dependency-plugin 161 | 2.3 162 | 163 | 164 | validate 165 | 166 | copy 167 | 168 | 169 | ${project.build.directory}/endorsed 170 | true 171 | 172 | 173 | javax.xml.bind 174 | jaxb-api 175 | 2.2.4 176 | jar 177 | 178 | 179 | javax.xml.ws 180 | jaxws-api 181 | 2.2.8 182 | jar 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /src/bindings/custombinding.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/Main.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 | import org.eclipse.jetty.server.Connector; 28 | import org.eclipse.jetty.server.Server; 29 | import org.eclipse.jetty.server.bio.SocketConnector; 30 | import org.eclipse.jetty.server.ssl.SslSocketConnector; 31 | import org.eclipse.jetty.webapp.WebAppContext; 32 | 33 | /** 34 | * 35 | * This class launches the web application in an embedded Jetty container. 36 | * This is the entry point to your application. The Java command that is used for 37 | * launching should fire this main method. 38 | * 39 | * @author John Simone 40 | */ 41 | public class Main { 42 | 43 | /** 44 | * @param args 45 | */ 46 | public static void main(String[] args) throws Exception{ 47 | 48 | boolean heroku = false; 49 | // A hacky way to determine if we are running on heroku or not 50 | String basedir = (String)System.getProperty("basedir"); 51 | if (basedir != null && basedir.endsWith("/app/target")) { 52 | heroku = true; 53 | } 54 | 55 | if (!heroku) { 56 | 57 | System.out.println("Looks like we are NOT running on heroku."); 58 | 59 | String webappDirLocation = "src/main/webapp/"; 60 | 61 | String webPort = System.getenv("PORT"); 62 | if(webPort == null || webPort.isEmpty()) { 63 | webPort = "8080"; 64 | } 65 | String sslPort = System.getenv("SSLPORT"); 66 | if(sslPort == null || sslPort.isEmpty()) { 67 | sslPort = System.getenv("SSL_PORT"); 68 | if(sslPort == null || sslPort.isEmpty()) { 69 | sslPort = "8443"; 70 | } 71 | } 72 | 73 | Server server = new Server(Integer.valueOf(Integer.valueOf(webPort))); 74 | 75 | SocketConnector connector = new SocketConnector(); 76 | connector.setPort(Integer.valueOf(webPort)); 77 | 78 | SslSocketConnector sslConnector = new SslSocketConnector(); 79 | sslConnector.setPort(Integer.valueOf(sslPort)); 80 | sslConnector.setKeyPassword("123456"); 81 | sslConnector.setKeystore("keystore"); 82 | 83 | server.setConnectors(new Connector[] { sslConnector, connector }); 84 | WebAppContext root = new WebAppContext(); 85 | 86 | root.setContextPath("/"); 87 | root.setDescriptor(webappDirLocation+"/WEB-INF/web.xml"); 88 | root.setResourceBase(webappDirLocation); 89 | root.setParentLoaderPriority(true); 90 | 91 | server.setHandler(root); 92 | server.start(); 93 | server.join(); 94 | } 95 | else { 96 | 97 | // Heroku does it's own SSL piggyback thing 98 | System.out.println("Looks like we are running on heroku."); 99 | 100 | String webappDirLocation = "src/main/webapp/"; 101 | 102 | //The port that we should run on can be set into an environment variable 103 | //Look for that variable and default to 8080 if it isn't there. 104 | String webPort = System.getenv("PORT"); 105 | if(webPort == null || webPort.isEmpty()) { 106 | webPort = "8080"; 107 | } 108 | 109 | Server server = new Server(Integer.valueOf(webPort)); 110 | WebAppContext root = new WebAppContext(); 111 | 112 | root.setContextPath("/"); 113 | root.setDescriptor(webappDirLocation+"/WEB-INF/web.xml"); 114 | root.setResourceBase(webappDirLocation); 115 | 116 | //Parent loader priority is a class loader setting that Jetty accepts. 117 | //By default Jetty will behave like most web containers in that it will 118 | //allow your application to replace non-server libraries that are part of the 119 | //container. Setting parent loader priority to true changes this behavior. 120 | //Read more here: http://wiki.eclipse.org/Jetty/Reference/Jetty_Classloading 121 | root.setParentLoaderPriority(true); 122 | 123 | server.setHandler(root); 124 | 125 | server.start(); 126 | server.join(); 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/canvas/CanvasClient.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 | package canvas; 27 | 28 | import java.util.HashMap; 29 | import java.util.Map; 30 | 31 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 32 | import org.codehaus.jackson.annotate.JsonProperty; 33 | 34 | /** 35 | * Describes contextual information about the current canvas client (third party app). This Object, 36 | * in JS literal notation, needs to ba passed on all Sfdc.canvas.client requests. 37 | * 38 | */ 39 | @JsonIgnoreProperties(ignoreUnknown=true) 40 | public class CanvasClient 41 | { 42 | private String OAuthToken; 43 | private String clientId; 44 | private String instanceId; 45 | private String targetOrigin; 46 | private String instanceUrl; 47 | 48 | /** 49 | * The scoped OAuth token to be used to subsequent REST calls 50 | */ 51 | @JsonProperty("oauthToken") 52 | public String getOAuthToken() { 53 | return OAuthToken; 54 | } 55 | 56 | /** 57 | * @return The scoped OAuth token to be usd for subsequent REST calls. 58 | */ 59 | @JsonProperty("oauthToken") 60 | public void setOAuthToken(String OAuthToken) { 61 | this.OAuthToken = OAuthToken; 62 | } 63 | 64 | @JsonProperty("clientId") 65 | public String getClientId() { 66 | return clientId; 67 | } 68 | 69 | @JsonProperty("clientId") 70 | public void setClientId(String clientId) { 71 | this.clientId = clientId; 72 | } 73 | 74 | /** 75 | * Unique identifier for this canvas app. This value will be different for each instance of a canvas app, even if 76 | * the same canvas app is placed on a page more than once. 77 | * @return Unique identifier for this canvas app 78 | */ 79 | @JsonProperty("instanceId") 80 | public String getInstanceId() { 81 | return instanceId; 82 | } 83 | 84 | @JsonProperty("instanceId") 85 | public void setInstanceId(String instanceId) { 86 | this.instanceId = instanceId; 87 | } 88 | 89 | /** 90 | * @returns the origin (http://somesalesforcedomain:port) of the parent to the canvas app. This is used so 91 | * the in browser proxy knows where to post the request to. 92 | */ 93 | @JsonProperty("targetOrigin") 94 | public String getTargetOrigin() { 95 | return this.targetOrigin; 96 | } 97 | 98 | @JsonProperty("targetOrigin") 99 | public void setTargetOrigin(String targetOrigin) { 100 | this.targetOrigin = targetOrigin; 101 | } 102 | 103 | /** 104 | * The base url for all subsequent REST call, this has the correct 105 | * Salesforce instance this organization is pinned to. 106 | */ 107 | @JsonProperty("instanceUrl") 108 | public String getInstanceUrl() { 109 | return instanceUrl; 110 | } 111 | 112 | @JsonProperty("instanceUrl") 113 | public void setInstanceUrl(String instanceUrl) { 114 | this.instanceUrl = instanceUrl; 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /src/main/java/canvas/CanvasContext.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 related to canvas applications. 34 | * 35 | *

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> typeRef 96 | = new TypeReference>() { }; 97 | try { 98 | HashMap o = mapper.readValue(json_envelope, typeRef); 99 | writer = new StringWriter(); 100 | mapper.writeValue(writer, o); 101 | algorithm = (String)o.get("algorithm"); 102 | } catch (IOException e) { 103 | throw new SecurityException(String.format("Error [%s] deserializing JSON to Object [%s]", e.getMessage(), 104 | typeRef.getClass().getName()), e); 105 | } 106 | 107 | verify(secret, algorithm, encodedEnvelope, encodedSig); 108 | 109 | // If we got this far, then the request was not tampered with. 110 | // return the request as a JSON string. 111 | return writer.toString(); 112 | } 113 | 114 | private static String[] getParts(String input) { 115 | 116 | if (input == null || input.indexOf(".") <= 0) { 117 | throw new SecurityException(String.format("Input [%s] doesn't look like a signed request", input)); 118 | } 119 | 120 | String[] split = input.split("[.]", 2); 121 | return split; 122 | } 123 | 124 | private static void verify(String secret, String algorithm, String encodedEnvelope, String encodedSig ) 125 | throws SecurityException 126 | { 127 | if (secret == null || secret.trim().length() == 0) { 128 | throw new IllegalArgumentException("secret is null, did you set your environment variable CANVAS_CONSUMER_SECRET?"); 129 | } 130 | 131 | SecretKey hmacKey = null; 132 | try { 133 | byte[] key = secret.getBytes(); 134 | hmacKey = new SecretKeySpec(key, algorithm); 135 | Mac mac = Mac.getInstance(algorithm); 136 | mac.init(hmacKey); 137 | 138 | // Check to see if the body was tampered with 139 | byte[] digest = mac.doFinal(encodedEnvelope.getBytes()); 140 | byte[] decode_sig = new Base64(true).decode(encodedSig); 141 | if (! Arrays.equals(digest, decode_sig)) { 142 | String label = "Warning: Request was tampered with"; 143 | throw new SecurityException(label); 144 | } 145 | } catch (NoSuchAlgorithmException e) { 146 | throw new SecurityException(String.format("Problem with algorithm [%s] Error [%s]", algorithm, e.getMessage()), e); 147 | } catch (InvalidKeyException e) { 148 | throw new SecurityException(String.format("Problem with key [%s] Error [%s]", hmacKey, e.getMessage()), e); 149 | } 150 | 151 | // If we got here and didn't throw a SecurityException then all is good. 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/canvas/ToolingAPI.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013, FinancialForce.com, inc 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without modification, 6 | * are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * - Neither the name of the FinancialForce.com, inc nor the names of its contributors 14 | * may be used to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 20 | * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 23 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | **/ 26 | 27 | package canvas; 28 | 29 | import java.util.ArrayList; 30 | import java.util.Arrays; 31 | import java.util.HashSet; 32 | import java.util.List; 33 | import java.util.Set; 34 | import java.util.TreeSet; 35 | 36 | import com.sforce.soap.tooling.*; 37 | 38 | /** 39 | * This class uses the new Spring'13 Tooling API from Salesforce to determine unused Apex Methods in an org 40 | * 41 | * NOTE: The current version only refers to references made between Apex Classes, further research 42 | * is required to determine if references made view Visualforce bindings are also supported 43 | * 44 | * @author afawcett 45 | */ 46 | public class ToolingAPI { 47 | 48 | public static String getUnusedApexMethods(String input, String secret) 49 | { 50 | // Get oAuth token 51 | CanvasRequest request = SignedRequest.verifyAndDecode(input, secret); 52 | String oAuthToken = request.getClient().getOAuthToken(); 53 | 54 | // Connect to Tooling API 55 | SforceServiceService service = new SforceServiceService(); 56 | SforceServicePortType port = service.getSforceService(); 57 | SessionHeader sessionHeader = new SessionHeader(); 58 | sessionHeader.setSessionId(oAuthToken); 59 | 60 | // Query visible Apex classes (this query does not support querying in packaging orgs) 61 | ApexClass[] apexClasses = 62 | port.query("select Id, Name, Body from ApexClass where NamespacePrefix = null", sessionHeader) 63 | .getRecords().toArray(new ApexClass[0]); 64 | 65 | // Delete existing MetadataContainer? 66 | MetadataContainer[] containers = 67 | port.query("select Id, Name from MetadataContainer where Name = 'UnusedApexMethods'", sessionHeader) 68 | .getRecords().toArray(new MetadataContainer[0]); 69 | if(containers.length>0) 70 | port.delete(Arrays.asList(containers[0].getId()), sessionHeader); 71 | 72 | // Create new MetadataContainer 73 | MetadataContainer container = new MetadataContainer(); 74 | container.setName("UnusedApexMethods"); 75 | List saveResults = port.create(new ArrayList(Arrays.asList(container)), sessionHeader); 76 | String containerId = saveResults.get(0).getId(); 77 | 78 | // Create ApexClassMember's and associate them with the MetadataContainer 79 | List apexClassMembers = new ArrayList(); 80 | for(ApexClass apexClass : apexClasses) 81 | { 82 | ApexClassMember apexClassMember = new ApexClassMember(); 83 | apexClassMember.setBody(apexClass.getBody()); 84 | apexClassMember.setContentEntityId(apexClass.getId()); 85 | apexClassMember.setMetadataContainerId(containerId); 86 | apexClassMembers.add(apexClassMember); 87 | } 88 | saveResults = port.create(new ArrayList(apexClassMembers), sessionHeader); 89 | List apexClassMemberIds = new ArrayList(); 90 | for(SaveResult saveResult : saveResults) 91 | apexClassMemberIds.add(saveResult.getId()); 92 | 93 | // Create ContainerAysncRequest to deploy the (check only) the Apex Classes and thus obtain the SymbolTable's 94 | ContainerAsyncRequest ayncRequest = new ContainerAsyncRequest(); 95 | ayncRequest.setMetadataContainerId(containerId); 96 | ayncRequest.setIsCheckOnly(true); 97 | saveResults = port.create(new ArrayList(Arrays.asList(ayncRequest)), sessionHeader); 98 | String containerAsyncRequestId = saveResults.get(0).getId(); 99 | ayncRequest = (ContainerAsyncRequest) 100 | port.retrieve("State", "ContainerAsyncRequest", Arrays.asList(containerAsyncRequestId), sessionHeader).get(0); 101 | while(ayncRequest.getState().equals("Queued")) 102 | { 103 | try { 104 | Thread.sleep(1 * 1000); // Wait for a second 105 | } catch (InterruptedException ex) { 106 | Thread.currentThread().interrupt(); 107 | } 108 | ayncRequest = (ContainerAsyncRequest) 109 | port.retrieve("State", "ContainerAsyncRequest", Arrays.asList(containerAsyncRequestId), sessionHeader).get(0); 110 | } 111 | 112 | // Query again the ApexClassMember's to retrieve the SymbolTable's 113 | ApexClassMember[] apexClassMembersWithSymbols = 114 | port.retrieve("Body, ContentEntityId, SymbolTable", "ApexClassMember", apexClassMemberIds, sessionHeader) 115 | .toArray(new ApexClassMember[0]); 116 | 117 | // Map declared methods and external method references from SymbolTable's 118 | Set declaredMethods = new HashSet(); 119 | Set methodReferences = new HashSet(); 120 | for(ApexClassMember apexClassMember : apexClassMembersWithSymbols) 121 | { 122 | // List class methods defined and referenced 123 | SymbolTable symbolTable = apexClassMember.getSymbolTable(); 124 | if(symbolTable==null) // No symbol table, then class likely is invalid 125 | continue; 126 | for(Method method : symbolTable.getMethods()) 127 | { 128 | // Annotations are not exposed currently, following attempts to detect test methods to avoid giving false positives 129 | if(method.getName().toLowerCase().contains("test") && 130 | method.getVisibility() == SymbolVisibility.PRIVATE && 131 | (method.getReferences()==null || method.getReferences().size()==0)) 132 | continue; 133 | // Skip Global methods as implicitly these are referenced 134 | if( method.getVisibility() == SymbolVisibility.GLOBAL) 135 | continue; 136 | // Bug? (public method from System.Test?) 137 | if( method.getName().equals("aot")) 138 | continue; 139 | // Add the qualified method name to the list 140 | declaredMethods.add(symbolTable.getName() + "." + method.getName()); 141 | // Any local references to this method? 142 | if(method.getReferences()!=null && method.getReferences().size()>0) 143 | methodReferences.add(symbolTable.getName() + "." + method.getName()); 144 | } 145 | // Add any method references this class makes to other class methods 146 | for(ExternalReference externalRef : symbolTable.getExternalReferences()) 147 | for(ExternalMethod externalMethodRef : externalRef.getMethods()) 148 | methodReferences.add(externalRef.getName() + "." + externalMethodRef.getName()); 149 | } 150 | 151 | // List declaredMethods with no external references 152 | TreeSet unusedMethods = new TreeSet(); 153 | for(String delcaredMethodName : declaredMethods) 154 | if(!methodReferences.contains(delcaredMethodName)) 155 | unusedMethods.add(delcaredMethodName); 156 | 157 | // Render HTML table to display results 158 | StringBuilder sb = new StringBuilder(); 159 | sb.append(""); 160 | for(String methodName : unusedMethods) 161 | sb.append(""); 162 | sb.append("
" + methodName + "
"); 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> headers = connection.getHeaderFields(); 230 | for (String name : headers.keySet()) { 231 | List values = headers.get(name); 232 | 233 | for (int i = 0; i < values.size(); i++) { 234 | String value = values.get(i); 235 | if (name == null || value == null) { 236 | continue; 237 | } 238 | response.addHeader(name, value); 239 | } 240 | } 241 | } 242 | 243 | // Helper methods 244 | private String getConfigParam(String name, String defaultValue) { 245 | String value = getServletConfig().getInitParameter(name); 246 | return (value == null || value.trim().length() == 0) ? defaultValue : value; 247 | } 248 | 249 | private int getConfigParam(String name, int defaultValue) { 250 | String value = getServletConfig().getInitParameter(name); 251 | return (value == null || value.trim().length() == 0) ? defaultValue : Integer.valueOf(value); 252 | } 253 | 254 | private boolean getConfigParam(String name, boolean defaultValue) { 255 | String value = getServletConfig().getInitParameter(name); 256 | return (value == null || value.trim().length() == 0) ? defaultValue : Boolean.valueOf(value); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Heroku Quick Start Template 7 | 8 | 9 | index.html 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/webapp/canvas.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="java.util.Map" %> 28 | <% 29 | // Pull the signed request out of the request body. 30 | Map parameters = request.getParameterMap(); 31 | String[] signedRequest = parameters.get("signed_request"); 32 | if ("GET".equals(request.getMethod()) || signedRequest == null) {%> 33 | <% 34 | } 35 | else {%> 36 | <% 37 | } 38 | %> 39 | -------------------------------------------------------------------------------- /src/main/webapp/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afawcett/apex-codeanalysis/5f80d916eaeeea52ca2d8106bc30be689f43c933/src/main/webapp/icon.png -------------------------------------------------------------------------------- /src/main/webapp/images/salesforce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afawcett/apex-codeanalysis/5f80d916eaeeea52ca2d8106bc30be689f43c933/src/main/webapp/images/salesforce.png -------------------------------------------------------------------------------- /src/main/webapp/images/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afawcett/apex-codeanalysis/5f80d916eaeeea52ca2d8106bc30be689f43c933/src/main/webapp/images/share.png -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 | 29 | Force.com Canvas Java Quick Start 30 | 114 | 115 | 116 |

117 |
118 | 121 |
122 |

123 |
124 | Your sample Java application is up and running. 125 |

126 | For more information on Force.com Canvas Framework, please visit 127 | our developer documentation. 128 |

129 |
130 |
131 | ` 132 | 133 |
134 | 135 | 136 | -------------------------------------------------------------------------------- /src/main/webapp/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afawcett/apex-codeanalysis/5f80d916eaeeea52ca2d8106bc30be689f43c933/src/main/webapp/logo.png -------------------------------------------------------------------------------- /src/main/webapp/scripts/chatter-talk.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011, salesforce.com, inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without modification, are permitted provided 5 | // that the following conditions are met: 6 | // 7 | // Redistributions of source code must retain the above copyright notice, this list of conditions and the 8 | // following disclaimer. 9 | // 10 | // Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 11 | // the following disclaimer in the documentation and/or other materials provided with the distribution. 12 | // 13 | // Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or 14 | // promote products derived from this software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 17 | // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 18 | // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 20 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | 25 | 26 | var chatterTalk; 27 | if (!chatterTalk) { 28 | chatterTalk = {}; 29 | } 30 | 31 | (function ($$) { 32 | 33 | "use strict"; 34 | 35 | function onClickHandler() { 36 | } 37 | 38 | chatterTalk.init = function(sr, button, input, callback) { 39 | $$.byId(button).onclick=function() { 40 | var value = $$.byId(input).value; 41 | chatterTalk.post(sr, value, callback); 42 | }; 43 | }; 44 | 45 | 46 | chatterTalk.post = function(sr, message, callback) { 47 | var url = sr.context.links.chatterFeedsUrl+"/news/"+sr.context.user.userId+"/feed-items"; 48 | var body = {body : {messageSegments : [{type: "Text", text: message}]}}; 49 | 50 | $$.client.ajax(url, 51 | {token : sr.oauthToken, 52 | method: 'POST', 53 | async: true, 54 | contentType: "application/json", 55 | data: JSON.stringify(body), 56 | success : function(data) { 57 | if ($$.isFunction(callback)) { 58 | callback(data); 59 | } 60 | } 61 | }); 62 | }; 63 | 64 | }(Sfdc.canvas)); -------------------------------------------------------------------------------- /src/main/webapp/scripts/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2011-01-18 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false, regexp: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | var JSON; 163 | if (!JSON) { 164 | JSON = {}; 165 | } 166 | 167 | (function () { 168 | "use strict"; 169 | 170 | function f(n) { 171 | // Format integers to have at least two digits. 172 | return n < 10 ? '0' + n : n; 173 | } 174 | 175 | if (typeof Date.prototype.toJSON !== 'function') { 176 | 177 | Date.prototype.toJSON = function (key) { 178 | 179 | return isFinite(this.valueOf()) ? 180 | this.getUTCFullYear() + '-' + 181 | f(this.getUTCMonth() + 1) + '-' + 182 | f(this.getUTCDate()) + 'T' + 183 | f(this.getUTCHours()) + ':' + 184 | f(this.getUTCMinutes()) + ':' + 185 | f(this.getUTCSeconds()) + 'Z' : null; 186 | }; 187 | 188 | String.prototype.toJSON = 189 | Number.prototype.toJSON = 190 | Boolean.prototype.toJSON = function (key) { 191 | return this.valueOf(); 192 | }; 193 | } 194 | 195 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 196 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 197 | gap, 198 | indent, 199 | meta = { // table of character substitutions 200 | '\b': '\\b', 201 | '\t': '\\t', 202 | '\n': '\\n', 203 | '\f': '\\f', 204 | '\r': '\\r', 205 | '"' : '\\"', 206 | '\\': '\\\\' 207 | }, 208 | rep; 209 | 210 | 211 | function quote(string) { 212 | 213 | // If the string contains no control characters, no quote characters, and no 214 | // backslash characters, then we can safely slap some quotes around it. 215 | // Otherwise we must also replace the offending characters with safe escape 216 | // sequences. 217 | 218 | escapable.lastIndex = 0; 219 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 220 | var c = meta[a]; 221 | return typeof c === 'string' ? c : 222 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 223 | }) + '"' : '"' + string + '"'; 224 | } 225 | 226 | 227 | function str(key, holder) { 228 | 229 | // Produce a string from holder[key]. 230 | 231 | var i, // The loop counter. 232 | k, // The member key. 233 | v, // The member value. 234 | length, 235 | mind = gap, 236 | partial, 237 | value = holder[key]; 238 | 239 | // If the value has a toJSON method, call it to obtain a replacement value. 240 | 241 | if (value && typeof value === 'object' && 242 | typeof value.toJSON === 'function') { 243 | value = value.toJSON(key); 244 | } 245 | 246 | // If we were called with a replacer function, then call the replacer to 247 | // obtain a replacement value. 248 | 249 | if (typeof rep === 'function') { 250 | value = rep.call(holder, key, value); 251 | } 252 | 253 | // What happens next depends on the value's type. 254 | 255 | switch (typeof value) { 256 | case 'string': 257 | return quote(value); 258 | 259 | case 'number': 260 | 261 | // JSON numbers must be finite. Encode non-finite numbers as null. 262 | 263 | return isFinite(value) ? String(value) : 'null'; 264 | 265 | case 'boolean': 266 | case 'null': 267 | 268 | // If the value is a boolean or null, convert it to a string. Note: 269 | // typeof null does not produce 'null'. The case is included here in 270 | // the remote chance that this gets fixed someday. 271 | 272 | return String(value); 273 | 274 | // If the type is 'object', we might be dealing with an object or an array or 275 | // null. 276 | 277 | case 'object': 278 | 279 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 280 | // so watch out for that case. 281 | 282 | if (!value) { 283 | return 'null'; 284 | } 285 | 286 | // Make an array to hold the partial results of stringifying this object value. 287 | 288 | gap += indent; 289 | partial = []; 290 | 291 | // Is the value an array? 292 | 293 | if (Object.prototype.toString.apply(value) === '[object Array]') { 294 | 295 | // The value is an array. Stringify every element. Use null as a placeholder 296 | // for non-JSON values. 297 | 298 | length = value.length; 299 | for (i = 0; i < length; i += 1) { 300 | partial[i] = str(i, value) || 'null'; 301 | } 302 | 303 | // Join all of the elements together, separated with commas, and wrap them in 304 | // brackets. 305 | 306 | v = partial.length === 0 ? '[]' : gap ? 307 | '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 308 | '[' + partial.join(',') + ']'; 309 | gap = mind; 310 | return v; 311 | } 312 | 313 | // If the replacer is an array, use it to select the members to be stringified. 314 | 315 | if (rep && typeof rep === 'object') { 316 | length = rep.length; 317 | for (i = 0; i < length; i += 1) { 318 | k = rep[i]; 319 | if (typeof k === 'string') { 320 | v = str(k, value); 321 | if (v) { 322 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 323 | } 324 | } 325 | } 326 | } else { 327 | 328 | // Otherwise, iterate through all of the keys in the object. 329 | 330 | for (k in value) { 331 | if (Object.hasOwnProperty.call(value, k)) { 332 | v = str(k, value); 333 | if (v) { 334 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 335 | } 336 | } 337 | } 338 | } 339 | 340 | // Join all of the member texts together, separated with commas, 341 | // and wrap them in braces. 342 | 343 | v = partial.length === 0 ? '{}' : gap ? 344 | '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : 345 | '{' + partial.join(',') + '}'; 346 | gap = mind; 347 | return v; 348 | } 349 | } 350 | 351 | // If the JSON object does not yet have a stringify method, give it one. 352 | 353 | if (typeof JSON.stringify !== 'function') { 354 | JSON.stringify = function (value, replacer, space) { 355 | 356 | // The stringify method takes a value and an optional replacer, and an optional 357 | // space parameter, and returns a JSON text. The replacer can be a function 358 | // that can replace values, or an array of strings that will select the keys. 359 | // A default replacer method can be provided. Use of the space parameter can 360 | // produce text that is more easily readable. 361 | 362 | var i; 363 | gap = ''; 364 | indent = ''; 365 | 366 | // If the space parameter is a number, make an indent string containing that 367 | // many spaces. 368 | 369 | if (typeof space === 'number') { 370 | for (i = 0; i < space; i += 1) { 371 | indent += ' '; 372 | } 373 | 374 | // If the space parameter is a string, it will be used as the indent string. 375 | 376 | } else if (typeof space === 'string') { 377 | indent = space; 378 | } 379 | 380 | // If there is a replacer, it must be a function or an array. 381 | // Otherwise, throw an error. 382 | 383 | rep = replacer; 384 | if (replacer && typeof replacer !== 'function' && 385 | (typeof replacer !== 'object' || 386 | typeof replacer.length !== 'number')) { 387 | throw new Error('JSON.stringify'); 388 | } 389 | 390 | // Make a fake root object containing our value under the key of ''. 391 | // Return the result of stringifying the value. 392 | 393 | return str('', {'': value}); 394 | }; 395 | } 396 | 397 | 398 | // If the JSON object does not yet have a parse method, give it one. 399 | 400 | if (typeof JSON.parse !== 'function') { 401 | JSON.parse = function (text, reviver) { 402 | 403 | // The parse method takes a text and an optional reviver function, and returns 404 | // a JavaScript value if the text is a valid JSON text. 405 | 406 | var j; 407 | 408 | function walk(holder, key) { 409 | 410 | // The walk method is used to recursively walk the resulting structure so 411 | // that modifications can be made. 412 | 413 | var k, v, value = holder[key]; 414 | if (value && typeof value === 'object') { 415 | for (k in value) { 416 | if (Object.hasOwnProperty.call(value, k)) { 417 | v = walk(value, k); 418 | if (v !== undefined) { 419 | value[k] = v; 420 | } else { 421 | delete value[k]; 422 | } 423 | } 424 | } 425 | } 426 | return reviver.call(holder, key, value); 427 | } 428 | 429 | 430 | // Parsing happens in four stages. In the first stage, we replace certain 431 | // Unicode characters with escape sequences. JavaScript handles many characters 432 | // incorrectly, either silently deleting them, or treating them as line endings. 433 | 434 | text = String(text); 435 | cx.lastIndex = 0; 436 | if (cx.test(text)) { 437 | text = text.replace(cx, function (a) { 438 | return '\\u' + 439 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 440 | }); 441 | } 442 | 443 | // In the second stage, we run the text against regular expressions that look 444 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 445 | // because they can cause invocation, and '=' because it can cause mutation. 446 | // But just to be safe, we want to reject all unexpected forms. 447 | 448 | // We split the second stage into 4 regexp operations in order to work around 449 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 450 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 451 | // replace all simple value tokens with ']' characters. Third, we delete all 452 | // open brackets that follow a colon or comma or that begin the text. Finally, 453 | // we look to see that the remaining characters are only whitespace or ']' or 454 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 455 | 456 | if (/^[\],:{}\s]*$/ 457 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 458 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 459 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 460 | 461 | // In the third stage we use the eval function to compile the text into a 462 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 463 | // in JavaScript: it can begin a block or an object literal. We wrap the text 464 | // in parens to eliminate the ambiguity. 465 | 466 | j = eval('(' + text + ')'); 467 | 468 | // In the optional fourth stage, we recursively walk the new structure, passing 469 | // each name/value pair to a reviver function for possible transformation. 470 | 471 | return typeof reviver === 'function' ? 472 | walk({'': j}, '') : j; 473 | } 474 | 475 | // If the text is not JSON parseable, then a SyntaxError is thrown. 476 | 477 | throw new SyntaxError('JSON.parse'); 478 | }; 479 | } 480 | }()); 481 | 482 | 483 | 484 | -------------------------------------------------------------------------------- /src/main/webapp/sdk/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Callback Refresh Window 5 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/webapp/sdk/css/canvas.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | margin-bottom: 0; 4 | margin-top: 0; 5 | padding: 10px; 6 | background-color: #FFFFFF; 7 | font-family: "Arial", "Helvetica", "sans-serif"; 8 | font-size: 75%; 9 | color: #222222 10 | } 11 | 12 | h1 { 13 | font-size: 28px; 14 | color: #000 15 | } 16 | 17 | a { 18 | color: #015BA7 19 | } 20 | 21 | a:hover { 22 | background-color: #015BA7; 23 | color: white; 24 | text-decoration: none 25 | } 26 | 27 | #page { 28 | background-color: #FFFFFF; 29 | width: 800px; 30 | margin: 0; 31 | margin-left: 0; 32 | margin-right: 0 33 | } 34 | 35 | #content { 36 | float: left; 37 | background-color: white; 38 | border: 3px solid #aaa; 39 | padding: 25px; 40 | width: 700px 41 | } 42 | 43 | #footer { 44 | clear: both 45 | } 46 | 47 | #header { 48 | padding-left: 75px; 49 | padding-right: 30px 50 | } 51 | 52 | #canvas-content { 53 | padding-top: 25px; 54 | padding-left: 0px; 55 | padding-right: 30px 56 | } 57 | 58 | #header { 59 | background-image: url("/logo.png"); 60 | background-repeat: no-repeat; 61 | background-position: top left; 62 | height: 64px 63 | } 64 | 65 | #header h1, #header h2 { 66 | margin: 0 67 | } 68 | 69 | #header h2 { 70 | color: #888; 71 | font-weight: normal; 72 | font-size: 16px 73 | } 74 | 75 | #canvas-content { 76 | border-top: 1px solid #ccc; 77 | margin-top: 20px; 78 | padding-top: 0 79 | } 80 | 81 | #canvas-content h1 { 82 | margin: 0; 83 | font-size: 20px 84 | } 85 | 86 | #canvas-content h2 { 87 | margin: 0; 88 | font-size: 14px; 89 | font-weight: normal; 90 | color: #333; 91 | margin-bottom: 25px 92 | } 93 | 94 | #canvas-content ol { 95 | margin-left: 0; 96 | padding-left: 0 97 | } 98 | 99 | #canvas-content li { 100 | font-size: 18px; 101 | color: #888; 102 | margin-bottom: 25px 103 | } 104 | 105 | #canvas-content li h2 { 106 | margin: 0; 107 | font-weight: normal; 108 | font-size: 18px; 109 | color: #333 110 | } 111 | 112 | #canvas-content li p { 113 | color: #555; 114 | font-size: 13px 115 | } 116 | 117 | #canvas-chatter { 118 | border-top: 1px solid #ccc; 119 | margin-top: 20px; 120 | padding-top: 0 121 | } 122 | 123 | #speech-input-field { 124 | width: 400px; 125 | height: 14px; 126 | padding: 6px 15px; 127 | border-radius: 10px; 128 | border: 1px solid #ccc; 129 | outline: 0; 130 | } 131 | 132 | button { 133 | clear: both; 134 | width: 48px; 135 | height:24px; 136 | background: #666666 url(/images/share.png) no-repeat; 137 | } 138 | 139 | #footercont 140 | { 141 | float: left; 142 | width: 700px; 143 | height: 50px; 144 | color: #999; 145 | } 146 | 147 | #footercont p 148 | { 149 | margin: 0; 150 | padding: 15px; 151 | } 152 | 153 | #footercont a 154 | { 155 | color: #999; 156 | text-decoration: none; 157 | } 158 | 159 | #footercont a:hover 160 | { 161 | color: #199BD2; 162 | text-decoration: none; 163 | } 164 | 165 | #footerleft 166 | { 167 | float: left; 168 | width: 400px; 169 | height: 50px; 170 | } 171 | 172 | #footerright 173 | { 174 | float: left; 175 | width: 300px; 176 | height: 50px; 177 | text-align: right; 178 | } 179 | -------------------------------------------------------------------------------- /src/main/webapp/sdk/js/canvas.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 | (function (global) { 27 | 28 | "use strict"; 29 | 30 | if (global.Sfdc && global.Sfdc.canvas) { 31 | return; 32 | } 33 | 34 | // cached references 35 | //------------------ 36 | 37 | var oproto = Object.prototype, 38 | aproto = Array.prototype, 39 | doc = document, 40 | /** 41 | * @class Canvas 42 | * @exports $ as Sfdc.canvas 43 | */ 44 | // $ functions 45 | // The canvas global object is made available in the global scope. The reveal to the global scope is done later. 46 | $ = { 47 | 48 | // type utilities 49 | //--------------- 50 | 51 | /** 52 | * @description Checks whether an object contains an uninherited property. 53 | * @param {Object} obj The object to check 54 | * @param {String} prop The property name to check 55 | * @returns {Boolean} 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 |

This function does one of three things
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 |

This function does one of three things
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 | Map parameters = request.getParameterMap(); 33 | String[] signedRequest = parameters.get("signed_request"); 34 | if (signedRequest == null) {%> 35 | This App must be invoked via a signed request!<% 36 | return; 37 | } 38 | String yourConsumerSecret=System.getenv("CANVAS_CONSUMER_SECRET"); 39 | String signedRequestJson = SignedRequest.verifyAndDecodeAsJson(signedRequest[0], yourConsumerSecret); 40 | %> 41 | 42 | 43 | 44 | Force.com Canvas Tooling API Demo - Unused Apex Methods 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 66 | 67 | 68 |

69 |
70 | 74 | 75 |
76 |

You may have some unused Apex Methods in this Org

77 |

Below is a list of unused methods calculated via the Tooling API:

78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
Unused Methods: <%=ToolingAPI.getUnusedApexMethods(signedRequest[0], yourConsumerSecret)%>
Note: References made by Visualforce component bindings are not currently checked.
89 |
90 |
91 | 92 |
93 |

Canvas Request

94 |

Below is some information received in the Canvas Request:

95 |
96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 |
First Name: Last Name:
Company:
107 |
108 |
109 | 110 |
111 | 112 |
113 |
114 |

Powered By: Heroku

115 |
116 |
117 |

Salesforce: SafeHarbor

118 |
119 |
120 | 121 |
122 | 123 | 124 | -------------------------------------------------------------------------------- /src/main/webapp/welcome.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 language="java" contentType="text/html; charset=ISO-8859-1" 28 | pageEncoding="ISO-8859-1"%> 29 | 30 | 31 | 32 | Force.com Canvas Java Quick Start 33 | 34 | 35 | 36 |
37 |
38 | 41 |
42 |

43 |
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 |

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 |
51 |
52 | ` 53 | 54 |
55 | 56 | 57 | --------------------------------------------------------------------------------