21 |
22 |
--------------------------------------------------------------------------------
/oauthclient/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/example/twitter/app/controllers/Application.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import java.net.URLEncoder;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | import models.*;
8 |
9 | import play.libs.WS;
10 | import play.libs.WS.HttpResponse;
11 | import play.modules.oauthclient.OAuthClient;
12 | import play.mvc.*;
13 |
14 | public class Application extends Controller {
15 |
16 | public static void index() throws Exception {
17 | String url = "http://twitter.com/statuses/mentions.xml";
18 | String mentions = play.libs.WS.url(getConnector().sign(getUser().twitterCreds, url)).get().getString();
19 | render(mentions);
20 | }
21 |
22 | public static void setStatus(String status) throws Exception {
23 | String url = "http://twitter.com/statuses/update.json?status=" + URLEncoder.encode(status, "utf-8");
24 | String response = getConnector().sign(getUser().twitterCreds, WS.url(url), "POST").post().getString();
25 | request.current().contentType = "application/json";
26 | renderText(response);
27 | }
28 |
29 | // Twitter authentication
30 |
31 | public static void authenticate(String callback) throws Exception {
32 | // 1: get the request token
33 | Map args = new HashMap();
34 | args.put("callback", callback);
35 | String callbackURL = Router.getFullUrl(request.controller + ".oauthCallback", args);
36 | getConnector().authenticate(getUser().twitterCreds, callbackURL);
37 | }
38 |
39 | public static void oauthCallback(String callback, String oauth_token, String oauth_verifier) throws Exception {
40 | // 2: get the access token
41 | getConnector().retrieveAccessToken(getUser().twitterCreds, oauth_verifier);
42 | redirect(callback);
43 | }
44 |
45 | private static OAuthClient connector = null;
46 | private static OAuthClient getConnector() {
47 | if (connector == null) {
48 | connector = new OAuthClient(
49 | "http://twitter.com/oauth/request_token",
50 | "http://twitter.com/oauth/access_token",
51 | "http://twitter.com/oauth/authorize",
52 | "eevIR82fiFK3e6VrGpO9rw",
53 | "OYCQA6fpsLiMVaxqqm1EqDjDWFmdlbkSYYcIbwICrg");
54 | }
55 | return connector;
56 | }
57 |
58 | // TODO: Make it real?
59 |
60 | private static User getUser() {
61 | return User.findOrCreate("guest");
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/oauthclient/app/play/modules/oauthclient/WSOAuthConsumer.java:
--------------------------------------------------------------------------------
1 | package play.modules.oauthclient;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.util.Map;
6 |
7 | import oauth.signpost.AbstractOAuthConsumer;
8 | import oauth.signpost.exception.OAuthCommunicationException;
9 | import oauth.signpost.exception.OAuthExpectationFailedException;
10 | import oauth.signpost.exception.OAuthMessageSignerException;
11 | import oauth.signpost.http.HttpRequest;
12 | import play.libs.WS.WSRequest;
13 |
14 | public class WSOAuthConsumer extends AbstractOAuthConsumer {
15 |
16 | public WSOAuthConsumer(String consumerKey, String consumerSecret) {
17 | super(consumerKey, consumerSecret);
18 | }
19 |
20 | @Override
21 | protected HttpRequest wrap(Object request) {
22 | if (!(request instanceof WSRequest)) {
23 | throw new IllegalArgumentException("WSOAuthConsumer expects requests of type play.libs.WS.WSRequest");
24 | }
25 | return new WSRequestAdapter((WSRequest)request);
26 | }
27 |
28 | public WSRequest sign(WSRequest request, String method) throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException {
29 | WSRequestAdapter req = (WSRequestAdapter)wrap(request);
30 | req.setMethod(method);
31 | sign(req);
32 | return request;
33 | }
34 |
35 | public class WSRequestAdapter implements HttpRequest {
36 |
37 | private WSRequest request;
38 |
39 | public WSRequestAdapter(WSRequest request) {
40 | this.request = request;
41 | }
42 |
43 | public Map getAllHeaders() {
44 | return request.headers;
45 | }
46 |
47 | public String getContentType() {
48 | return request.mimeType;
49 | }
50 |
51 | public String getHeader(String name) {
52 | return request.headers.get(name);
53 | }
54 |
55 | public InputStream getMessagePayload() throws IOException {
56 | return null;
57 | }
58 |
59 | private String method = "GET";
60 |
61 | public String getMethod() {
62 | return this.method;
63 | }
64 |
65 | private void setMethod(String method) {
66 | this.method = method;
67 | }
68 |
69 | public String getRequestUrl() {
70 | return request.url;
71 | }
72 |
73 | public void setHeader(String name, String value) {
74 | request.setHeader(name, value);
75 | }
76 |
77 | public void setRequestUrl(String url) {
78 | request.url = url;
79 | }
80 |
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/oauthclient/documentation/manual/home.textile:
--------------------------------------------------------------------------------
1 | h1. OAuth Client
2 |
3 | This module allows applications to connect to an "oauth":http://oauth.net provider, such as "Twitter":http://apiwiki.twitter.com/ or "Google":http://code.google.com/apis/accounts/docs/AuthForWebApps.html .
4 |
5 | h2. OAuth Basics
6 |
7 | In order to access to users' data from a oauth provider, you need to register and obtain a consumer key. See the documentation on your provider's website for detailed on how to obtain it.
8 |
9 | You need to following:
10 | * Request URL
11 | * Access URL
12 | * Authorize URL
13 | * Consumer key
14 | * Consumer secret
15 |
16 | The workflow is the following, for each user:
17 | # Get a request token from the provider. The user must be redirected to a page on the provider's site where he explicitly grants access rights to your application.
18 | # Exchange the request token for an access token.
19 | # Use the token to sign requests to access the user's data with REST request on the provider.
20 |
21 | Depending on the provider's policy the token may expire at any time. The user can also revoke the permission to your application.
22 |
23 | h2. Using The Module
24 |
25 | The main class to use is play.modules.oauthclient.OAuthClient. It is used to retrieve request and access token, and sign requests.
26 |
27 | You need to store each user's token in your data layer. To do so you must provide a ICredentials for each user; you can use the Credentials implementation which inherits model and as such will be serialized to your data layer.
28 |
29 | h2. The OAuthClient Class
30 |
31 | Here is how you would create a client to connect to Twitter's API:
32 |
33 | bc. OAuthClient client = new OAuthClient(
34 | "http://twitter.com/oauth/request_token",
35 | "http://twitter.com/oauth/access_token",
36 | "http://twitter.com/oauth/authorize",
37 | "aaaaaaaaaaaaaaaaaaaaaa", // Your application's consumer key
38 | "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); // You application's consumer secret
39 |
40 | h2. Authentication
41 |
42 | First, you need a request token:
43 |
44 | bc. ICredentials creds = new Credentials();
45 | client.authenticate(creds, callbackURL);
46 |
47 | Calling this method will redirect the user to a page on the provider where he is asked to grant your application permission to access his data. After that, the provider will redirect him to the URL you provided in callbackURL with oauth_token and oauth_verifier as arguments. You will need the oauth_verifier to get your access token. The token and secret will be set in creds. If you use your own implementation of ICredentials, make sure that they get saved in the setters.
48 |
49 | Then, you need to exchange this request token for an access token:
50 |
51 | bc. client.retrieveAccessToken(creds, oauth_verifier);
52 |
53 | creds should contain the tokens set by OAuthClient.authenticate. oauth_verifier comes from the callback in the previous step.
54 |
55 | When this is done, creds contain everything you need to sign request and retrieve data related to the user who performed the workflow.
56 |
57 | h2. Signing Requests
58 |
59 | With the correct ICredentials, you can sign any request; this is not limited to the current session. Requests are done using play.libs.WS:
60 |
61 | While a classical, non signed request would be the following:
62 |
63 | bc. WS.url(url).get();
64 |
65 | A signed request will be:
66 |
67 | bc. WS.url(client.sign(creds, url)).get();
68 |
69 | If the request is not a GET, you need to sign the request instead of the URL and pass the method:
70 |
71 | bc. client.sign(creds, WS.url(url), "POST").post();
72 |
73 | h2. Example
74 |
75 | A simple "twitter example":http://github.com/erwan/playoauthclient/tree/master/example/twitter/ is available.
76 |
--------------------------------------------------------------------------------
/oauthclient/app/play/modules/oauthclient/OAuthClient.java:
--------------------------------------------------------------------------------
1 | package play.modules.oauthclient;
2 |
3 | import oauth.signpost.OAuthProvider;
4 | import oauth.signpost.basic.DefaultOAuthProvider;
5 | import play.Logger;
6 | import play.libs.WS.WSRequest;
7 | import play.mvc.results.Redirect;
8 |
9 | public class OAuthClient {
10 |
11 | private String requestURL;
12 | private String accessURL;
13 | private String authorizeURL;
14 | private String consumerKey;
15 | private String consumerSecret;
16 |
17 | private WSOAuthConsumer consumer;
18 | private OAuthProvider provider;
19 |
20 | public OAuthClient(String requestURL, String accessURL, String authorizeURL, String consumerKey, String consumerSecret) {
21 | this.requestURL = requestURL;
22 | this.accessURL = accessURL;
23 | this.authorizeURL = authorizeURL;
24 | this.consumerKey = consumerKey;
25 | this.consumerSecret = consumerSecret;
26 | }
27 |
28 | public WSOAuthConsumer getConsumer(ICredentials cred) {
29 | if (consumer == null) {
30 | consumer = new WSOAuthConsumer(
31 | consumerKey,
32 | consumerSecret);
33 | consumer.setTokenWithSecret(cred.getToken(), cred.getSecret());
34 | }
35 | return consumer;
36 | }
37 |
38 | public OAuthProvider getProvider() {
39 | if (provider == null) {
40 | provider = new DefaultOAuthProvider(
41 | requestURL,
42 | accessURL,
43 | authorizeURL);
44 | provider.setOAuth10a(true);
45 | }
46 | return provider;
47 | }
48 |
49 | // Authentication
50 |
51 | public void authenticate(ICredentials cred, String callbackURL) throws Exception {
52 | throw new Redirect(retrieveRequestToken(cred, callbackURL));
53 | }
54 |
55 | /**
56 | * Retrieve the request token, and store it in user.
57 | * to in order to get the token.
58 | * @param cred the ICredentials where the oauth token and oauth secret will be set.
59 | * @param callbackURL: the URL the user should be redirected after he grants the rights to our app
60 | * @return the URL on the provider's site that we should redirect the user
61 | */
62 | public String retrieveRequestToken(ICredentials cred, String callbackURL) throws Exception {
63 | Logger.info("Consumer key: " + getConsumer(cred).getConsumerKey());
64 | Logger.info("Consumer secret: " + getConsumer(cred).getConsumerSecret());
65 | Logger.info("Token before request: " + getConsumer(cred).getToken());
66 | String authUrl = getProvider().retrieveRequestToken(getConsumer(cred), callbackURL);
67 | Logger.info("Token after request: " + getConsumer(cred).getToken());
68 | cred.setToken(consumer.getToken());
69 | cred.setSecret(consumer.getTokenSecret());
70 | return authUrl;
71 | }
72 |
73 | /**
74 | * Retrieve the access token, and store it in user.
75 | * to in order to get the token.
76 | * @param user the ICredentials with the request token and secret already set (using retrieveRequestToken).
77 | * The access token and secret will be set these.
78 | * @return the URL on the provider's site that we should redirect the user
79 | * @see retrieveRequestToken
80 | */
81 | public void retrieveAccessToken(ICredentials user, String verifier) throws Exception {
82 | Logger.info("Token before retrieve: " + getConsumer(user).getToken());
83 | Logger.info("Verifier: " + verifier);
84 | getProvider().retrieveAccessToken(getConsumer(user), verifier);
85 | user.setToken(consumer.getToken());
86 | user.setSecret(consumer.getTokenSecret());
87 | }
88 |
89 | // Signing requests
90 |
91 | /**
92 | * Sign the url with the OAuth tokens for the user. This method can only be used for GET requests.
93 | * @param url
94 | * @return
95 | * @throws OAuthMessageSignerException
96 | * @throws OAuthExpectationFailedException
97 | * @throws OAuthCommunicationException
98 | */
99 | public String sign(ICredentials user, String url) throws Exception {
100 | return getConsumer(user).sign(url);
101 | }
102 |
103 | public WSRequest sign(ICredentials user, WSRequest request, String method) throws Exception {
104 | return getConsumer(user).sign(request, method);
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/example/twitter/conf/application.conf:
--------------------------------------------------------------------------------
1 | # This is the main configuration file for the application.
2 | # ~~~~~
3 | application.name=Twitter
4 |
5 | # Application mode
6 | # ~~~~~
7 | # Set to dev to enable instant reloading and other development help.
8 | # Otherwise set to prod.
9 | application.mode=dev
10 |
11 | # Secret key
12 | # ~~~~~
13 | # The secret key is used to secure cryptographics functions
14 | # If you deploy your application to several instances be sure to use the same key !
15 | application.secret=e227tafmfs0xrexah43hm34kkrcetav48nwk9x037wp87jkrp06m7n8wc8m7gbag
16 |
17 | # Additional modules
18 | # ~~~~~
19 | # A module is another play! application. Add a line for each module you want
20 | # to add to your application. Modules path are either absolutes or relative to
21 | # the application root.
22 | #
23 | #module.crud=${play.path}/modules/crud
24 | #module.secure=${play.path}/modules/secure
25 | #module.ecss=${play.path}/modules/ecss
26 | #module.gae=${play.path}/modules/gae
27 | #module.gwt=${play.path}/modules/gwt
28 | #module.search=${play.path}/modules/search
29 | #module.siena=${play.path}/modules/siena
30 | #module.spring=${play.path}/modules/spring
31 | module.oauthclient=/home/erwan/Devel/Play/oauthclient/oauthclient
32 |
33 | # i18n
34 | # ~~~~~
35 | # Define locales used by your application.
36 | # You can then place localized messages in conf/messages.{locale} files
37 | # application.langs=fr,en,ja
38 |
39 | # Server configuration
40 | # ~~~~~
41 | # If you need to change the HTTP port, uncomment this (default is set to 9000)
42 | # http.port=9000
43 | #
44 | # By default the server listen for HTTP on the wilcard address.
45 | # You can restrict this.
46 | # http.address=127.0.0.1
47 |
48 | # Session configuration
49 | # ~~~~~~~~~~~~~~~~~~~~~~
50 | # By default, session will be written to the transient PLAY_SESSION cookie.
51 | # application.session.cookie=PLAY
52 | # application.session.maxAge=1h
53 |
54 | # JVM configuration
55 | # ~~~~~
56 | # Define which port is used by JPDA when application is in debug mode (default is set to 8000)
57 | # jpda.port=8000
58 | #
59 | # Java source level => 1.5, 1.6 or 1.7 (experimental)
60 | # java.source=1.5
61 |
62 | # Log level
63 | # ~~~~~
64 | # Specify log level for your application.
65 | # If you want a very customized log, create a log4j.properties file in the conf directory
66 | # application.log=INFO
67 | #
68 | # More logging configuration
69 | # application.log.path=/log4j.properties
70 | # application.log.system.out=off
71 |
72 | # Database configuration
73 | # ~~~~~
74 | # Enable a database engine if needed.
75 | #
76 | # To quickly set up a development database, use either:
77 | # - mem : for a transient in memory database (HSQL in memory)
78 | # - fs : for a simple file written database (HSQL file stored)
79 | db=mem
80 | #
81 | # To connect to a local MySQL5 database, use:
82 | # db=mysql:user:pwd@database_name
83 | #
84 | # If you need a full JDBC configuration use the following :
85 | # db.url=jdbc:postgresql:database_name
86 | # db.driver=org.postgresql.Driver
87 | # db.user=root
88 | # db.pass=secret
89 | #
90 | # Connections pool configuration :
91 | # db.pool.timeout=1000
92 | # db.pool.maxSize=30
93 | # db.pool.minSize=10
94 | #
95 | # If you want to reuse an existing Datasource from your application server, use:
96 | # db=java:/comp/env/jdbc/myDatasource
97 |
98 | # JPA Configuration (Hibernate)
99 | # ~~~~~
100 | #
101 | # Specify the custom JPA dialect to use here (default to guess):
102 | # jpa.dialect=org.hibernate.dialect.PostgreSQLDialect
103 | #
104 | # Specify the ddl generation pattern to use (default to update, set to none to disable it):
105 | # jpa.ddl=update
106 | #
107 | # Debug SQL statements (logged using DEBUG level):
108 | # jpa.debugSQL=true
109 | #
110 | # You can even specify additional hibernate properties here:
111 | # hibernate.use_sql_comments=true
112 | # ...
113 |
114 | # Memcached configuration
115 | # ~~~~~
116 | # Enable memcached if needed. Otherwise a local cache is used.
117 | # memcached=enabled
118 | #
119 | # Specify memcached host (default to 127.0.0.1:11211)
120 | # memcached.host=127.0.0.1:11211
121 | #
122 | # Or you can specify multiple host to build a distributed cache
123 | # memcached.1.host=127.0.0.1:11211
124 | # memcached.2.host=127.0.0.1:11212
125 |
126 | # HTTP Response headers control for static files
127 | # ~~~~~
128 | # Set the default max-age, telling the user's browser how long it should cache the page.
129 | # Default is 3600 (one hour). Set it to 0 to send no-cache.
130 | # This is only read in prod mode, in dev mode the cache is disabled.
131 | http.cacheControl=3600
132 |
133 | # If enabled, Play will generate entity tags automatically and send a 304 when needed.
134 | # Default is true, set it to false to deactivate use of entity tags.
135 | http.useETag=true
136 |
137 | # WS configuration
138 | # ~~~~~
139 | # If you need to set proxy params for WS requests
140 | # http.proxyHost = localhost
141 | # http.proxyPort = 3128
142 | # http.proxyUser = jojo
143 | # http.proxyPassword = jojo
144 |
145 | # Mail configuration
146 | # ~~~~~
147 | # Default is to use a mock Mailer
148 | mail.smtp=mock
149 |
150 | # Or, specify mail host configuration
151 | # mail.smtp.host=127.0.0.1
152 | # mail.smtp.user=admin
153 | # mail.smtp.pass=
154 | # mail.smtp.channel=ssl
155 |
156 | # Execution pool
157 | # ~~~~~
158 | # Default to 1 thread in DEV mode or (nb processors + 1) threads in PROD mode.
159 | # Try to keep a low as possible. 1 thread will serialize all requests (very useful for debugging purpose)
160 | # play.pool=3
161 |
162 | # Open file from errors pages
163 | # ~~~~~
164 | # If your text editor supports to open files using URL, Play! will link
165 | # error pages to files dynamically
166 | #
167 | # Example, for textmate:
168 | # play.editor=txmt://open?url=file://%s&line=%s
169 |
170 | # Testing. Set up a custom configuration for test mode
171 | # ~~~~~
172 | #%test.module.cobertura=${play.path}/modules/cobertura
173 | %test.application.mode=dev
174 | %test.db=mem
175 | %test.jpa.ddl=create-drop
176 | %test.mail.smtp=mock
177 |
178 | # These features will be automatically enabled in the 1.1 release
179 | # For now you can enable them if you want
180 | # ~~~~~
181 | future.bindJPAObjects=true
182 | future.escapeInTemplates=true
183 |
--------------------------------------------------------------------------------
/example/twitter/public/javascripts/jquery-1.4.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery JavaScript Library v1.4
3 | * http://jquery.com/
4 | *
5 | * Copyright 2010, John Resig
6 | * Dual licensed under the MIT or GPL Version 2 licenses.
7 | * http://docs.jquery.com/License
8 | *
9 | * Includes Sizzle.js
10 | * http://sizzlejs.com/
11 | * Copyright 2010, The Dojo Foundation
12 | * Released under the MIT, BSD, and GPL Licenses.
13 | *
14 | * Date: Wed Jan 13 15:23:05 2010 -0500
15 | */
16 | (function(A,w){function oa(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(oa,1);return}c.ready()}}function La(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function $(a,b,d,f,e,i){var j=a.length;if(typeof b==="object"){for(var o in b)$(a,o,b[o],f,e,d);return a}if(d!==w){f=!i&&f&&c.isFunction(d);for(o=0;o-1){i=j.data;i.beforeFilter&&i.beforeFilter[a.type]&&!i.beforeFilter[a.type](a)||f.push(j.selector)}else delete t[p]}i=c(a.target).closest(f,a.currentTarget);
18 | n=0;for(l=i.length;n)[^>]*$|^#([\w-]+)$/,Pa=/^.[^:#\[\.,]*$/,Qa=/\S/,
21 | Ra=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Sa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],M,ca=Object.prototype.toString,da=Object.prototype.hasOwnProperty,ea=Array.prototype.push,R=Array.prototype.slice,V=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(typeof a==="string")if((d=Oa.exec(a))&&(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Sa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];
22 | c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=ua([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return U.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a)}else return!b||b.jquery?(b||U).find(a):c(b).find(a);else if(c.isFunction(a))return U.ready(a);if(a.selector!==w){this.selector=a.selector;
23 | this.context=a.context}return c.isArray(a)?this.setArray(a):c.makeArray(a,this)},selector:"",jquery:"1.4",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){a=c(a||null);a.prevObject=this;a.context=this.context;if(b==="find")a.selector=this.selector+(this.selector?" ":"")+d;else if(b)a.selector=this.selector+"."+b+"("+d+")";return a},setArray:function(a){this.length=
24 | 0;ea.apply(this,a);return this},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||
25 | c(null)},push:ea,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,i,j,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b