├── lean-server-example ├── src │ ├── main │ │ └── webapp │ │ │ ├── favicon.ico │ │ │ ├── images │ │ │ ├── exit.png │ │ │ ├── yahoo.png │ │ │ ├── google.png │ │ │ ├── openid.png │ │ │ ├── twitter.png │ │ │ └── facebook.png │ │ │ ├── WEB-INF │ │ │ ├── datastore-indexes.xml │ │ │ ├── appengine-web.xml │ │ │ └── web.xml │ │ │ ├── admin │ │ │ ├── scripts.jsp │ │ │ └── settings.jsp │ │ │ ├── loginerror.jsp │ │ │ └── home.jsp │ └── test │ │ └── java │ │ └── com │ │ └── leanengine │ │ └── gwtlite │ │ └── GwtTestSample.java ├── License-example.txt └── pom.xml ├── lean-server-lib ├── License-library.txt ├── src │ └── main │ │ └── java │ │ └── com │ │ └── leanengine │ │ └── server │ │ ├── auth │ │ ├── Scheme.java │ │ ├── AuthToken.java │ │ ├── LeanAccount.java │ │ ├── LogoutServlet.java │ │ ├── MobileScheme.java │ │ ├── WebScheme.java │ │ ├── AuthFilter.java │ │ ├── LoginErrorServlet.java │ │ ├── FacebookMockLogin.java │ │ ├── AuthService.java │ │ ├── OpenIdLoginServlet.java │ │ ├── FacebookAuth.java │ │ └── FacebookLoginServlet.java │ │ ├── appengine │ │ ├── ServerUtils.java │ │ ├── AccountUtils.java │ │ └── DatastoreUtils.java │ │ ├── entity │ │ ├── QueryResult.java │ │ ├── QuerySort.java │ │ ├── QueryFilter.java │ │ └── LeanQuery.java │ │ ├── rest │ │ ├── resteasy │ │ │ ├── RestExceptionWrapper.java │ │ │ ├── RestApplication.java │ │ │ ├── RestExceptionMapper.java │ │ │ └── RestSecurityInterceptor.java │ │ ├── PublicServiceRest.java │ │ ├── QueryRest.java │ │ ├── EntityRest.java │ │ └── ScriptRest.java │ │ ├── SecurityFilter.java │ │ ├── LeanEngineSettings.java │ │ ├── LeanException.java │ │ ├── DumpFilter.java │ │ └── JsonUtils.java └── pom.xml ├── .gitignore ├── License.txt ├── README.md └── pom.xml /lean-server-example/src/main/webapp/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterKnego/LeanEngine-Server/HEAD/lean-server-example/src/main/webapp/favicon.ico -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/images/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterKnego/LeanEngine-Server/HEAD/lean-server-example/src/main/webapp/images/exit.png -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/images/yahoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterKnego/LeanEngine-Server/HEAD/lean-server-example/src/main/webapp/images/yahoo.png -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/images/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterKnego/LeanEngine-Server/HEAD/lean-server-example/src/main/webapp/images/google.png -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/images/openid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterKnego/LeanEngine-Server/HEAD/lean-server-example/src/main/webapp/images/openid.png -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/images/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterKnego/LeanEngine-Server/HEAD/lean-server-example/src/main/webapp/images/twitter.png -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/images/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterKnego/LeanEngine-Server/HEAD/lean-server-example/src/main/webapp/images/facebook.png -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/WEB-INF/datastore-indexes.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /lean-server-lib/License-library.txt: -------------------------------------------------------------------------------- 1 | All files for this library project, contained in this directory and sub-directories, 2 | are licensed under GNU Lesser General Public License version 3. 3 | 4 | For more info see http://www.gnu.org/licenses/lgpl.html -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | */.idea/ 3 | *.iml 4 | *.ipr 5 | *.iws 6 | */*.iml 7 | */*.ipr 8 | */*.iws 9 | target/ 10 | out/ 11 | lean-server-lib/target/ 12 | lean-server-lib/out/ 13 | lean-server-main/target/ 14 | lean-server-main/out/ 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/Scheme.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import com.leanengine.server.LeanException; 4 | 5 | public interface Scheme { 6 | 7 | String getUrl(String authToken, String redirectUrl) throws LeanException; 8 | 9 | String getErrorUrl(LeanException exception, String redirectUrl); 10 | } 11 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/appengine/ServerUtils.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.appengine; 2 | 3 | import com.google.appengine.api.utils.SystemProperty; 4 | 5 | public class ServerUtils { 6 | 7 | public static boolean isDevServer(){ 8 | return SystemProperty.environment.value() == SystemProperty.Environment.Value.Development; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/admin/scripts.jsp: -------------------------------------------------------------------------------- 1 | <%-- 2 | ~ This software is released under the BSD license. For full license see License-library.txt file. 3 | ~ 4 | ~ Copyright (c) 2011, Peter Knego & Matjaz Tercelj 5 | ~ All rights reserved. 6 | --%> 7 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> 8 | 9 | 10 | 11 | Scripts be here. 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | LeanEngine Server license 2 | 3 | All files for LeanEngine example server application, contained in ./lean-server-example directory and sub-directories, 4 | are licensed under modified BSD license. See lean-server-example/License-example.txt 5 | 6 | All files for LeanEngine server library, contained in ./lean-server-lib directory and sub-directories, 7 | are licensed under GNU Lesser General Public License v3. See lean-server-lib/License-library.txt 8 | -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/WEB-INF/appengine-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lean-engine-demo 4 | 1 5 | 6 | 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/entity/QueryResult.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.entity; 2 | 3 | import com.google.appengine.api.datastore.Cursor; 4 | import com.google.appengine.api.datastore.Entity; 5 | 6 | import java.util.List; 7 | 8 | public class QueryResult { 9 | private List result; 10 | private Cursor cursor; 11 | 12 | public QueryResult(List result, Cursor cursor) { 13 | this.result = result; 14 | this.cursor = cursor; 15 | } 16 | 17 | public List getResult() { 18 | return result; 19 | } 20 | 21 | public Cursor getCursor() { 22 | return cursor; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/AuthToken.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import java.util.UUID; 4 | 5 | public class AuthToken { 6 | 7 | public String token; 8 | public long accountID = 0; 9 | public long timeCreated; 10 | 11 | public AuthToken(long accountID) { 12 | this.accountID = accountID; 13 | this.token = UUID.randomUUID().toString(); 14 | this.timeCreated = System.currentTimeMillis(); 15 | } 16 | 17 | public AuthToken(String token, long accountID, long timeCreated) { 18 | this.token = token; 19 | this.accountID = accountID; 20 | this.timeCreated = timeCreated; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lean-server-example/src/test/java/com/leanengine/gwtlite/GwtTestSample.java: -------------------------------------------------------------------------------- 1 | //package com.omnispots.client; 2 | // 3 | //import junit.framework.Assert; 4 | // 5 | //import com.google.gwt.junit.gwtlite.GWTTestCase; 6 | // 7 | //public class GwtTestSample 8 | // extends GWTTestCase 9 | //{ 10 | // 11 | // public String getModuleName() 12 | // { 13 | // return "com.leanengine.Application"; 14 | // } 15 | // 16 | // public void testSomething() 17 | // { 18 | // // Not much to actually test in this sample web 19 | // // Ideally you would test your Controller here (NOT YOUR UI) 20 | // // (Make calls to RPC services, test gwtlite side model objects, test gwtlite side logic, etc) 21 | // Assert.assertTrue( true ); 22 | // } 23 | //} -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/rest/resteasy/RestExceptionWrapper.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.rest.resteasy; 2 | 3 | import com.leanengine.server.LeanException; 4 | import org.codehaus.jackson.map.annotate.JsonSerialize; 5 | 6 | @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) 7 | public class RestExceptionWrapper { 8 | 9 | public int code; 10 | public String message; 11 | public String cause; 12 | 13 | public RestExceptionWrapper(LeanException leanException) { 14 | code = leanException.getErrorCode(); 15 | message = leanException.getMessage(); 16 | cause = leanException.getCause() != null ? leanException.getCause().getMessage() : null; 17 | } 18 | 19 | public RestExceptionWrapper(Throwable throwable) { 20 | code = 0; 21 | message = throwable.getMessage(); 22 | cause = throwable.getCause() != null ? throwable.getCause().getMessage() : null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/SecurityFilter.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server; 2 | 3 | import com.leanengine.server.auth.AuthService; 4 | 5 | import javax.servlet.*; 6 | import javax.servlet.http.HttpServletResponse; 7 | import java.io.IOException; 8 | 9 | public class SecurityFilter implements Filter { 10 | 11 | @Override 12 | public void init(FilterConfig filterConfig) throws ServletException { 13 | } 14 | 15 | @Override 16 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 17 | if (AuthService.isUserLoggedIn()) { 18 | chain.doFilter(request, response); 19 | } else { 20 | HttpServletResponse httpResponse = (HttpServletResponse)response; 21 | httpResponse.sendError(401, "Error 401 Unauthorized: no user logged in."); 22 | } 23 | } 24 | 25 | @Override 26 | public void destroy() { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/rest/resteasy/RestApplication.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.rest.resteasy; 2 | 3 | import com.leanengine.server.rest.*; 4 | 5 | import javax.ws.rs.core.Application; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | import java.util.logging.Logger; 9 | 10 | public class RestApplication extends Application { 11 | 12 | private Set singletons = new HashSet(); 13 | 14 | private static final Logger log = Logger.getLogger(RestApplication.class.getName()); 15 | 16 | 17 | public RestApplication() { 18 | 19 | singletons.add(new RestSecurityInterceptor()); 20 | 21 | singletons.add(new RestExceptionMapper()); 22 | singletons.add(new EntityRest()); 23 | singletons.add(new PublicServiceRest()); 24 | singletons.add(new QueryRest()); 25 | // singletons.add(new ScriptRest()); 26 | 27 | } 28 | 29 | @Override 30 | public Set getSingletons() { 31 | return singletons; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/rest/PublicServiceRest.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.rest; 2 | 3 | import com.leanengine.server.LeanException; 4 | import com.leanengine.server.auth.AuthService; 5 | import com.leanengine.server.auth.LeanAccount; 6 | 7 | import javax.ws.rs.GET; 8 | import javax.ws.rs.Path; 9 | import javax.ws.rs.Produces; 10 | 11 | @Path("/v1/public") 12 | public class PublicServiceRest { 13 | 14 | @GET 15 | @Path("/account") 16 | @Produces("application/json") 17 | public String getCurrentAccount() throws LeanException { 18 | LeanAccount account = AuthService.getCurrentAccount(); 19 | return account != null ? account.toJson() : "{}"; 20 | } 21 | 22 | @GET 23 | @Path("/logout") 24 | @Produces("application/json") 25 | public String logout() { 26 | // returns 'false' of user is not logged in 27 | if (AuthService.getCurrentAccount() == null) { 28 | return "{\"result\":false}"; 29 | } else { 30 | AuthService.resetCurrentAuthData(); 31 | return "{\"result\":true}"; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LeanEngine Server 2 | ================= 3 | 4 | This is the server part of LeanEngine project 5 | To get started visit http://www.lean-engine.com 6 | 7 | What is LeanEngine 8 | ------------------ 9 | 10 | LeanEngine is an open source project aiming make the development of cloud-enabled mobile applications really simple, fast and fun. It lets you focus on developing your mobile app while it handles all the boring and complicated stuff. Stuff like: 11 | 12 | * logins (Facebook, Google, Twitter, OpenID) 13 | * syncing data (store and retrieve data to and from cloud) 14 | * security 15 | * push over multiple platforms (coming soon) 16 | 17 | All this you can achieve with using LeanEngine libraries and a few lines of code on your clients. LeanEngine server runs on Google App Engine. 18 | 19 | 20 | Stay updated 21 | ------------ 22 | 23 | Twitter: http://twitter.com/#!/leanengine 24 | Google Plus: https://plus.google.com/u/0/115510332327304386091 25 | 26 | 27 | License 28 | ------- 29 | 30 | Example server application is licensed under BSD-style license. 31 | 32 | Server library is licensed under LGPL v3. 33 | 34 | For more info see License.txt in root directory. -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/LeanAccount.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import com.leanengine.server.JsonUtils; 4 | import com.leanengine.server.LeanException; 5 | 6 | import java.io.IOException; 7 | import java.util.Map; 8 | 9 | public class LeanAccount { 10 | 11 | public long id = 0; 12 | public String nickName; 13 | public String providerId; 14 | public String provider; 15 | public Map providerProperties; 16 | 17 | public LeanAccount(long id, String nickName, String providerId, String provider, Map providerProperties) { 18 | this.id = id; 19 | this.nickName = nickName; 20 | this.providerId = providerId; 21 | this.provider = provider; 22 | this.providerProperties = providerProperties; 23 | } 24 | 25 | public String toJson() throws LeanException { 26 | try { 27 | return JsonUtils.getObjectMapper().writeValueAsString(this); 28 | } catch (IOException e) { 29 | throw new LeanException(LeanException.Error.ErrorSerializingToJson, "\n\n" + e.getMessage()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/LogoutServlet.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import javax.servlet.ServletException; 4 | import javax.servlet.http.HttpServlet; 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | import javax.servlet.http.HttpSession; 8 | import java.io.IOException; 9 | 10 | public class LogoutServlet extends HttpServlet { 11 | 12 | @Override 13 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 14 | 15 | HttpSession session = request.getSession(); 16 | String token = (String) session.getAttribute("lean_token"); 17 | if (token != null) { 18 | AuthService.resetCurrentAuthData(); 19 | session.removeAttribute("lean_token"); 20 | } 21 | 22 | String redirectUrl; 23 | if (request.getParameter("redirect") != null) { 24 | redirectUrl = request.getParameter("redirect"); 25 | } else if (request.getHeader("Referer") != null) { 26 | redirectUrl = request.getHeader("Referer"); 27 | } else { 28 | redirectUrl = "/"; 29 | } 30 | 31 | response.sendRedirect(redirectUrl); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/MobileScheme.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import com.leanengine.server.LeanException; 4 | 5 | import java.io.UnsupportedEncodingException; 6 | import java.net.URLEncoder; 7 | import java.util.logging.Logger; 8 | 9 | public class MobileScheme implements Scheme { 10 | 11 | private static final Logger log = Logger.getLogger(MobileScheme.class.getName()); 12 | 13 | private String scheme = "leanengine://"; 14 | private String hostname; 15 | 16 | public MobileScheme(String hostname) { 17 | this.hostname = hostname; 18 | } 19 | 20 | @Override 21 | public String getUrl(String authToken, String redirectUrl) { 22 | return scheme + hostname + "/?auth_token=" + authToken; 23 | } 24 | 25 | @Override 26 | public String getErrorUrl(LeanException exception, String redirectUrl) { 27 | 28 | log.severe(exception.getMessage()); 29 | 30 | try { 31 | return scheme + hostname + "/?errorcode=" + exception.getErrorCode() + 32 | "&errormsg=" + URLEncoder.encode(exception.getMessage(), "UTF-8"); 33 | } catch (UnsupportedEncodingException e) { 34 | // should not happen - UTF-8 is supported on all JVMs 35 | return null; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/rest/resteasy/RestExceptionMapper.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.rest.resteasy; 2 | 3 | import com.google.appengine.api.datastore.DatastoreNeedIndexException; 4 | import com.leanengine.server.LeanException; 5 | 6 | import javax.ws.rs.core.Response; 7 | import javax.ws.rs.ext.ExceptionMapper; 8 | import javax.ws.rs.ext.Provider; 9 | import java.util.logging.Logger; 10 | 11 | @Provider 12 | public class RestExceptionMapper implements ExceptionMapper { 13 | 14 | private static final Logger log = Logger.getLogger(RestExceptionMapper.class.getName()); 15 | 16 | @Override 17 | public Response toResponse(Throwable exception) { 18 | 19 | log.severe(exception.getMessage()); 20 | 21 | if (exception instanceof LeanException) { 22 | LeanException leanException = (LeanException) exception; 23 | if (leanException.getErrorCode() >= 100) { 24 | // client error 25 | return Response.status(400).entity(new RestExceptionWrapper(leanException)).build(); 26 | } else { 27 | // server error 28 | return Response.status(500).entity(new RestExceptionWrapper(leanException)).build(); 29 | } 30 | } else { 31 | return Response.status(500).entity(new RestExceptionWrapper(exception)).build(); 32 | } 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /lean-server-example/License-example.txt: -------------------------------------------------------------------------------- 1 | All files in this application project, contained in this directory and sub-directories, are licensed under the 2 | following BSD-style license: 3 | 4 | Copyright (c) 2011, Peter Knego & Matjaz Tercelj 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 8 | following conditions are met: 9 | 10 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the 11 | following disclaimer. 12 | 13 | - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/WebScheme.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import com.leanengine.server.LeanException; 4 | 5 | import java.io.UnsupportedEncodingException; 6 | import java.net.URLEncoder; 7 | import java.util.logging.Logger; 8 | 9 | public class WebScheme implements Scheme { 10 | 11 | private static final Logger log = Logger.getLogger(WebScheme.class.getName()); 12 | 13 | private String hostname; 14 | private String scheme; 15 | 16 | public WebScheme(String scheme, String hostname) { 17 | this.hostname = hostname; 18 | this.scheme = scheme + "://"; 19 | } 20 | 21 | @Override 22 | public String getUrl(String authToken, String redirectUrl) throws LeanException { 23 | // if null throw error 24 | if(redirectUrl ==null) 25 | throw new LeanException(LeanException.Error.MissingRedirectUrl); 26 | return scheme + hostname + redirectUrl; 27 | } 28 | 29 | @Override 30 | public String getErrorUrl(LeanException exception, String redirectUrl) { 31 | 32 | log.severe(exception.getMessage()); 33 | 34 | // if null set default value 35 | redirectUrl = redirectUrl == null ? "/loginerror" : redirectUrl; 36 | try { 37 | return scheme + hostname + redirectUrl + "?errorlogin=true&errorcode=" + exception.getErrorCode() + 38 | "&errormsg=" + URLEncoder.encode(exception.getMessage(), "UTF-8"); 39 | } catch (UnsupportedEncodingException e) { 40 | // should not happen - UTF-8 is supported on all JVMs 41 | return null; 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/loginerror.jsp: -------------------------------------------------------------------------------- 1 | <%-- 2 | ~ This software is released under the BSD license. For full license see License-library.txt file. 3 | ~ 4 | ~ Copyright (c) 2011, Peter Knego & Matjaz Tercelj 5 | ~ All rights reserved. 6 | --%> 7 | 8 | 9 | 10 | Login error 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | LeanEngine Demo 20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 | × 29 | 30 |

Error: there was an error during login process:

31 |
    32 |
  • error code: <%=request.getParameter("errorcode")%> 33 |
  • 34 |
  • error message: <%=request.getParameter("errormsg")%> 35 |
  • 36 |
37 |
38 | Close 39 |
40 |
41 |
42 |
43 |
44 | 45 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 5 | 4.0.0 6 | 7 | com.leanengine.server 8 | server-parent 9 | pom 10 | 0.8 11 | Lean Server Parent Project 12 | 13 | 14 | lean-server-example 15 | lean-server-lib 16 | 17 | 18 | 19 | 20 | 1.5.5 21 | 22 | 23 | lean-engine 24 | 25 | 26 | ${user.home}/.m2/repository/com/google/appengine/appengine-java-sdk/${gae.version}/appengine-java-sdk-${gae.version} 27 | 28 | 29 | UTF-8 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-compiler-plugin 39 | 40 | 1.6 41 | 1.6 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/entity/QuerySort.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.entity; 2 | 3 | import com.google.appengine.api.datastore.Query; 4 | import com.leanengine.server.LeanException; 5 | 6 | public class QuerySort { 7 | private String property; 8 | private SortDirection direction; 9 | 10 | public QuerySort(String property, SortDirection direction) { 11 | this.property = property; 12 | this.direction = direction; 13 | } 14 | 15 | public String getProperty() { 16 | return property; 17 | } 18 | 19 | public SortDirection getDirection() { 20 | return direction; 21 | } 22 | 23 | public enum SortDirection { 24 | ASCENDING("asc", Query.SortDirection.ASCENDING), 25 | DESCENDING("desc", Query.SortDirection.DESCENDING); 26 | 27 | private String sortString; 28 | private Query.SortDirection sortDirection; 29 | 30 | SortDirection(String sortString, Query.SortDirection sortDirection) { 31 | this.sortString = sortString; 32 | this.sortDirection = sortDirection; 33 | } 34 | 35 | public static SortDirection create(String sortJson) throws LeanException { 36 | if ("asc".equals(sortJson)) { 37 | return SortDirection.ASCENDING; 38 | } else if ("desc".equals(sortJson)) { 39 | return SortDirection.DESCENDING; 40 | } 41 | throw new LeanException(LeanException.Error.UnsupportedQuerySortOperation, sortJson); 42 | } 43 | 44 | public Query.SortDirection getSortDirection() { 45 | return sortDirection; 46 | } 47 | 48 | public String toJSON() { 49 | return sortString; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/rest/QueryRest.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.rest; 2 | 3 | import com.leanengine.server.JsonUtils; 4 | import com.leanengine.server.LeanException; 5 | import com.leanengine.server.appengine.DatastoreUtils; 6 | import com.leanengine.server.entity.LeanQuery; 7 | import com.leanengine.server.entity.QueryFilter; 8 | import com.leanengine.server.entity.QueryResult; 9 | import com.leanengine.server.entity.QuerySort; 10 | import org.codehaus.jackson.JsonNode; 11 | import org.codehaus.jackson.node.ObjectNode; 12 | 13 | import javax.ws.rs.*; 14 | 15 | @Path("/v1/query") 16 | @Produces("application/json") 17 | @Consumes("application/json") 18 | public class QueryRest { 19 | 20 | @POST 21 | @Path("/") 22 | public JsonNode query(String queryJson) throws LeanException { 23 | QueryResult result = DatastoreUtils.queryEntityPrivate(LeanQuery.fromJson(queryJson)); 24 | ObjectNode jsonResult = JsonUtils.entityListToJson(result.getResult()); 25 | if (result.getCursor() != null) { 26 | jsonResult.put("cursor", result.getCursor().toWebSafeString()); 27 | } 28 | return jsonResult; 29 | } 30 | 31 | @GET 32 | @Path("/example") 33 | public JsonNode exampleQuery() throws LeanException { 34 | LeanQuery query = new LeanQuery("somekind"); 35 | query.addFilter("prop1", QueryFilter.FilterOperator.EQUAL, "value1"); 36 | query.addFilter("prop2", QueryFilter.FilterOperator.LESS_THAN_OR_EQUAL, 1.23); 37 | query.addFilter("prop2", QueryFilter.FilterOperator.GREATER_THAN_OR_EQUAL, 0.5); 38 | query.addSort("prop2", QuerySort.SortDirection.ASCENDING); 39 | query.addSort("prop3", QuerySort.SortDirection.DESCENDING); 40 | return query.toJson(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/rest/resteasy/RestSecurityInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.rest.resteasy; 2 | 3 | import com.leanengine.server.auth.AuthService; 4 | import com.leanengine.server.rest.PublicServiceRest; 5 | import org.jboss.resteasy.annotations.interception.ServerInterceptor; 6 | import org.jboss.resteasy.core.Headers; 7 | import org.jboss.resteasy.core.ResourceMethod; 8 | import org.jboss.resteasy.core.ServerResponse; 9 | import org.jboss.resteasy.spi.HttpRequest; 10 | import org.jboss.resteasy.spi.UnauthorizedException; 11 | import org.jboss.resteasy.spi.interception.PreProcessInterceptor; 12 | import org.jboss.resteasy.util.HttpResponseCodes; 13 | 14 | import javax.ws.rs.core.MultivaluedMap; 15 | import javax.ws.rs.ext.Provider; 16 | import java.util.logging.Logger; 17 | 18 | @Provider 19 | @ServerInterceptor 20 | public class RestSecurityInterceptor implements PreProcessInterceptor { 21 | 22 | private static final Logger log = Logger.getLogger(RestSecurityInterceptor.class.getName()); 23 | 24 | @Override 25 | public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws UnauthorizedException { 26 | 27 | // pass public methods 28 | if (method.getResourceClass().equals(PublicServiceRest.class)) { 29 | return null; 30 | } 31 | 32 | // user not logged-in? 33 | if (AuthService.getCurrentAccount() == null) { 34 | ServerResponse response = new ServerResponse(); 35 | response.setStatus(HttpResponseCodes.SC_UNAUTHORIZED); 36 | MultivaluedMap headers = new Headers(); 37 | headers.add("Content-Type", "text/plain"); 38 | response.setMetadata(headers); 39 | response.setEntity("{\"code\":401, \"message\":\"HTTP error 401: Unauthorized to access " + 40 | request.getPreprocessedPath() + "\"" + ""); 41 | return response; 42 | } 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/AuthFilter.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import javax.servlet.*; 4 | import javax.servlet.http.HttpServletRequest; 5 | import javax.servlet.http.HttpServletResponse; 6 | import javax.servlet.http.HttpSession; 7 | import java.io.IOException; 8 | import java.util.logging.Logger; 9 | 10 | public class AuthFilter implements Filter { 11 | 12 | private static final Logger log = Logger.getLogger(AuthFilter.class.getName()); 13 | 14 | @Override 15 | public void init(FilterConfig filterConfig) throws ServletException { 16 | // nothing to do 17 | } 18 | 19 | @Override 20 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 21 | HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; 22 | 23 | // REST requests have auth_token set as parameter 24 | String token = httpServletRequest.getParameter("lean_token"); 25 | 26 | // check the session support 27 | HttpSession session = httpServletRequest.getSession(); 28 | if (session.getId() == null) { 29 | log.severe("Session support NOT enabled."); 30 | HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; 31 | httpResponse.sendError(500, "Server not properly configured: sessions not enabled"); 32 | return; 33 | } 34 | 35 | // if we did not get token from parameters, try getting it from session 36 | if (token == null) { 37 | // Web requests have auth_token set in session 38 | token = (String) session.getAttribute("lean_token"); 39 | } 40 | 41 | if (token != null) { 42 | AuthService.startAuthSession(token); 43 | } 44 | 45 | filterChain.doFilter(servletRequest, servletResponse); 46 | 47 | AuthService.finishAuthSession(); 48 | } 49 | 50 | @Override 51 | public void destroy() { 52 | // nothing to do 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/rest/EntityRest.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.rest; 2 | 3 | import com.google.appengine.api.datastore.Entity; 4 | import com.leanengine.server.JsonUtils; 5 | import com.leanengine.server.LeanException; 6 | import com.leanengine.server.appengine.DatastoreUtils; 7 | import org.codehaus.jackson.JsonNode; 8 | 9 | import javax.ws.rs.*; 10 | import java.util.List; 11 | import java.util.logging.Logger; 12 | 13 | @Path("/v1/entity") 14 | @Produces("application/json") 15 | @Consumes("application/json") 16 | public class EntityRest { 17 | 18 | private static final Logger log = Logger.getLogger(EntityRest.class.getName()); 19 | 20 | @GET 21 | @Path("/{entityName}/{entityId}") 22 | public JsonNode getEntity(@PathParam("entityName") String entityName, @PathParam("entityId") long entityId) throws LeanException { 23 | Entity entity = DatastoreUtils.getPrivateEntity(entityName, entityId); 24 | return JsonUtils.entityToJson(entity); 25 | } 26 | 27 | @DELETE 28 | @Path("/{entityName}/{entityId}") 29 | public void deleteEntity(@PathParam("entityName") String entityName, @PathParam("entityId") long entityId) throws LeanException { 30 | DatastoreUtils.deletePrivateEntity(entityName, entityId); 31 | } 32 | 33 | @GET 34 | @Path("/{entityName}") 35 | public JsonNode getAllUserPrivateEntities(@PathParam("entityName") String kind) throws LeanException { 36 | List entities = DatastoreUtils.getPrivateEntities(kind); 37 | return JsonUtils.entityListToJson(entities); 38 | } 39 | 40 | @GET 41 | @Path("/") 42 | public JsonNode getAllUserPrivateEntities() throws LeanException { 43 | List entities = DatastoreUtils.getPrivateEntities(); 44 | return JsonUtils.entityListToJson(entities); 45 | } 46 | 47 | @POST 48 | @Path("/{entityName}") 49 | public String putEntity(@PathParam("entityName") String entityName, JsonNode entityJson) throws LeanException { 50 | long entityID = DatastoreUtils.putPrivateEntity(entityName, JsonUtils.entityPropertiesFromJson(entityJson)); 51 | return "{\"id\":" + entityID + "}"; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/LoginErrorServlet.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import javax.servlet.ServletException; 4 | import javax.servlet.http.HttpServlet; 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | import java.io.IOException; 8 | import java.io.PrintWriter; 9 | 10 | public class LoginErrorServlet extends HttpServlet { 11 | 12 | @Override 13 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 14 | 15 | PrintWriter writer = response.getWriter(); 16 | 17 | writer.append("\n") 18 | .append("\n") 19 | .append("Login error\n") 20 | .append("\n") 21 | .append("\n") 22 | .append("\n") 23 | .append(""); 24 | 25 | writer.append("
") 26 | .append("
\n") 27 | .append("×\n") 28 | .append("

Error: there was an error during login process:

\n") 29 | .append("
  • error code: " + request.getParameter("errorcode") + "
  • \n") 30 | .append("
  • error message: " + request.getParameter("errormsg") + "
\n") 31 | .append("
\n") 32 | .append("Close\n") 33 | .append("
\n") 34 | .append("\n") 35 | .append(""); 36 | 37 | writer.close(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/entity/QueryFilter.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.entity; 2 | 3 | import com.google.appengine.api.datastore.Query; 4 | import com.leanengine.server.LeanException; 5 | 6 | public class QueryFilter { 7 | private String property; 8 | private FilterOperator operator; 9 | private Object value; 10 | 11 | public QueryFilter(String property, FilterOperator operator, Object value) { 12 | 13 | this.property = property; 14 | this.operator = operator; 15 | this.value = value; 16 | } 17 | 18 | public String getProperty() { 19 | return property; 20 | } 21 | 22 | public FilterOperator getOperator() { 23 | return operator; 24 | } 25 | 26 | public Object getValue() { 27 | return value; 28 | } 29 | 30 | public enum FilterOperator { 31 | IN("IN",Query.FilterOperator.IN), 32 | EQUAL("=",Query.FilterOperator.EQUAL), 33 | GREATER_THAN(">",Query.FilterOperator.GREATER_THAN), 34 | GREATER_THAN_OR_EQUAL(">=",Query.FilterOperator.GREATER_THAN_OR_EQUAL), 35 | LESS_THAN("<",Query.FilterOperator.LESS_THAN), 36 | LESS_THAN_OR_EQUAL("<=",Query.FilterOperator.LESS_THAN_OR_EQUAL), 37 | NOT_EQUAL("!=",Query.FilterOperator.NOT_EQUAL); 38 | 39 | private String operatorString; 40 | private Query.FilterOperator gaeOperator; 41 | 42 | static FilterOperator create(String jsonOperator) throws LeanException { 43 | if("=".equals(jsonOperator)){ 44 | return FilterOperator.EQUAL; 45 | } else if(">".equals(jsonOperator)){ 46 | return FilterOperator.GREATER_THAN; 47 | } else if(">=".equals(jsonOperator)){ 48 | return FilterOperator.GREATER_THAN_OR_EQUAL; 49 | } else if("<".equals(jsonOperator)){ 50 | return FilterOperator.LESS_THAN; 51 | } else if("<=".equals(jsonOperator)){ 52 | return FilterOperator.LESS_THAN_OR_EQUAL; 53 | } else if("!=".equals(jsonOperator)){ 54 | return FilterOperator.NOT_EQUAL; 55 | } else if("IN".equals(jsonOperator)){ 56 | return FilterOperator.IN; 57 | } 58 | throw new LeanException(LeanException.Error.UnsupportedQueryFilterOperation, jsonOperator); 59 | } 60 | 61 | FilterOperator( String operatorString, Query.FilterOperator gaeOperator) { 62 | this.operatorString = operatorString; 63 | this.gaeOperator = gaeOperator; 64 | } 65 | 66 | public Query.FilterOperator getFilterOperator() { 67 | return gaeOperator; 68 | } 69 | 70 | public String toJSON() { 71 | return operatorString; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/FacebookMockLogin.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import com.leanengine.server.LeanException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import javax.servlet.http.HttpSession; 9 | import java.io.IOException; 10 | import java.io.PrintWriter; 11 | 12 | public class FacebookMockLogin { 13 | 14 | protected static void showForm(HttpServletRequest request, HttpServletResponse response, boolean isMobile) throws ServletException, IOException { 15 | 16 | PrintWriter writer = response.getWriter(); 17 | 18 | writer.append("\n").append("\n") 19 | .append("
\n") 20 | .append("
\n") 21 | .append("

Facebook mock login

\n") 22 | .append("

\n") 23 | .append("\n") 24 | .append("\n") 25 | .append("

\n").append("

\n") 26 | .append("

\n").append("\n") 27 | .append("

\n"); 28 | 29 | if (isMobile) writer.append("\n"); 30 | 31 | writer.append("\n") 32 | .append("\n") 33 | .append("

\n") 34 | .append("
\n") 35 | .append("
\n") 36 | .append("\n") 37 | .append(""); 38 | 39 | writer.close(); 40 | } 41 | 42 | protected static void login(HttpServletRequest request, HttpServletResponse response, 43 | String email, Scheme scheme, String redirectUrl, String errorUrl) throws IOException { 44 | // authenticate with Facebook Graph OAuth API 45 | AuthToken lean_token = AuthService.createMockFacebookAccount(email); 46 | 47 | // save token in session 48 | HttpSession session = request.getSession(); 49 | session.setAttribute("lean_token", lean_token.token); 50 | 51 | //send lean_token back to browser 52 | try { 53 | response.sendRedirect(scheme.getUrl(lean_token.token, redirectUrl)); 54 | } catch (LeanException e) { 55 | response.sendRedirect(scheme.getErrorUrl(e, errorUrl)); 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/rest/ScriptRest.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.rest; 2 | 3 | import com.leanengine.server.JsonUtils; 4 | import com.leanengine.server.LeanException; 5 | import org.codehaus.jackson.JsonNode; 6 | import org.codehaus.jackson.node.ArrayNode; 7 | import org.codehaus.jackson.node.ObjectNode; 8 | import org.mozilla.javascript.*; 9 | import javax.ws.rs.*; 10 | import java.util.Map; 11 | import java.util.logging.Logger; 12 | 13 | @Path("/v1/script") 14 | @Produces("application/json") 15 | @Consumes("application/json") 16 | public class ScriptRest { 17 | 18 | private static final Logger log = Logger.getLogger(ScriptRest.class.getName()); 19 | 20 | 21 | @POST 22 | @Path("/{scriptName}") 23 | public String scriptPOST(@PathParam("scriptName") String scriptName, String code) throws LeanException { 24 | 25 | //todo resolve Script name 26 | 27 | Context ctx = Context.enter(); 28 | try { 29 | ScriptableObject scope = ctx.initStandardObjects(); 30 | 31 | Script script = ctx.compileString(code, "", 1, null); 32 | 33 | Object result = script.exec(ctx, scope); 34 | 35 | if (result == null || !result.getClass().equals(NativeObject.class)) { 36 | throw new LeanException(LeanException.Error.ScriptOutputError, scriptName); 37 | } 38 | 39 | return toJSON((NativeObject) result).toString(); 40 | } catch (RhinoException ex) { 41 | throw new LeanException(LeanException.Error.ScriptExecutionError, scriptName, ex); 42 | } finally { 43 | Context.exit(); 44 | } 45 | } 46 | 47 | @POST 48 | @Path("/{scriptName}/{property1}") 49 | public String scriptPOST(@PathParam("scriptName") String scriptName, String property1, String jsonObject) { 50 | return jsonObject; 51 | } 52 | 53 | private JsonNode toJSON(NativeObject object) { 54 | if (object == null) return null; 55 | 56 | ObjectNode result = JsonUtils.getObjectMapper().createObjectNode(); 57 | for (Map.Entry entry : object.entrySet()) { 58 | if (entry.getValue().getClass().equals(NativeObject.class)) { 59 | result.put((String) entry.getKey(), toJSON((NativeObject) entry.getValue())); 60 | } else if (entry.getValue().getClass().equals(NativeArray.class)) { 61 | result.put((String) entry.getKey(), toJSON((NativeArray) entry.getValue())); 62 | } else { 63 | result.putPOJO((String) entry.getKey(), entry.getValue()); 64 | } 65 | 66 | } 67 | 68 | return result; 69 | } 70 | 71 | private ArrayNode toJSON(NativeArray array) { 72 | ArrayNode result = JsonUtils.getObjectMapper().createArrayNode(); 73 | for (Object o : array) { 74 | if (o.getClass().equals(NativeObject.class)) { 75 | result.add(toJSON((NativeObject) o)); 76 | } else { 77 | result.addPOJO(o); 78 | } 79 | } 80 | return result; 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/AuthService.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import com.leanengine.server.appengine.AccountUtils; 4 | import com.leanengine.server.appengine.ServerUtils; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.UUID; 9 | import java.util.logging.Logger; 10 | 11 | public class AuthService { 12 | 13 | private static final Logger log = Logger.getLogger(AuthService.class.getName()); 14 | 15 | private static ThreadLocal tlAuthToken = new ThreadLocal(); 16 | private static ThreadLocal tlLeanAccount = new ThreadLocal(); 17 | 18 | public static void startAuthSession(String token) { 19 | LeanAccount user = getAccountByToken(token); 20 | tlAuthToken.set(token); 21 | tlLeanAccount.set(user); 22 | } 23 | 24 | public static void finishAuthSession() { 25 | tlLeanAccount.remove(); 26 | tlAuthToken.remove(); 27 | } 28 | 29 | public static AuthToken createMockFacebookAccount(String email) { 30 | if(!ServerUtils.isDevServer()){ 31 | throw new IllegalStateException("Method 'createMockFacebookAccount(email)' should only be called while running Dev Server."); 32 | } 33 | 34 | LeanAccount account = AccountUtils.findAccountByEmail(email, "fb-oauth"); 35 | if (account == null) { 36 | //todo this is one-to-one mapping between Account and User 37 | //change this in the future 38 | 39 | Map props = new HashMap(1); 40 | props.put("email", email); 41 | 42 | // account does not yet exist - create it 43 | account = new LeanAccount( 44 | 0, 45 | email, 46 | UUID.randomUUID().toString(), 47 | "fb-oauth", 48 | props); 49 | AccountUtils.saveAccount(account); 50 | } 51 | 52 | // create our own authentication token 53 | // todo retrieve existing token if not expired 54 | return AuthService.createAuthToken(account.id); 55 | } 56 | 57 | private static LeanAccount getAccountByToken(String authToken) { 58 | 59 | //todo Use MemCache to cache this 60 | AuthToken savedToken = AccountUtils.getAuthToken(authToken); 61 | if (savedToken == null) return null; 62 | LeanAccount user = AccountUtils.getAccount(savedToken.accountID); 63 | if (user == null) return null; 64 | 65 | return user; 66 | } 67 | 68 | public static void resetCurrentAuthData() { 69 | String token = tlAuthToken.get(); 70 | if (token != null) AccountUtils.removeAuthToken(token); 71 | tlLeanAccount.remove(); 72 | tlAuthToken.remove(); 73 | } 74 | 75 | public static AuthToken createAuthToken(long accountID) { 76 | AuthToken authToken = new AuthToken(accountID); 77 | AccountUtils.saveAuthToken(authToken); 78 | return authToken; 79 | } 80 | 81 | public static LeanAccount getCurrentAccount() { 82 | return tlLeanAccount.get(); 83 | } 84 | 85 | public static boolean isUserLoggedIn() { 86 | return tlAuthToken.get() != null; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/LeanEngineSettings.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server; 2 | 3 | import com.google.appengine.api.datastore.*; 4 | import com.google.appengine.api.utils.SystemProperty; 5 | import com.leanengine.server.appengine.ServerUtils; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class LeanEngineSettings { 11 | 12 | private static Map settings; 13 | 14 | private static Map load() { 15 | DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 16 | Key key = KeyFactory.createKey("_settings", 1); 17 | Entity leanEntity; 18 | try { 19 | leanEntity = datastore.get(key); 20 | settings = leanEntity.getProperties(); 21 | } catch (EntityNotFoundException e) { 22 | settings = new HashMap(); 23 | 24 | // By default enable all logins on Development server 25 | if (ServerUtils.isDevServer()) { 26 | settings.put("fbLoginEnable", true); 27 | settings.put("fbAppId", "mockFacebookAppId"); 28 | settings.put("fbAppSecret", "mockFacebookAppSecret"); 29 | settings.put("openIdLoginEnable", true); 30 | saveSettings(settings); 31 | } 32 | } 33 | return settings; 34 | } 35 | 36 | public static void saveSettings(Map newSettings) { 37 | // there is only one instance of LeanEngineSettings so the same ID=1 is always used 38 | Entity leanEntity = new Entity("_settings", 1); 39 | for (String propName : newSettings.keySet()) { 40 | leanEntity.setProperty(propName, newSettings.get(propName)); 41 | } 42 | DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 43 | datastore.put(leanEntity); 44 | LeanEngineSettings.settings = newSettings; 45 | } 46 | 47 | public static boolean isFacebookLoginEnabled() { 48 | if (settings == null) load(); 49 | Object fbLoginEnable = settings.get("fbLoginEnable"); 50 | return fbLoginEnable == null ? false : (Boolean) fbLoginEnable; 51 | } 52 | 53 | 54 | public static String getFacebookAppID() { 55 | if (settings == null) load(); 56 | return (String) settings.get("fbAppId"); 57 | } 58 | 59 | public static String getFacebookAppSecret() { 60 | if (settings == null) load(); 61 | return (String) settings.get("fbAppSecret"); 62 | } 63 | 64 | public static boolean isOpenIdLoginEnabled() { 65 | if (settings == null) load(); 66 | Object openIdLoginEnable = settings.get("openIdLoginEnable"); 67 | return openIdLoginEnable == null ? false : (Boolean) openIdLoginEnable; 68 | } 69 | 70 | /** 71 | * Retrieves application settings. 72 | * 73 | * @return Map of application settings. 74 | */ 75 | public static Map getSettings() { 76 | load(); 77 | return settings; 78 | } 79 | 80 | /** 81 | * Helper class for one-line saving of multiple settings. 82 | */ 83 | public static class Builder { 84 | 85 | private Map temp = new HashMap(); 86 | 87 | public Builder add(String name, Object value) { 88 | temp.put(name, value); 89 | return this; 90 | } 91 | 92 | public void save() { 93 | if (!temp.isEmpty()) LeanEngineSettings.saveSettings(temp); 94 | } 95 | 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /lean-server-lib/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | com.leanengine.server 7 | leanengine-server-lib 8 | jar 9 | 0.8 10 | 11 | 12 | com.leanengine.server 13 | server-parent 14 | 0.8 15 | 16 | 17 | 18 | 19 | 20 | 21 | javax.servlet 22 | servlet-api 23 | 2.5 24 | provided 25 | 26 | 27 | javax.servlet.jsp 28 | jsp-api 29 | 2.1 30 | provided 31 | 32 | 33 | 34 | 35 | org.jboss.resteasy 36 | resteasy-jaxrs 37 | 2.2.2.GA 38 | 39 | 40 | org.jboss.resteasy 41 | resteasy-jackson-provider 42 | 2.2.2.GA 43 | 44 | 45 | com.google.appengine 46 | appengine-api-1.0-sdk 47 | ${gae.version} 48 | compile 49 | 50 | 51 | com.google.appengine 52 | appengine-tools-sdk 53 | ${gae.version} 54 | test 55 | 56 | 57 | com.google.appengine 58 | appengine-api-labs 59 | ${gae.version} 60 | test 61 | 62 | 63 | com.google.appengine 64 | appengine-api-stubs 65 | ${gae.version} 66 | test 67 | 68 | 69 | com.google.appengine 70 | appengine-testing 71 | ${gae.version} 72 | test 73 | 74 | 75 | 76 | 77 | org.mozilla 78 | rhino 79 | 1.7R3 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | ${basedir}/src/main/resources 89 | 90 | 91 | 92 | 93 | 94 | 95 | Jboss-repo 96 | http://repository.jboss.org/nexus/content/groups/public-jboss/ 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/LeanException.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server; 2 | 3 | public class LeanException extends Throwable { 4 | 5 | public enum Error { 6 | // reply errors are produced by wrong client parameters 7 | IllegalEntityName(1, "Illegal LeanEntity name."), 8 | EmptyEntity(2, "LeanEntity contains no properties."), 9 | EntityNotFound(3, "Entity not found."), 10 | ErrorSerializingToJson(4, "Object could not be serialized to JSON"), 11 | QueryJSON(5, "Query JSON could not be parsed."), 12 | UnsupportedQueryFilterOperation(6, "Query contains unsupported filter operation: "), 13 | UnsupportedQuerySortOperation(7, "Query contains unsupported sort operation: "), 14 | ValueToJSON(8, "Value node could not be converted to a supported type."), 15 | 16 | // user is not authorized to access resource 17 | // or error during authorization process 18 | // error codes 100-199 19 | FacebookAuthError(101, "Facebook authorization error."), 20 | FacebookAuthParseError(102, "Facebook authorization error."), 21 | FacebookAuthConnectError(103, "Could not connect to Facebook authorization server."), 22 | FacebookAuthResponseError(104, "Facebook OAuth server error."), 23 | FacebookAuthMissingParam(105, "OAuth error: missing parameters in server reply."), 24 | FacebookAuthNoConnection(106, "Could not connect to Facebook authorization server."), 25 | FacebookAuthNotEnabled(107, "Server configuration error: Facebook login not enabled."), 26 | FacebookAuthMissingAppId(108, "Server configuration error: missing Facebook Application ID."), 27 | FacebookAuthMissingAppSecret(109, "Server configuration error: missing Facebook Application Secret."), 28 | FacebookAuthMissingCRSF(110, "Facebook OAuth request missing CSRF protection code."), 29 | OpenIdAuthFailed(111, "OpenID authentication failed."), 30 | OpenIdAuthNotEnabled(112, "Server configuration error: OpenID login not enabled."), 31 | NotAuthorized(113, "No account active or account not authorized to access this resource."), 32 | MissingRedirectUrl(114, "Login request must have URL parameter 'onlogin' used for redirect on successful login."), 33 | 34 | // server errors have codes between 200-299 35 | // they happen when server has problems fulfilling request 36 | ScriptExecutionError(201, "Error executing script: "), 37 | ScriptOutputError(202, "Illegal script result error: custom scripts must produce a Javascript object. Script: "), 38 | AppEngineMissingIndex(203, "AppEngine query error: missing index. Try running this query on dev server to " + 39 | "automatically create needed indexes and then upload to production."), 40 | // this is only produced on client, when server sends malformed error message 41 | LeanExceptionToJSON(204, "Error parsing error JSON data."); 42 | 43 | public int errorCode; 44 | public String errorMessage; 45 | 46 | Error(int errorCode, String errorMessage) { 47 | this.errorCode = errorCode; 48 | this.errorMessage = errorMessage; 49 | } 50 | } 51 | 52 | private int errorCode; 53 | 54 | public LeanException(Error errorType) { 55 | super(errorType.errorMessage); 56 | this.errorCode = errorType.errorCode; 57 | } 58 | 59 | public LeanException(Error errorType, Throwable cause) { 60 | super(errorType.errorMessage, cause); 61 | this.errorCode = errorType.errorCode; 62 | } 63 | 64 | public LeanException(Error errorType, String additionalErrorMessage) { 65 | super(errorType.errorMessage + additionalErrorMessage); 66 | this.errorCode = errorType.errorCode; 67 | } 68 | 69 | public LeanException(Error errorType, String additionalErrorMessage, Throwable cause) { 70 | super(errorType.errorMessage + additionalErrorMessage, cause); 71 | this.errorCode = errorType.errorCode; 72 | } 73 | 74 | public int getErrorCode() { 75 | return errorCode; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | OpenIdLogin 10 | com.leanengine.server.auth.OpenIdLoginServlet 11 | 12 | 13 | OpenIdLogin 14 | /openid 15 | 16 | 17 | FacebookLogin 18 | com.leanengine.server.auth.FacebookLoginServlet 19 | 20 | 21 | FacebookLogin 22 | /facebook 23 | 24 | 25 | Logout 26 | com.leanengine.server.auth.LogoutServlet 27 | 28 | 29 | Logout 30 | /logout 31 | 32 | 33 | LoginError 34 | com.leanengine.server.auth.LoginErrorServlet 35 | 36 | 37 | LoginError 38 | /loginerror 39 | 40 | 41 | 42 | 43 | javax.ws.rs.Application 44 | com.leanengine.server.rest.resteasy.RestApplication 45 | 46 | 47 | resteasy.servlet.mapping.prefix 48 | /rest 49 | 50 | 51 | Resteasy 52 | org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher 53 | 54 | 55 | Resteasy 56 | /rest/* 57 | 58 | 59 | 60 | org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap 61 | 62 | 63 | 64 | 65 | 66 | AuthFilter 67 | com.leanengine.server.auth.AuthFilter 68 | 69 | 70 | AuthFilter 71 | /* 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | home.jsp 111 | 112 | 113 | -------------------------------------------------------------------------------- /lean-server-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | com.leanengine.server 7 | leanengine-server-example 8 | war 9 | 0.8 10 | 11 | 12 | com.leanengine.server 13 | server-parent 14 | 0.8 15 | 16 | 17 | 18 | 19 | 20 | 21 | com.leanengine.server 22 | leanengine-server-lib 23 | 0.8 24 | 25 | 26 | 27 | 28 | javax.servlet 29 | servlet-api 30 | 2.5 31 | provided 32 | 33 | 34 | javax.servlet.jsp 35 | jsp-api 36 | 2.1 37 | provided 38 | 39 | 40 | 41 | 42 | org.mozilla 43 | rhino 44 | 1.7R3 45 | 46 | 47 | 48 | 49 | 50 | 51 | net.kindleit 52 | maven-gae-plugin 53 | 0.9.2 54 | 55 | ${gae.version} 56 | 57 | 58 | 59 | validate 60 | 61 | 62 | unpack 63 | 64 | 65 | 66 | 67 | 68 | net.kindleit 69 | gae-runtime 70 | ${gae.version} 71 | pom 72 | 73 | 74 | 75 | 76 | 77 | maven-resources-plugin 78 | 79 | 80 | 81 | maven-release-plugin 82 | 83 | gae:deploy 84 | 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-compiler-plugin 90 | 2.3.2 91 | 92 | 1.6 93 | 1.6 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/appengine/AccountUtils.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.appengine; 2 | 3 | import com.google.appengine.api.datastore.*; 4 | import com.leanengine.server.auth.AuthToken; 5 | import com.leanengine.server.auth.LeanAccount; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.logging.Logger; 10 | 11 | public class AccountUtils { 12 | private static final Logger log = Logger.getLogger(AccountUtils.class.getName()); 13 | 14 | private static String authTokenKind = "_auth_tokens"; 15 | private static String accountsKind = "_accounts"; 16 | 17 | private static DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 18 | 19 | public static LeanAccount getAccount(long accountID) { 20 | if (accountID <= 0) return null; 21 | Entity accountEntity; 22 | try { 23 | accountEntity = datastore.get(KeyFactory.createKey(accountsKind, accountID)); 24 | } catch (EntityNotFoundException e) { 25 | return null; 26 | } 27 | 28 | return toLeanAccount(accountEntity); 29 | } 30 | 31 | public static LeanAccount findAccountByProvider(String providerID, String provider) { 32 | if (providerID == null) { 33 | log.severe("Empty providerID. Can not find account without providerID."); 34 | return null; 35 | } 36 | Query query = new Query(accountsKind); 37 | query.addFilter("_provider_id", Query.FilterOperator.EQUAL, providerID); 38 | query.addFilter("_provider", Query.FilterOperator.EQUAL, provider); 39 | PreparedQuery pq = datastore.prepare(query); 40 | 41 | Entity accountEntity = pq.asSingleEntity(); 42 | 43 | return (accountEntity == null) ? null : toLeanAccount(accountEntity); 44 | } 45 | 46 | public static LeanAccount findAccountByEmail(String email, String provider) { 47 | if (email == null) { 48 | log.severe("Empty email. Can not find account without email."); 49 | return null; 50 | } 51 | Query query = new Query(accountsKind); 52 | query.addFilter("email", Query.FilterOperator.EQUAL, email); 53 | query.addFilter("_provider", Query.FilterOperator.EQUAL, provider); 54 | PreparedQuery pq = datastore.prepare(query); 55 | 56 | Entity accountEntity = pq.asSingleEntity(); 57 | 58 | return (accountEntity == null) ? null : toLeanAccount(accountEntity); 59 | } 60 | 61 | public static AuthToken getAuthToken(String token) { 62 | //todo use MemCache 63 | Entity tokenEntity; 64 | try { 65 | tokenEntity = datastore.get(KeyFactory.createKey(authTokenKind, token)); 66 | } catch (EntityNotFoundException e) { 67 | return null; 68 | } 69 | 70 | return new AuthToken( 71 | token, 72 | (Long) tokenEntity.getProperty("account"), 73 | (Long) tokenEntity.getProperty("time") 74 | ); 75 | } 76 | 77 | public static void saveAuthToken(AuthToken authToken) { 78 | //todo use MemCache 79 | 80 | Entity tokenEntity = new Entity(authTokenKind, authToken.token); 81 | 82 | tokenEntity.setProperty("account", authToken.accountID); 83 | tokenEntity.setProperty("time", authToken.timeCreated); 84 | datastore.put(tokenEntity); 85 | } 86 | 87 | public static void removeAuthToken(String token) { 88 | //todo use MemCache 89 | datastore.delete(KeyFactory.createKey(authTokenKind, token)); 90 | } 91 | 92 | public static void saveAccount(LeanAccount leanAccount) { 93 | 94 | Entity accountEntity; 95 | 96 | // Is it a new LeanAccount? They do not have 'id' yet. 97 | if (leanAccount.id <= 0) { 98 | // create account 99 | accountEntity = new Entity(accountsKind); 100 | } else { 101 | // update account 102 | accountEntity = new Entity(accountsKind, leanAccount.id); 103 | } 104 | 105 | accountEntity.setProperty("_provider_id", leanAccount.providerId); 106 | accountEntity.setProperty("_provider", leanAccount.provider); 107 | accountEntity.setProperty("_nickname", leanAccount.nickName); 108 | for (Map.Entry property : leanAccount.providerProperties.entrySet()) { 109 | // properties must not start with underscore - this is reserved for system properties 110 | accountEntity.setProperty(property.getKey(), property.getValue()); 111 | } 112 | Key accountKey = datastore.put(accountEntity); 113 | leanAccount.id = accountKey.getId(); 114 | } 115 | 116 | public static LeanAccount toLeanAccount(Entity entity) { 117 | 118 | Map props = new HashMap(entity.getProperties().size() - 3); 119 | for (Map.Entry entityProp : entity.getProperties().entrySet()) { 120 | if(!entityProp.getKey().startsWith("_")) 121 | props.put(entityProp.getKey(), entityProp.getValue()); 122 | } 123 | 124 | return new LeanAccount( 125 | entity.getKey().getId(), 126 | (String) entity.getProperty("_nickname"), 127 | (String) entity.getProperty("_provider_id"), 128 | (String) entity.getProperty("_provider"), 129 | props 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/home.jsp: -------------------------------------------------------------------------------- 1 | <%@ page import="com.google.appengine.api.datastore.Entity" %> 2 | <%@ page import="com.leanengine.server.auth.AuthService" %> 3 | <%@ page import="com.leanengine.server.auth.LeanAccount" %> 4 | <%@ page import="com.leanengine.server.appengine.DatastoreUtils" %> 5 | <%@ page import="java.util.ArrayList" %> 6 | <%@ page import="java.util.List" %> 7 | <%@ page import="java.util.Map" %> 8 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> 9 | <%-- 10 | ~ This software is released under the BSD license. For full license see License-library.txt file. 11 | ~ 12 | ~ Copyright (c) 2011, Peter Knego & Matjaz Tercelj 13 | ~ All rights reserved. 14 | --%> 15 | 16 | 17 | 18 | 19 | 20 | LeanEngine Demo 21 | 22 | 23 | 24 | <% 25 | LeanAccount account = AuthService.getCurrentAccount(); 26 | String show = request.getParameter("show") == null ? "entities" : request.getParameter("show"); 27 | boolean isLoginError = request.getParameter("errorlogin") != null 28 | && request.getParameter("errorlogin").equals("true") ? true : false; 29 | 30 | %> 31 | 32 | 33 |
34 |
35 |
36 | LeanEngine Demo 37 | <%----%> 42 | <% 43 | if (account == null) { 44 | %> 45 |
46 | 64 |
65 | <% 66 | } else { 67 | %> 68 |
69 | 75 |
76 | <% 77 | } 78 | %> 79 |
80 |
81 |
82 | 83 |
84 |
85 |
86 | <% 87 | if (account != null) { 88 | %> 89 | 90 | <% if (show.equals("entities")) {%> 91 |

92 | 93 | 94 | 97 | 98 | 99 | 102 | 105 | 106 | <% 107 | List entities = DatastoreUtils.getPrivateEntities(); 108 | 109 | // protection - does nothing just prevents NPE 110 | if (entities == null) entities = new ArrayList(); 111 | 112 | for (Entity entity : entities) { 113 | StringBuilder props = new StringBuilder(); 114 | String separator = ""; 115 | for (Map.Entry prop : entity.getProperties().entrySet()) { 116 | if (prop.getKey().startsWith("_")) continue; 117 | props.append(separator).append(prop.getKey()).append(":").append(prop.getValue()); 118 | separator = ", "; 119 | } 120 | %> 121 | 122 | 124 | 126 | 127 | <% 128 | } 129 | %> 130 |
95 |
Entities for user: <%=account.nickName%>
96 |
100 |
Name
101 |
103 |
Properties
104 |
<%=entity.getKind()%> 123 | <%=props.toString()%> 125 |
131 | <% 132 | } 133 | } 134 | %> 135 |
136 |
137 |
138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /lean-server-example/src/main/webapp/admin/settings.jsp: -------------------------------------------------------------------------------- 1 | <%@ page import="com.leanengine.server.LeanEngineSettings" %> 2 | <%@ page import="java.util.Map" %> 3 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> 4 | <%-- 5 | ~ This software is released under the BSD license. For full license see License-library.txt file. 6 | ~ 7 | ~ Copyright (c) 2011, Peter Knego & Matjaz Tercelj 8 | ~ All rights reserved. 9 | --%> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 43 | 44 | 45 | <% 46 | boolean fbLoginEnable, openIdLoginEnable; 47 | String fbAppId, fbAppSecret; 48 | boolean saved = false; 49 | 50 | // Was the form submitted? 51 | if (request.getParameter("process") != null) { 52 | fbLoginEnable = request.getParameter("fbLoginEnable") != null; 53 | openIdLoginEnable = request.getParameter("openIdLoginEnable") != null; 54 | fbAppId = request.getParameter("fbAppId"); 55 | fbAppSecret = request.getParameter("fbAppSecret"); 56 | 57 | LeanEngineSettings.Builder settingsBuilder = new LeanEngineSettings.Builder(); 58 | if (fbLoginEnable) { 59 | settingsBuilder.add("fbLoginEnable", fbLoginEnable); 60 | settingsBuilder.add("fbAppId", fbAppId); 61 | settingsBuilder.add("fbAppSecret", fbAppSecret); 62 | } 63 | if (openIdLoginEnable) { 64 | settingsBuilder.add("openIdLoginEnable", openIdLoginEnable); 65 | } 66 | settingsBuilder.save(); 67 | saved = true; 68 | 69 | } else { 70 | // load settings 71 | Map settings = LeanEngineSettings.getSettings(); 72 | fbLoginEnable = settings.get("fbLoginEnable") != null && settings.get("fbLoginEnable").equals(true); 73 | openIdLoginEnable = settings.get("openIdLoginEnable") != null && settings.get("openIdLoginEnable").equals(true); 74 | fbAppId = settings.get("fbAppId") != null ? (String) settings.get("fbAppId") : ""; 75 | fbAppSecret = settings.get("fbAppSecret") != null ? (String) settings.get("fbAppSecret") : ""; 76 | } 77 | %> 78 | 79 | > 80 | 81 |
82 |
83 |
84 |
85 | LeanEngine Settings 86 | 87 |
88 | 89 | 90 |
91 |
    92 |
  • 93 | 98 |
  • 99 |
100 |
101 |
102 | 105 | placeholder="Facebook Application ID" style="margin-top: 5px"/> 106 |
107 |
108 | 111 | placeholder="Facebook Application Secret" style="margin-top: 10px"/> 112 |
113 |
114 | 115 |
116 | 117 | 118 |
119 |
    120 |
  • 121 | 126 |
  • 127 |
128 |
129 |
130 | 131 | 132 |
133 |
134 |
135 | 136 | 137 |   138 |
139 | 144 | 147 | 148 |
149 |
150 | 151 | 152 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/OpenIdLoginServlet.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import com.google.appengine.api.users.User; 4 | import com.google.appengine.api.users.UserServiceFactory; 5 | import com.leanengine.server.LeanEngineSettings; 6 | import com.leanengine.server.LeanException; 7 | import com.leanengine.server.appengine.AccountUtils; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServlet; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import javax.servlet.http.HttpSession; 14 | import java.io.IOException; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | public class OpenIdLoginServlet extends HttpServlet { 19 | 20 | @Override 21 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 22 | 23 | String nextUrl = request.getParameter("next"); 24 | 25 | if (nextUrl == null) { // first part of the OpenID flow 26 | 27 | String type = request.getParameter("type") == null ? "web" : request.getParameter("type"); 28 | 29 | String errorUrl; 30 | if (request.getParameter("onerror") != null) { 31 | errorUrl = request.getParameter("onerror"); 32 | } else { 33 | errorUrl = "/loginerror"; 34 | } 35 | 36 | // redirectUrl is composed so that it redirects twice: 37 | // first to /openid for authentication 38 | // second to the final destination URL 39 | String redirectUrl; 40 | if (type.equals("mobile")) { 41 | redirectUrl = request.getRequestURI()+"?next=@mobile"; 42 | } else { 43 | String redirectParam; 44 | if (request.getParameter("onlogin") != null) { 45 | redirectParam = request.getParameter("onlogin"); 46 | } else { 47 | redirectParam = "/"; 48 | } 49 | redirectUrl = request.getRequestURI()+ "?next=" + redirectParam + "@" + errorUrl; 50 | } 51 | 52 | // check if OpenID is enabled 53 | if (!LeanEngineSettings.isOpenIdLoginEnabled()) { 54 | Scheme scheme; 55 | if (type.equals("mobile")) { 56 | scheme = new MobileScheme(request.getServerName()); 57 | } else { 58 | String hostname = request.getServerName(); 59 | if (request.getLocalPort() != 80 && request.getLocalPort() != 0) { 60 | hostname = hostname + ":" + request.getLocalPort(); 61 | } 62 | scheme = new WebScheme(request.getScheme(), hostname); 63 | } 64 | response.sendRedirect( 65 | scheme.getErrorUrl(new LeanException(LeanException.Error.OpenIdAuthNotEnabled), errorUrl)); 66 | return; 67 | } 68 | 69 | // default OpenID provider is Google 70 | String openIdProvider = request.getParameter("provider"); 71 | 72 | // is it a shortcut? 73 | if (openIdProvider == null || openIdProvider.equals("google")) { 74 | openIdProvider = "https://www.google.com/accounts/o8/id"; 75 | } else if (openIdProvider.equals("yahoo")) { 76 | openIdProvider = "https://me.yahoo.com"; 77 | } 78 | 79 | String loginUrl = UserServiceFactory.getUserService().createLoginURL(redirectUrl, null, openIdProvider, null); 80 | 81 | response.sendRedirect(loginUrl); 82 | 83 | 84 | } else { // second part of the OpenID flow 85 | // type parameters tells us the type of redirect we should perform 86 | Scheme scheme; 87 | String redirectUrl = null; 88 | String errorUrl = null; 89 | 90 | if (nextUrl.equals("@mobile")) { 91 | scheme = new MobileScheme(request.getServerName()); 92 | } else { 93 | String hostname = request.getServerName(); 94 | if (request.getLocalPort() != 80 && request.getLocalPort() != 0) { 95 | hostname = hostname + ":" + request.getLocalPort(); 96 | } 97 | scheme = new WebScheme(request.getScheme(), hostname); 98 | 99 | // extract the redirect URL 100 | String[] stateItems = nextUrl.split("@"); 101 | redirectUrl = (stateItems.length == 2) ? stateItems[0] : null; 102 | errorUrl = (stateItems.length == 2) ? stateItems[1] : null; 103 | } 104 | 105 | 106 | // get user 107 | User currentUser = UserServiceFactory.getUserService().getCurrentUser(); 108 | 109 | //OpenID login did not succeed 110 | if (currentUser == null) { 111 | response.sendRedirect( 112 | scheme.getErrorUrl(new LeanException(LeanException.Error.OpenIdAuthFailed), errorUrl)); 113 | return; 114 | } 115 | // get toke for this user 116 | AuthToken authToken; 117 | 118 | LeanAccount account = AccountUtils.findAccountByProvider(currentUser.getUserId(), 119 | currentUser.getFederatedIdentity()); 120 | 121 | if (account == null) { 122 | //todo this is one-to-one mapping between Account and User - change this in the future 123 | 124 | Map props = new HashMap(); 125 | props.put("email", currentUser.getEmail()); 126 | 127 | // account does not yet exist - create it 128 | account = new LeanAccount( 129 | 0, 130 | currentUser.getNickname(), 131 | currentUser.getUserId(), 132 | currentUser.getFederatedIdentity(), 133 | props 134 | ); 135 | 136 | // saving the LeanAccount sets the 'id' on it 137 | AccountUtils.saveAccount(account); 138 | } 139 | 140 | // create our own authentication token 141 | authToken = AuthService.createAuthToken(account.id); 142 | 143 | // save token in session 144 | HttpSession session = request.getSession(true); 145 | session.setAttribute("lean_token", authToken.token); 146 | 147 | //send lean_token back to browser 148 | try { 149 | response.sendRedirect(scheme.getUrl(authToken.token, redirectUrl)); 150 | } catch (LeanException e) { 151 | response.sendRedirect(scheme.getErrorUrl(e, errorUrl)); 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/entity/LeanQuery.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.entity; 2 | 3 | import com.google.appengine.api.datastore.Cursor; 4 | import com.leanengine.server.JsonUtils; 5 | import com.leanengine.server.LeanException; 6 | import org.codehaus.jackson.JsonNode; 7 | import org.codehaus.jackson.node.ObjectNode; 8 | 9 | import java.io.IOException; 10 | import java.util.ArrayList; 11 | import java.util.Iterator; 12 | import java.util.List; 13 | 14 | public class LeanQuery { 15 | private final String kind; 16 | private List filters = new ArrayList(); 17 | private List sorts = new ArrayList(); 18 | private Cursor cursor; 19 | private Integer offset; 20 | private Integer limit; 21 | 22 | public LeanQuery(String kind) { 23 | this.kind = kind; 24 | } 25 | 26 | public void addFilter(String property, QueryFilter.FilterOperator operator, Object value) { 27 | filters.add(new QueryFilter(property, operator, value)); 28 | } 29 | 30 | public void addSort(String property, QuerySort.SortDirection direction) { 31 | sorts.add(new QuerySort(property, direction)); 32 | } 33 | 34 | public String getKind() { 35 | return kind; 36 | } 37 | 38 | public Cursor getCursor() { 39 | return cursor; 40 | } 41 | 42 | public void setCursor(Cursor cursor) { 43 | this.cursor = cursor; 44 | } 45 | 46 | public List getSorts() { 47 | return sorts; 48 | } 49 | 50 | public List getFilters() { 51 | return filters; 52 | } 53 | 54 | public JsonNode toJson() throws LeanException { 55 | ObjectNode json = JsonUtils.getObjectMapper().createObjectNode(); 56 | json.put("kind", kind); 57 | 58 | if (!filters.isEmpty()) { 59 | ObjectNode jsonFilters = JsonUtils.getObjectMapper().createObjectNode(); 60 | for (QueryFilter filter : filters) { 61 | ObjectNode jsonFilter; 62 | if (jsonFilters.has(filter.getProperty())) { 63 | jsonFilter = (ObjectNode) jsonFilters.get(filter.getProperty()); 64 | } else { 65 | jsonFilter = JsonUtils.getObjectMapper().createObjectNode(); 66 | } 67 | JsonUtils.addTypedNode(jsonFilter, filter.getOperator().toJSON(), filter.getValue()); 68 | jsonFilters.put(filter.getProperty(), jsonFilter); 69 | } 70 | json.put("filter", jsonFilters); 71 | } 72 | 73 | if (!sorts.isEmpty()) { 74 | ObjectNode jsonSorts = JsonUtils.getObjectMapper().createObjectNode(); 75 | for (QuerySort sort : sorts) { 76 | jsonSorts.put(sort.getProperty(), sort.getDirection().toJSON()); 77 | } 78 | json.put("sort", jsonSorts); 79 | } 80 | 81 | if (this.cursor != null) { 82 | json.put("cursor", cursor.toWebSafeString()); 83 | } 84 | 85 | return json; 86 | } 87 | 88 | public static LeanQuery fromJson(String json) throws LeanException { 89 | ObjectNode jsonNode; 90 | try { 91 | jsonNode = (ObjectNode) JsonUtils.getObjectMapper().readTree(json); 92 | } catch (IOException e) { 93 | throw new LeanException(LeanException.Error.QueryJSON); 94 | } catch (ClassCastException cce) { 95 | throw new LeanException(LeanException.Error.QueryJSON, " Expected JSON object, instead got JSON array."); 96 | } 97 | 98 | // get the 'kind' of the query 99 | LeanQuery query = new LeanQuery(jsonNode.get("kind").getTextValue()); 100 | if (query.getKind() == null) { 101 | throw new LeanException(LeanException.Error.QueryJSON, " Missing 'kind' property."); 102 | } 103 | 104 | // get 'filter' 105 | ObjectNode filters; 106 | try { 107 | filters = (ObjectNode) jsonNode.get("filter"); 108 | } catch (ClassCastException cce) { 109 | throw new LeanException(LeanException.Error.QueryJSON, " Property 'filter' must be a JSON object."); 110 | } 111 | if (filters != null) { 112 | Iterator filterIterator = filters.getFieldNames(); 113 | while (filterIterator.hasNext()) { 114 | String filterProperty = filterIterator.next(); 115 | ObjectNode filter; 116 | try { 117 | filter = (ObjectNode) filters.get(filterProperty); 118 | } catch (ClassCastException cce) { 119 | throw new LeanException(LeanException.Error.QueryJSON, " Filter value must be a JSON object."); 120 | } 121 | Iterator operatorIterator = filter.getFieldNames(); 122 | while (operatorIterator.hasNext()) { 123 | String operator = operatorIterator.next(); 124 | Object filterValue = JsonUtils.propertyFromJson(filter.get(operator)); 125 | query.addFilter( 126 | filterProperty, 127 | QueryFilter.FilterOperator.create(operator), 128 | filterValue); 129 | } 130 | } 131 | } 132 | 133 | // get 'sort' 134 | ObjectNode sorts; 135 | try { 136 | sorts = (ObjectNode) jsonNode.get("sort"); 137 | } catch (ClassCastException cce) { 138 | throw new LeanException(LeanException.Error.QueryJSON, " Property 'sort' must be a JSON object."); 139 | } 140 | if (sorts != null) { 141 | Iterator sortIterator = sorts.getFieldNames(); 142 | while (sortIterator.hasNext()) { 143 | String sortProperty = sortIterator.next(); 144 | query.addSort(sortProperty, QuerySort.SortDirection.create(sorts.get(sortProperty).getTextValue())); 145 | } 146 | } 147 | 148 | // get 'cursor' 149 | JsonNode cursorNode = jsonNode.get("cursor"); 150 | if (cursorNode != null) { 151 | query.cursor = Cursor.fromWebSafeString(cursorNode.getTextValue()); 152 | } 153 | 154 | // get 'cursor' 155 | JsonNode limitNode = jsonNode.get("limit"); 156 | if (limitNode != null) { 157 | query.limit = limitNode.getIntValue(); 158 | } 159 | 160 | // get 'cursor' 161 | JsonNode offsetNode = jsonNode.get("offset"); 162 | if (offsetNode != null) { 163 | query.offset = offsetNode.getIntValue(); 164 | } 165 | 166 | return query; 167 | } 168 | 169 | public Integer getOffset() { 170 | return offset; 171 | } 172 | 173 | public void setOffset(int offset) { 174 | this.offset = offset; 175 | } 176 | 177 | public Integer getLimit() { 178 | return limit; 179 | } 180 | 181 | public void setLimit(int limit) { 182 | this.limit = limit; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/DumpFilter.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server; 2 | 3 | import javax.servlet.*; 4 | import javax.servlet.http.HttpServletRequest; 5 | import javax.servlet.http.HttpServletRequestWrapper; 6 | import javax.servlet.http.HttpServletResponse; 7 | import javax.servlet.http.HttpServletResponseWrapper; 8 | import java.io.*; 9 | import java.util.Enumeration; 10 | import java.util.logging.Logger; 11 | 12 | /** 13 | * This a straightforward request and reply, header & body dump filter. Usable when trableshooting. 14 | * Just enable it in web.xml with {@code}, {@code} and {@code}:

15 | * {@code}
16 | * {@code DumpFilter}
17 | * {@code com.leanengine.server.DumpFilter}
18 | * {@code }
19 | * {@code dumpRequest}
20 | * {@code true}
21 | * {@code
}
22 | * {@code }
23 | * {@code dumpResponse}
24 | * {@code true}
25 | * {@code
}
26 | * {@code }
27 | * {@code dumpHeader}
28 | * {@code true}
29 | * {@code
}
30 | * {@code
}
31 | * {@code }
32 | * {@code DumpFilter}
33 | * {@code /some/url/*}
34 | * {@code
}
35 | */ 36 | public class DumpFilter implements Filter { 37 | 38 | private static final Logger log = Logger.getLogger(DumpFilter.class.getName()); 39 | 40 | private static class ByteArrayServletStream extends ServletOutputStream { 41 | 42 | ByteArrayOutputStream baos; 43 | 44 | ByteArrayServletStream(ByteArrayOutputStream baos) { 45 | this.baos = baos; 46 | } 47 | 48 | public void write(int param) throws IOException { 49 | baos.write(param); 50 | } 51 | } 52 | 53 | private static class ByteArrayPrintWriter { 54 | 55 | private ByteArrayOutputStream baos = new ByteArrayOutputStream(); 56 | 57 | private PrintWriter pw = new PrintWriter(baos); 58 | 59 | private ServletOutputStream sos = new ByteArrayServletStream(baos); 60 | 61 | public PrintWriter getWriter() { 62 | return pw; 63 | } 64 | 65 | public ServletOutputStream getStream() { 66 | return sos; 67 | } 68 | 69 | byte[] toByteArray() { 70 | return baos.toByteArray(); 71 | } 72 | } 73 | 74 | private class BufferedServletInputStream extends ServletInputStream { 75 | 76 | ByteArrayInputStream bais; 77 | 78 | public BufferedServletInputStream(ByteArrayInputStream bais) { 79 | this.bais = bais; 80 | } 81 | 82 | public int available() { 83 | return bais.available(); 84 | } 85 | 86 | public int read() { 87 | return bais.read(); 88 | } 89 | 90 | public int read(byte[] buf, int off, int len) { 91 | return bais.read(buf, off, len); 92 | } 93 | 94 | } 95 | 96 | private class BufferedRequestWrapper extends HttpServletRequestWrapper { 97 | 98 | ByteArrayInputStream bais; 99 | 100 | ByteArrayOutputStream baos; 101 | 102 | BufferedServletInputStream bsis; 103 | 104 | byte[] buffer; 105 | 106 | public BufferedRequestWrapper(HttpServletRequest req) throws IOException { 107 | super(req); 108 | InputStream is = req.getInputStream(); 109 | baos = new ByteArrayOutputStream(); 110 | byte buf[] = new byte[1024]; 111 | int letti; 112 | while ((letti = is.read(buf)) > 0) { 113 | baos.write(buf, 0, letti); 114 | } 115 | buffer = baos.toByteArray(); 116 | } 117 | 118 | public ServletInputStream getInputStream() { 119 | try { 120 | bais = new ByteArrayInputStream(buffer); 121 | bsis = new BufferedServletInputStream(bais); 122 | } catch (Exception ex) { 123 | ex.printStackTrace(); 124 | } 125 | 126 | return bsis; 127 | } 128 | 129 | public byte[] getBuffer() { 130 | return buffer; 131 | } 132 | 133 | } 134 | 135 | private boolean dumpRequest; 136 | private boolean dumpResponse; 137 | private boolean dumpHeader; 138 | 139 | public void init(FilterConfig filterConfig) throws ServletException { 140 | dumpRequest = Boolean.valueOf(filterConfig.getInitParameter("dumpRequest")); 141 | dumpResponse = Boolean.valueOf(filterConfig.getInitParameter("dumpResponse")); 142 | dumpHeader = Boolean.valueOf(filterConfig.getInitParameter("dumpHeader")); 143 | } 144 | 145 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, 146 | FilterChain filterChain) throws IOException, ServletException { 147 | 148 | final HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; 149 | BufferedRequestWrapper bufferedRequest = new BufferedRequestWrapper(httpRequest); 150 | 151 | if (dumpRequest) { 152 | // log.info("REQUEST -> " + new String(bufferedRequest.getBuffer())); 153 | System.out.println("REQUEST URL: " + httpRequest.getServletPath()); 154 | System.out.println("REQUEST -> " + new String(bufferedRequest.getBuffer())); 155 | } 156 | 157 | if (dumpHeader) { 158 | // log.info(" REQUEST HEADER:"); 159 | System.out.println(" REQUEST HEADER:"); 160 | 161 | Enumeration headers = httpRequest.getHeaderNames(); 162 | while (headers.hasMoreElements()) { 163 | String header = (String) headers.nextElement(); 164 | System.out.println(header + "=" + httpRequest.getHeader(header)); 165 | } 166 | } 167 | 168 | 169 | final HttpServletResponse response = (HttpServletResponse) servletResponse; 170 | 171 | final ByteArrayPrintWriter pw = new ByteArrayPrintWriter(); 172 | HttpServletResponse wrappedResp = new HttpServletResponseWrapper(response) { 173 | public PrintWriter getWriter() { 174 | return pw.getWriter(); 175 | } 176 | 177 | public ServletOutputStream getOutputStream() { 178 | return pw.getStream(); 179 | } 180 | 181 | }; 182 | 183 | filterChain.doFilter(bufferedRequest, wrappedResp); 184 | 185 | byte[] bytes = pw.toByteArray(); 186 | response.getOutputStream().write(bytes); 187 | if (dumpResponse) { 188 | // log.info("RESPONSE -> " + new String(bytes)); 189 | System.out.println("RESPONSE -> " + new String(bytes)); 190 | } 191 | } 192 | 193 | public void destroy() { 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/appengine/DatastoreUtils.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.appengine; 2 | 3 | import com.google.appengine.api.datastore.*; 4 | import com.leanengine.server.LeanException; 5 | import com.leanengine.server.auth.AuthService; 6 | import com.leanengine.server.auth.LeanAccount; 7 | import com.leanengine.server.entity.*; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.logging.Logger; 13 | import java.util.regex.Pattern; 14 | 15 | public class DatastoreUtils { 16 | 17 | private static final Logger log = Logger.getLogger(DatastoreUtils.class.getName()); 18 | 19 | private static DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 20 | private static Pattern pattern = Pattern.compile("^[A-Za-z][A-Za-z_0-9]*"); 21 | 22 | public static Entity getPrivateEntity(String kind, long entityId) throws LeanException { 23 | LeanAccount account = findCurrentAccount(); 24 | 25 | if (entityId <= 0 || kind == null) throw new LeanException(LeanException.Error.EntityNotFound, 26 | " Entity 'kind' and 'id' must NOT be null."); 27 | 28 | Entity entity; 29 | try { 30 | entity = datastore.get(KeyFactory.createKey(kind, entityId)); 31 | } catch (EntityNotFoundException e) { 32 | throw new LeanException(LeanException.Error.EntityNotFound); 33 | } 34 | 35 | if (account.id != (Long) entity.getProperty("_account")) 36 | throw new LeanException(LeanException.Error.NotAuthorized, 37 | " Account not authorized to access entity '" + kind + "'with ID '" + entityId + "'"); 38 | 39 | return entity; 40 | } 41 | 42 | 43 | public static void deletePrivateEntity(String entityKind, long entityId) throws LeanException { 44 | LeanAccount account = findCurrentAccount(); 45 | 46 | if (entityId <= 0 || entityKind == null) throw new LeanException(LeanException.Error.EntityNotFound, 47 | " Entity 'kind' and 'id' must NOT be null."); 48 | 49 | Entity entity; 50 | try { 51 | entity = datastore.get(KeyFactory.createKey(entityKind, entityId)); 52 | } catch (EntityNotFoundException e) { 53 | throw new LeanException(LeanException.Error.EntityNotFound); 54 | } 55 | 56 | if (account.id != (Long) entity.getProperty("_account")) 57 | throw new LeanException(LeanException.Error.NotAuthorized, 58 | " Account not authorized to access entity '" + entityKind + "'with ID '" + entityId + "'"); 59 | 60 | datastore.delete(entity.getKey()); 61 | } 62 | 63 | private static LeanAccount findCurrentAccount() throws LeanException { 64 | LeanAccount account = AuthService.getCurrentAccount(); 65 | // this should not happen, but we check anyway 66 | if (account == null) throw new LeanException(LeanException.Error.NotAuthorized); 67 | return account; 68 | } 69 | 70 | public static List getPrivateEntities() throws LeanException { 71 | findCurrentAccount(); 72 | 73 | List kindNames = findAllEntityKinds(); 74 | 75 | List result = new ArrayList(); 76 | 77 | for (String kindName : kindNames) { 78 | result.addAll(getPrivateEntities(kindName)); 79 | } 80 | 81 | return result; 82 | } 83 | 84 | public static List getPrivateEntities(String kind) throws LeanException { 85 | 86 | if (!pattern.matcher(kind).matches()) { 87 | throw new LeanException(LeanException.Error.IllegalEntityName); 88 | } 89 | 90 | LeanAccount account = AuthService.getCurrentAccount(); 91 | // this should not happen, but we check anyway 92 | if (account == null) throw new LeanException(LeanException.Error.NotAuthorized); 93 | 94 | Query query = new Query(kind); 95 | query.addFilter("_account", Query.FilterOperator.EQUAL, account.id); 96 | PreparedQuery pq = datastore.prepare(query); 97 | 98 | return pq.asList(FetchOptions.Builder.withDefaults()); 99 | } 100 | 101 | public static long putPrivateEntity(String entityName, Map properties) throws LeanException { 102 | 103 | if (!pattern.matcher(entityName).matches()) { 104 | throw new LeanException(LeanException.Error.IllegalEntityName); 105 | } 106 | 107 | Entity entityEntity = new Entity(entityName); 108 | entityEntity.setProperty("_account", AuthService.getCurrentAccount().id); 109 | 110 | if (properties != null) { 111 | for (Map.Entry entry : properties.entrySet()) { 112 | entityEntity.setProperty(entry.getKey(), entry.getValue()); 113 | } 114 | } 115 | Key result = datastore.put(entityEntity); 116 | return result.getId(); 117 | } 118 | 119 | public static QueryResult queryEntityPrivate(LeanQuery leanQuery) throws LeanException { 120 | LeanAccount account = findCurrentAccount(); 121 | 122 | Query query = new Query(leanQuery.getKind()); 123 | query.addFilter("_account", Query.FilterOperator.EQUAL, account.id); 124 | 125 | for (QueryFilter queryFilter : leanQuery.getFilters()) { 126 | query.addFilter( 127 | queryFilter.getProperty(), 128 | queryFilter.getOperator().getFilterOperator(), 129 | queryFilter.getValue()); 130 | } 131 | 132 | for (QuerySort querySort : leanQuery.getSorts()) { 133 | query.addSort(querySort.getProperty(), querySort.getDirection().getSortDirection()); 134 | } 135 | 136 | FetchOptions fetchOptions = FetchOptions.Builder.withDefaults(); 137 | if(leanQuery.getCursor() != null ) 138 | fetchOptions.startCursor(leanQuery.getCursor()); 139 | if(leanQuery.getOffset() != null) 140 | fetchOptions.offset(leanQuery.getOffset()); 141 | if(leanQuery.getLimit() != null) 142 | fetchOptions.limit(leanQuery.getLimit()); 143 | 144 | try { 145 | PreparedQuery pq = datastore.prepare(query); 146 | 147 | QueryResultList result; 148 | result = pq.asQueryResultList(fetchOptions); 149 | 150 | return new QueryResult(result, result.getCursor()); 151 | } catch (DatastoreNeedIndexException dnie) { 152 | throw new LeanException(LeanException.Error.AppEngineMissingIndex); 153 | } 154 | } 155 | 156 | public static List findAllEntityKinds() throws LeanException { 157 | 158 | Query q = new Query(Query.KIND_METADATA_KIND); 159 | 160 | DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 161 | PreparedQuery pq = datastore.prepare(q); 162 | 163 | List list = pq.asList(FetchOptions.Builder.withDefaults()); 164 | 165 | List result = new ArrayList(); 166 | for (Entity entity : list) { 167 | if (!entity.getKey().getName().startsWith("_")) 168 | result.add(entity.getKey().getName()); 169 | } 170 | 171 | return result; 172 | 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/FacebookAuth.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import com.google.appengine.api.urlfetch.HTTPResponse; 4 | import com.google.appengine.api.urlfetch.URLFetchService; 5 | import com.google.appengine.api.urlfetch.URLFetchServiceFactory; 6 | import com.leanengine.server.JsonUtils; 7 | import com.leanengine.server.LeanEngineSettings; 8 | import com.leanengine.server.LeanException; 9 | import com.leanengine.server.appengine.AccountUtils; 10 | import org.codehaus.jackson.JsonNode; 11 | import org.codehaus.jackson.JsonProcessingException; 12 | import org.codehaus.jackson.map.ObjectMapper; 13 | 14 | import java.io.IOException; 15 | import java.net.MalformedURLException; 16 | import java.net.URL; 17 | import java.nio.charset.Charset; 18 | import java.util.HashMap; 19 | import java.util.Iterator; 20 | import java.util.Map; 21 | import java.util.logging.Logger; 22 | 23 | public class FacebookAuth { 24 | 25 | private static final Logger log = Logger.getLogger(FacebookAuth.class.getName()); 26 | 27 | public static String getLoginUrlMobile(String serverName, String state, String facebookLoginPath, String display) throws LeanException { 28 | if (LeanEngineSettings.getFacebookAppID() == null) { 29 | throw new LeanException(LeanException.Error.FacebookAuthMissingAppId); 30 | } 31 | 32 | String redirectUrl = serverName + facebookLoginPath; 33 | StringBuilder stringBuilder = new StringBuilder(); 34 | stringBuilder 35 | .append("https://m.facebook.com/dialog/oauth?") 36 | .append("client_id=").append(LeanEngineSettings.getFacebookAppID()).append("&") 37 | .append("redirect_uri=").append(redirectUrl).append("&"); 38 | 39 | if (display != null) 40 | stringBuilder.append("display=").append(display).append("&"); 41 | 42 | stringBuilder.append("scope=offline_access&") 43 | .append("response_type=code&") 44 | .append("state=") 45 | .append(state); 46 | return stringBuilder.toString(); 47 | } 48 | 49 | public static String getLoginUrlWeb(String serverName, String state, String facebookLoginPath) throws LeanException { 50 | if (LeanEngineSettings.getFacebookAppID() == null) { 51 | throw new LeanException(LeanException.Error.FacebookAuthMissingAppId); 52 | } 53 | 54 | String redirectUrl = serverName + facebookLoginPath; 55 | return "https://www.facebook.com/dialog/oauth?" + 56 | "client_id=" + LeanEngineSettings.getFacebookAppID() + "&" + 57 | "redirect_uri=" + redirectUrl + "&" + 58 | "scope=offline_access&" + 59 | "response_type=code&" + 60 | "state=" + state; 61 | } 62 | 63 | public static String getGraphAuthUrl(String redirectUrl, String code) throws LeanException { 64 | if (LeanEngineSettings.getFacebookAppID() == null) { 65 | throw new LeanException(LeanException.Error.FacebookAuthMissingAppId); 66 | } 67 | 68 | if (LeanEngineSettings.getFacebookAppID() == null) { 69 | throw new LeanException(LeanException.Error.FacebookAuthMissingAppSecret); 70 | } 71 | 72 | return "https://graph.facebook.com/oauth/access_token?" + 73 | "client_id=" + LeanEngineSettings.getFacebookAppID() + "&" + 74 | "redirect_uri=" + redirectUrl + "&" + 75 | "client_secret=" + LeanEngineSettings.getFacebookAppSecret() + "&" + 76 | "code=" + code; 77 | } 78 | 79 | public static JsonNode fetchUserDataFromFacebook(String access_token) throws LeanException { 80 | String url = "https://graph.facebook.com/me?access_token=" + access_token; 81 | URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService(); 82 | HTTPResponse fetchResponse; 83 | try { 84 | fetchResponse = fetchService.fetch(new URL(url)); 85 | if (fetchResponse.getResponseCode() == 200) { 86 | return JsonUtils.getObjectMapper().readTree(new String(fetchResponse.getContent(), "UTF-8")); 87 | } else { 88 | throw new LeanException(LeanException.Error.FacebookAuthError); 89 | } 90 | } catch (MalformedURLException ex) { 91 | //todo This should not happen - log it 92 | } catch (JsonProcessingException ex) { 93 | throw new LeanException(LeanException.Error.FacebookAuthParseError, ex); 94 | } catch (IOException e) { 95 | throw new LeanException(LeanException.Error.FacebookAuthConnectError, e); 96 | } 97 | return null; 98 | } 99 | 100 | public static AuthToken authenticateWithOAuthGraphAPI(String currentUrl, String code) throws LeanException { 101 | 102 | try { 103 | URL facebookGraphUrl = new URL(FacebookAuth.getGraphAuthUrl(currentUrl, code)); 104 | log.info("facebookGraphUrl="+facebookGraphUrl); 105 | URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService(); 106 | HTTPResponse fetchResponse = fetchService.fetch(facebookGraphUrl); 107 | String responseContent = new String(fetchResponse.getContent(), Charset.forName("UTF-8")); 108 | if (fetchResponse.getResponseCode() == 400) { 109 | 110 | // error: facebook server error replied with 400 111 | throw new LeanException(LeanException.Error.FacebookAuthResponseError," \n\n"+responseContent); 112 | } 113 | 114 | String fbAccessToken = null, expires = null; 115 | String[] splitResponse = responseContent.split("&"); 116 | 117 | for (String split : splitResponse) { 118 | String[] parts = split.split("="); 119 | if (parts.length != 2) break; 120 | fbAccessToken = parts[0].equals("access_token") ? parts[1] : fbAccessToken; 121 | expires = parts[0].equals("expires") ? parts[1] : expires; 122 | } 123 | 124 | // check if we got required parameters 125 | if (fbAccessToken == null) { 126 | //error: wrong parameters: facebook should return 'access_token' parameter 127 | throw new LeanException(LeanException.Error.FacebookAuthMissingParam, " 'access_token' not available."); 128 | } 129 | 130 | // All is good - check the user 131 | JsonNode userData = FacebookAuth.fetchUserDataFromFacebook(fbAccessToken); 132 | String providerID = userData.get("id").getTextValue(); 133 | 134 | if (providerID == null || providerID.length() == 0) { 135 | //Facebook returned user data but ID field is missing 136 | throw new LeanException(LeanException.Error.FacebookAuthMissingParam, 137 | " Missing ID field in user data. Content: " + responseContent); 138 | } 139 | 140 | LeanAccount account = AccountUtils.findAccountByProvider(providerID, "fb-oauth"); 141 | if (account == null) { 142 | //todo this is one-to-one mapping between Account and User 143 | //change this in the future 144 | 145 | // account does not yet exist - create it 146 | account = parseAccountFB(userData); 147 | AccountUtils.saveAccount(account); 148 | } 149 | 150 | // create our own authentication token 151 | return AuthService.createAuthToken(account.id); 152 | 153 | } catch (MalformedURLException e) { 154 | throw new LeanException(LeanException.Error.FacebookAuthNoConnection, e); 155 | } catch (IOException e) { 156 | throw new LeanException(LeanException.Error.FacebookAuthNoConnection, e); 157 | } 158 | } 159 | 160 | 161 | /** 162 | * Creates LeanAccount form data returned by Facebook authentication service 163 | * 164 | * @param userData JSON data as provided by Facebook OAuth 165 | * @return 166 | */ 167 | public static LeanAccount parseAccountFB(JsonNode userData) { 168 | 169 | Map props = new HashMap(userData.size()); 170 | Iterator fields = userData.getFieldNames(); 171 | while (fields.hasNext()) { 172 | String field = fields.next(); 173 | // field 'id' is not treated as part of provider properties 174 | if (field.equals("id")) continue; 175 | props.put(field, userData.get(field).getValueAsText()); 176 | } 177 | 178 | return new LeanAccount( 179 | 0, 180 | userData.get("name").getTextValue(), 181 | userData.get("id").getTextValue(), 182 | "fb-oauth", 183 | props); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/auth/FacebookLoginServlet.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server.auth; 2 | 3 | import com.leanengine.server.LeanEngineSettings; 4 | import com.leanengine.server.LeanException; 5 | import com.leanengine.server.appengine.ServerUtils; 6 | 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.HttpServlet; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import javax.servlet.http.HttpSession; 12 | import java.io.IOException; 13 | import java.util.UUID; 14 | 15 | public class FacebookLoginServlet extends HttpServlet { 16 | 17 | @Override 18 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 19 | 20 | String errorCode = request.getParameter("error"); 21 | String authorizationCode = request.getParameter("code"); 22 | 23 | // checking the stage of a Facebook OAuth flow 24 | if (errorCode == null && authorizationCode == null) { // first part of Facebook OAuth flow 25 | 26 | String type = request.getParameter("type"); 27 | String display = request.getParameter("display"); 28 | Scheme scheme; 29 | String facebookAntiCSRF; 30 | 31 | // checking if request is coming from mobile client 32 | boolean isMobile = type != null && type.equals("mobile"); 33 | 34 | String redirectUrl; 35 | if (request.getParameter("onlogin") != null) { 36 | redirectUrl = request.getParameter("onlogin"); 37 | } else { 38 | redirectUrl = "/"; 39 | } 40 | 41 | String errorUrl; 42 | if (request.getParameter("onerror") != null) { 43 | errorUrl = request.getParameter("onerror"); 44 | } else { 45 | errorUrl = "/loginerror"; 46 | } 47 | 48 | if (isMobile) { 49 | // login via mobile device - this means redirecting to 'leanengine://hostname/?access_token=..' URLs 50 | scheme = new MobileScheme(request.getServerName()); 51 | // we add 'mob:' in front of state parameter to indicate that request comes from mobile device 52 | facebookAntiCSRF = "mob:" + UUID.randomUUID().toString(); 53 | } else { 54 | // login via web interface 55 | String hostname = request.getServerName(); 56 | if (request.getLocalPort() != 80 && request.getLocalPort() != 0) { 57 | hostname = hostname + ":" + request.getLocalPort(); 58 | } 59 | scheme = new WebScheme(request.getScheme(), hostname); 60 | facebookAntiCSRF = "web:" + UUID.randomUUID().toString() + ":" + redirectUrl + ":" + errorUrl; 61 | } 62 | 63 | // Facebook login is not enabled in settings 64 | if (!LeanEngineSettings.isFacebookLoginEnabled()) { 65 | response.sendRedirect( 66 | scheme.getErrorUrl(new LeanException(LeanException.Error.FacebookAuthNotEnabled), errorUrl)); 67 | return; 68 | } 69 | 70 | // development server mocks Facebook logins 71 | if (ServerUtils.isDevServer()) { 72 | String mockEmail = request.getParameter("email"); 73 | String action = request.getParameter("action"); 74 | if (mockEmail == null) { 75 | FacebookMockLogin.showForm(request, response, isMobile); 76 | } else { 77 | if ("Log Out".equals(action)) { 78 | response.sendRedirect(scheme.getErrorUrl( 79 | new LeanException(LeanException.Error.FacebookAuthError, " User cancelled login."), errorUrl)); 80 | } else { 81 | FacebookMockLogin.login(request, response, mockEmail, scheme, redirectUrl, errorUrl); 82 | } 83 | } 84 | return; 85 | } 86 | 87 | // 'state' parameter is passed around the Facebook OAuth redirects and ends up at our final auth page 88 | // Primarily it's used for preventing CSRF attacks, but we also use it to signal the login type to the final auth page 89 | HttpSession session = request.getSession(true); 90 | session.setAttribute("antiCSRF", facebookAntiCSRF); 91 | 92 | // get Facebook OAuth Login URL 93 | String loginUrl = null; 94 | try { 95 | loginUrl = isMobile ? 96 | FacebookAuth.getLoginUrlMobile(request.getScheme() + "://" + request.getServerName(), 97 | facebookAntiCSRF, request.getRequestURI(), display) : 98 | FacebookAuth.getLoginUrlWeb(request.getScheme() + "://" + request.getServerName(), 99 | facebookAntiCSRF, request.getRequestURI()); 100 | } catch (LeanException e) { 101 | response.sendRedirect(scheme.getErrorUrl(e, errorUrl)); 102 | return; 103 | } 104 | response.sendRedirect(loginUrl); 105 | 106 | } else { // second part of Facebook OAuth flow 107 | 108 | // user is trying to login 109 | // reset the existing auth data if it exists 110 | AuthService.resetCurrentAuthData(); 111 | HttpSession session = request.getSession(true); 112 | session.removeAttribute("lean_token"); 113 | 114 | // include the port number - usually needed for Dev server 115 | String hostname = request.getServerName(); 116 | if (request.getLocalPort() != 80 && request.getLocalPort() != 0) { 117 | hostname = hostname + ":" + request.getLocalPort(); 118 | } 119 | 120 | // get the 'state' parameter containing CSRF code 121 | String state = request.getParameter("state"); 122 | // 'state' must be equal to 'antiCSRF' attribute saved to web session 123 | if (state == null || !state.equals(session.getAttribute("antiCSRF"))) { 124 | // oauth error - redirect back to client with error 125 | response.sendRedirect(new WebScheme(request.getScheme(), 126 | hostname).getErrorUrl(new LeanException(LeanException.Error.FacebookAuthMissingCRSF), "/")); 127 | return; 128 | } 129 | 130 | String redirectUrl = null; 131 | String errorUrl = null; 132 | 133 | // 'state' parameter has format "login_type:CSRFtoken:redirect_url:error_url" 134 | // extract the login type from 'state' parameter 135 | Scheme scheme; 136 | if (state.startsWith("mob:")) { 137 | scheme = new MobileScheme(request.getServerName()); 138 | } else { 139 | scheme = new WebScheme(request.getScheme(), hostname); 140 | 141 | // extract the redirect URL 142 | String[] stateItems = state.split(":"); 143 | redirectUrl = (stateItems.length == 4) ? stateItems[2] : null; 144 | errorUrl = (stateItems.length == 4) ? stateItems[3] : null; 145 | } 146 | 147 | // error url might not have been supplied if this second part of the flow was invoked directly 148 | errorUrl = (errorUrl == null || errorUrl.isEmpty()) ? "/loginerror" : errorUrl; 149 | 150 | // did Facebook OAuth return error? 151 | if (errorCode != null) { 152 | // oauth error - redirect back to client with error 153 | response.sendRedirect(scheme.getErrorUrl( 154 | new LeanException(LeanException.Error.FacebookAuthError, " OAuth error: " + errorCode), errorUrl)); 155 | 156 | } else { // no error 157 | String currentUrl = request.getRequestURL().toString(); 158 | 159 | try { 160 | // authenticate with Facebook Graph OAuth API 161 | // this makes a direct connection from server to 'https://graph.facebook.com/oauth/access_token' 162 | AuthToken lean_token = FacebookAuth.authenticateWithOAuthGraphAPI(currentUrl, authorizationCode); 163 | 164 | // save token in session 165 | session.setAttribute("lean_token", lean_token.token); 166 | 167 | //send lean_token back to browser 168 | response.sendRedirect(scheme.getUrl(lean_token.token, redirectUrl)); 169 | 170 | } catch (LeanException le) { 171 | response.sendRedirect(scheme.getErrorUrl(le, errorUrl)); 172 | } 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /lean-server-lib/src/main/java/com/leanengine/server/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package com.leanengine.server; 2 | 3 | import com.google.appengine.api.datastore.Entity; 4 | import com.google.appengine.api.datastore.Text; 5 | import org.codehaus.jackson.JsonNode; 6 | import org.codehaus.jackson.map.ObjectMapper; 7 | import org.codehaus.jackson.node.ArrayNode; 8 | import org.codehaus.jackson.node.ObjectNode; 9 | 10 | import java.util.*; 11 | 12 | public class JsonUtils { 13 | 14 | private static ThreadLocal tlObjectMapper = new ThreadLocal(); 15 | 16 | /** 17 | * Returns a thread-local instance of JSON ObjectMapper. 18 | * 19 | * @return ObjectMapper. 20 | */ 21 | public static ObjectMapper getObjectMapper() { 22 | ObjectMapper objectMapper = tlObjectMapper.get(); 23 | if (objectMapper == null) { 24 | objectMapper = initObjectMapper(); 25 | tlObjectMapper.set(objectMapper); 26 | } 27 | return objectMapper; 28 | } 29 | 30 | private static ObjectMapper initObjectMapper() { 31 | return new ObjectMapper(); 32 | } 33 | 34 | public static ObjectNode entityListToJson(List entityList) throws LeanException { 35 | ObjectNode json = getObjectMapper().createObjectNode(); 36 | ArrayNode array = getObjectMapper().createArrayNode(); 37 | 38 | for (Entity entity : entityList) { 39 | array.add(entityToJson(entity)); 40 | } 41 | json.put("result", array); 42 | return json; 43 | } 44 | 45 | public static JsonNode entityToJson(Entity entity) throws LeanException { 46 | ObjectNode json = getObjectMapper().createObjectNode(); 47 | json.put("_id", entity.getKey().getId()); 48 | json.putPOJO("_kind", entity.getKind()); 49 | json.putPOJO("_account", entity.getProperty("_account")); 50 | Map props = entity.getProperties(); 51 | for (Map.Entry prop : props.entrySet()) { 52 | addTypedNode(json, prop.getKey(), prop.getValue()); 53 | } 54 | return json; 55 | } 56 | 57 | public static Map entityPropertiesFromJson(JsonNode jsonNode) throws LeanException { 58 | Map props = new HashMap(jsonNode.size()); 59 | 60 | // must have some properties 61 | if (jsonNode.size() == 0) throw new LeanException(LeanException.Error.EmptyEntity); 62 | 63 | Iterator fieldNames = jsonNode.getFieldNames(); 64 | while (fieldNames.hasNext()) { 65 | String field = fieldNames.next(); 66 | 67 | // skip LeanEngine system properties (starting with underscore '_') 68 | if (field.startsWith("_")) continue; 69 | JsonNode subNode = jsonNode.get(field); 70 | props.put(field, propertyFromJson(subNode)); 71 | } 72 | return props; 73 | } 74 | 75 | public static Object propertyFromJson(JsonNode node) throws LeanException { 76 | 77 | if (node.isObject()) { 78 | return typedObjectFromJson((ObjectNode) node); 79 | } else if (node.isArray()) { 80 | return typedArrayFromJson((ArrayNode) node); 81 | } else if (node.isLong()) { 82 | return node.getLongValue(); 83 | } else if (node.isInt()) { 84 | return node.getIntValue(); 85 | } else if (node.isDouble()) { 86 | return node.getDoubleValue(); 87 | } else if (node.isBoolean()) { 88 | return node.getBooleanValue(); 89 | } else if (node.isTextual()) { 90 | return node.getTextValue(); 91 | } else { 92 | throw new LeanException(LeanException.Error.ValueToJSON, " Unknown value node type."); 93 | } 94 | } 95 | 96 | private static List typedArrayFromJson(ArrayNode arrayNode) throws LeanException { 97 | List result = new ArrayList(arrayNode.size()); 98 | for (JsonNode node : arrayNode) { 99 | result.add(propertyFromJson(node)); 100 | } 101 | return result; 102 | } 103 | 104 | private static Object typedObjectFromJson(ObjectNode node) throws LeanException { 105 | // must have 'type' field 106 | String type = node.get("type").getTextValue(); 107 | if (type == null) throw new LeanException(LeanException.Error.ValueToJSON, " Missing 'type' field."); 108 | 109 | if ("date".equals(type)) { 110 | return new Date(getLongFromValueNode("value", node)); 111 | } else if ("text".equals(type)) { 112 | return new Text(getStringFromValueNode("value", node)); 113 | } else if ("geopt".equals(type)) { 114 | throw new IllegalArgumentException("Value nodes of type 'geopt' are not yet implemented."); 115 | } else if ("geohash".equals(type)) { 116 | throw new IllegalArgumentException("Value nodes of type 'geohash' are not yet implemented."); 117 | } else if ("blob".equals(type)) { 118 | throw new IllegalArgumentException("Value nodes of type 'blob' are not yet implemented."); 119 | } else if ("shortblob".equals(type)) { 120 | throw new IllegalArgumentException("Value nodes of type 'shortblob' are not yet implemented."); 121 | } else if ("reference".equals(type)) { 122 | throw new IllegalArgumentException("Value nodes of type 'reference' are not yet implemented."); 123 | } else { 124 | //unknown node type 125 | throw new LeanException(LeanException.Error.ValueToJSON, " Unknown type '" + type + "'."); 126 | } 127 | } 128 | 129 | private static long getLongFromValueNode(String fieldName, JsonNode node) throws LeanException { 130 | return getNodeValue(fieldName, node).getLongValue(); 131 | } 132 | 133 | private static String getStringFromValueNode(String fieldName, JsonNode node) throws LeanException { 134 | return getNodeValue(fieldName, node).getTextValue(); 135 | } 136 | 137 | private static JsonNode getNodeValue(String fieldName, JsonNode node) throws LeanException { 138 | // must have 'fieldName' field 139 | JsonNode valueNode = node.get(fieldName); 140 | if (valueNode == null) 141 | throw new LeanException(LeanException.Error.ValueToJSON, " Missing '" + fieldName + "' field."); 142 | 143 | return valueNode; 144 | } 145 | 146 | public static void addTypedNode(ObjectNode node, String key, Object value) throws LeanException { 147 | if (value instanceof List) { 148 | List list = (List) value; 149 | ArrayNode arrayNode = JsonUtils.getObjectMapper().createArrayNode(); 150 | for (Object listItem : list) { 151 | addTypedValueToArray(arrayNode, listItem); 152 | } 153 | node.put(key, arrayNode); 154 | } else { 155 | addTypedValue(node, key, value); 156 | } 157 | } 158 | 159 | private static void addTypedValueToArray(ArrayNode node, Object value) { 160 | if (value instanceof Long) { 161 | node.add((Long) value); 162 | } else if (value instanceof Double) { 163 | node.add((Double) value); 164 | } else if (value instanceof String) { 165 | node.add((String) value); 166 | } else if (value instanceof Boolean) { 167 | node.add((Boolean) value); 168 | } else if (value instanceof Date) { 169 | node.add(getDateNode((Date) value)); 170 | } else if (value instanceof Text) { 171 | node.add(getTextNode((Text) value)); 172 | } 173 | } 174 | 175 | private static void addTypedValue(ObjectNode node, String key, Object value) { 176 | if (value instanceof Long) { 177 | node.put(key, (Long) value); 178 | } else if (value instanceof Double) { 179 | node.put(key, (Double) value); 180 | } else if (value instanceof String) { 181 | node.put(key, (String) value); 182 | } else if (value instanceof Boolean) { 183 | node.put(key, (Boolean) value); 184 | } else if (value instanceof Date) { 185 | node.put(key, getDateNode((Date) value)); 186 | } else if (value instanceof Text) { 187 | node.put(key, getTextNode((Text) value)); 188 | } 189 | } 190 | 191 | private static ObjectNode getDateNode(Date date) { 192 | ObjectNode dateNode = JsonUtils.getObjectMapper().createObjectNode(); 193 | dateNode.put("type", "date"); 194 | dateNode.put("value", date.getTime()); 195 | return dateNode; 196 | } 197 | 198 | private static ObjectNode getTextNode(Text text) { 199 | ObjectNode dateNode = JsonUtils.getObjectMapper().createObjectNode(); 200 | dateNode.put("type", "text"); 201 | dateNode.put("value", text.getValue()); 202 | return dateNode; 203 | } 204 | 205 | 206 | } 207 | --------------------------------------------------------------------------------