├── client ├── ios │ ├── www │ ├── html5expense │ │ ├── en.lproj │ │ │ └── InfoPlist.strings │ │ ├── Plugins │ │ │ └── README │ │ ├── Resources │ │ │ ├── icons │ │ │ │ ├── icon.png │ │ │ │ ├── icon-72.png │ │ │ │ └── icon@2x.png │ │ │ ├── splash │ │ │ │ ├── Default.png │ │ │ │ └── Default@2x.png │ │ │ ├── Capture.bundle │ │ │ │ ├── controls_bg.png │ │ │ │ ├── microphone.png │ │ │ │ ├── recording_bg.png │ │ │ │ ├── stop_button.png │ │ │ │ ├── controls_bg@2x.png │ │ │ │ ├── microphone@2x.png │ │ │ │ ├── record_button.png │ │ │ │ ├── stop_button@2x.png │ │ │ │ ├── controls_bg~ipad.png │ │ │ │ ├── microphone~ipad.png │ │ │ │ ├── record_button@2x.png │ │ │ │ ├── recording_bg@2x.png │ │ │ │ ├── recording_bg~ipad.png │ │ │ │ ├── stop_button~ipad.png │ │ │ │ └── record_button~ipad.png │ │ │ ├── en.lproj │ │ │ │ └── Localizable.strings │ │ │ └── es.lproj │ │ │ │ └── Localizable.strings │ │ ├── html5expense-Prefix.pch │ │ ├── main.m │ │ ├── Classes │ │ │ ├── AppDelegate.h │ │ │ └── AppDelegate.m │ │ ├── html5expense-Info.plist │ │ └── PhoneGap.plist │ └── .gitignore ├── android │ ├── assets │ │ └── www │ ├── .gitignore │ ├── libs │ │ └── phonegap-1.3.0.jar │ ├── res │ │ ├── drawable-hdpi │ │ │ └── icon.png │ │ ├── drawable-ldpi │ │ │ └── icon.png │ │ ├── drawable-mdpi │ │ │ └── icon.png │ │ ├── values │ │ │ └── strings.xml │ │ ├── layout │ │ │ └── main.xml │ │ └── xml │ │ │ ├── phonegap.xml │ │ │ └── plugins.xml │ ├── project.properties │ ├── src │ │ └── org │ │ │ └── springsource │ │ │ └── html5expense │ │ │ └── Html5expense.java │ ├── proguard.cfg │ └── AndroidManifest.xml ├── shared │ └── www │ │ ├── images │ │ ├── ajax-loader.png │ │ ├── icons-18-black.png │ │ ├── icons-18-white.png │ │ ├── icons-36-black.png │ │ └── icons-36-white.png │ │ ├── jquery.currency.1.0.0.min.js │ │ ├── device-detection.js │ │ ├── jquery.json-2.3.min.js │ │ └── jquery.json-2.3.js └── README.md ├── server ├── oauth │ ├── .gitignore │ ├── src │ │ └── main │ │ │ ├── webapp │ │ │ └── WEB-INF │ │ │ │ ├── layouts │ │ │ │ ├── standard │ │ │ │ │ ├── header.jsp │ │ │ │ │ ├── footer.jsp │ │ │ │ │ └── page.jsp │ │ │ │ └── tiles.xml │ │ │ │ ├── tags │ │ │ │ └── develop │ │ │ │ │ └── apps │ │ │ │ │ └── summary.tag │ │ │ │ ├── views │ │ │ │ └── develop │ │ │ │ │ └── apps │ │ │ │ │ ├── view.jsp │ │ │ │ │ ├── appFormFragment.jsp │ │ │ │ │ ├── list.jsp │ │ │ │ │ ├── edit.jsp │ │ │ │ │ ├── new.jsp │ │ │ │ │ └── tiles.xml │ │ │ │ └── web.xml │ │ │ └── java │ │ │ └── com │ │ │ └── springsource │ │ │ └── oauthservice │ │ │ ├── config │ │ │ ├── demo-data.sql │ │ │ ├── oauth.xml │ │ │ ├── ComponentConfig.java │ │ │ ├── schema.sql │ │ │ ├── DataConfig.java │ │ │ ├── data.xml │ │ │ ├── WebConfig.java │ │ │ ├── security.xml │ │ │ └── SecurityConfig.java │ │ │ ├── develop │ │ │ ├── AppRepository.java │ │ │ ├── AppSummary.java │ │ │ ├── App.java │ │ │ ├── AppForm.java │ │ │ ├── AppController.java │ │ │ └── JdbcAppRepository.java │ │ │ ├── utils │ │ │ └── SlugUtils.java │ │ │ ├── Bootstrap.java │ │ │ └── IdentityController.java │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── README.md │ ├── build.gradle │ └── gradlew.bat └── api │ ├── src │ ├── main │ │ ├── resources │ │ │ ├── errors.properties │ │ │ ├── messages.properties │ │ │ ├── messages_en_US.properties │ │ │ ├── config.properties │ │ │ ├── log4j.properties │ │ │ ├── oauth.xml │ │ │ ├── setup │ │ │ │ ├── schema.sql │ │ │ │ └── demo-data.sql │ │ │ ├── ec-loader.xml │ │ │ └── security.xml │ │ ├── webapp │ │ │ ├── assets │ │ │ │ ├── css │ │ │ │ │ └── styles.css │ │ │ │ ├── images │ │ │ │ │ ├── checkbox.ai │ │ │ │ │ ├── trash-icon.ai │ │ │ │ │ ├── ajax-loader.gif │ │ │ │ │ ├── receipts icon.ai │ │ │ │ │ ├── trash_sprite.ai │ │ │ │ │ ├── trash_sprite.gif │ │ │ │ │ ├── attach-receipt.png │ │ │ │ │ ├── checkmark_sprite.ai │ │ │ │ │ ├── checkmark_sprite.gif │ │ │ │ │ └── attachments │ │ │ │ │ │ ├── receipts.ai │ │ │ │ │ │ ├── receipts.gif │ │ │ │ │ │ ├── receipts-provided.gif │ │ │ │ │ │ └── receipts-required.gif │ │ │ │ └── js │ │ │ │ │ └── upload.js │ │ │ └── WEB-INF │ │ │ │ └── web.xml │ │ └── java │ │ │ └── com │ │ │ └── springsource │ │ │ └── html5expense │ │ │ ├── config │ │ │ ├── DataSourceConfig.java │ │ │ ├── SecurityConfig.java │ │ │ ├── LocalDataSourceConfig.java │ │ │ ├── CloudDataSourceConfig.java │ │ │ ├── ComponentConfig.java │ │ │ ├── WebConfig.java │ │ │ └── BatchConfig.java │ │ │ ├── services │ │ │ ├── Flag.java │ │ │ └── utilities │ │ │ │ └── MongoDbGridFsUtilities.java │ │ │ ├── State.java │ │ │ ├── controllers │ │ │ ├── ExpenseReportingViewController.java │ │ │ └── ExpenseReportingApiController.java │ │ │ ├── integrations │ │ │ ├── EligibleChargeProcessorHeaders.java │ │ │ └── EligibleChargeProcessor.java │ │ │ ├── web │ │ │ └── CloudAwareApplicationContextInitializer.java │ │ │ ├── EligibleCharge.java │ │ │ ├── Expense.java │ │ │ ├── ExpenseReportingService.java │ │ │ ├── security │ │ │ ├── EndpointTokenServices.java │ │ │ └── OAuth2AuthenticationMixin.java │ │ │ └── ExpenseReport.java │ └── test │ │ └── java │ │ └── com │ │ └── springsource │ │ └── html5expense │ │ └── services │ │ └── TestJpaExpenseReportingService.java │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── manifest.yml │ ├── setup.txt │ ├── todo.txt │ ├── gradlew.bat │ ├── build.gradle │ ├── gradlew │ └── pom.xml ├── .gitignore ├── README.md └── NOTICE /client/ios/www: -------------------------------------------------------------------------------- 1 | ../shared/www/ -------------------------------------------------------------------------------- /server/oauth/.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml -------------------------------------------------------------------------------- /client/android/assets/www: -------------------------------------------------------------------------------- 1 | ../../shared/www/ -------------------------------------------------------------------------------- /server/api/src/main/resources/errors.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/api/src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/layouts/standard/header.jsp: -------------------------------------------------------------------------------- 1 | <%@ page session="false" %> 2 | -------------------------------------------------------------------------------- /client/android/.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | build 5 | target 6 | bin 7 | gen -------------------------------------------------------------------------------- /client/ios/html5expense/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /client/ios/html5expense/Plugins/README: -------------------------------------------------------------------------------- 1 | Put the .h and .m files of your plugin here. The .js files of your plugin belong in the www folder. -------------------------------------------------------------------------------- /client/android/libs/phonegap-1.3.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/android/libs/phonegap-1.3.0.jar -------------------------------------------------------------------------------- /client/shared/www/images/ajax-loader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/shared/www/images/ajax-loader.png -------------------------------------------------------------------------------- /client/android/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/android/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /client/android/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/android/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /client/android/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/android/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /client/shared/www/images/icons-18-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/shared/www/images/icons-18-black.png -------------------------------------------------------------------------------- /client/shared/www/images/icons-18-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/shared/www/images/icons-18-white.png -------------------------------------------------------------------------------- /client/shared/www/images/icons-36-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/shared/www/images/icons-36-black.png -------------------------------------------------------------------------------- /client/shared/www/images/icons-36-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/shared/www/images/icons-36-white.png -------------------------------------------------------------------------------- /server/api/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /server/oauth/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/oauth/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/icons/icon.png -------------------------------------------------------------------------------- /client/android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Html5expense 4 | 5 | -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/icons/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/icons/icon-72.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/icons/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/icons/icon@2x.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/splash/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/splash/Default.png -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/checkbox.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/checkbox.ai -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/splash/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/splash/Default@2x.png -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/trash-icon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/trash-icon.ai -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/ajax-loader.gif -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/receipts icon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/receipts icon.ai -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/trash_sprite.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/trash_sprite.ai -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/trash_sprite.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/trash_sprite.gif -------------------------------------------------------------------------------- /client/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1 2 | *.mode1v3 3 | *.mode2v3 4 | *.perspective 5 | *.perspectivev3 6 | *.pbxuser 7 | *.xcworkspace 8 | xcuserdata 9 | *~.nib 10 | *.swp 11 | www/ -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/attach-receipt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/attach-receipt.png -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/checkmark_sprite.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/checkmark_sprite.ai -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/checkmark_sprite.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/checkmark_sprite.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build 3 | .gradle 4 | .classpath 5 | .springBeans 6 | .project 7 | .settings 8 | bin 9 | .DS_Store 10 | Thumbs.db 11 | *.iml 12 | *.iws 13 | *.ipr 14 | target 15 | -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/controls_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/controls_bg.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/microphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/microphone.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/recording_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/recording_bg.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/stop_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/stop_button.png -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/attachments/receipts.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/attachments/receipts.ai -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/attachments/receipts.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/attachments/receipts.gif -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/controls_bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/controls_bg@2x.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/microphone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/microphone@2x.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/record_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/record_button.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/stop_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/stop_button@2x.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/controls_bg~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/controls_bg~ipad.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/microphone~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/microphone~ipad.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/record_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/record_button@2x.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/recording_bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/recording_bg@2x.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/recording_bg~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/recording_bg~ipad.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/stop_button~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/stop_button~ipad.png -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/Capture.bundle/record_button~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/client/ios/html5expense/Resources/Capture.bundle/record_button~ipad.png -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/attachments/receipts-provided.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/attachments/receipts-provided.gif -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/images/attachments/receipts-required.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/html5expense/master/server/api/src/main/webapp/assets/images/attachments/receipts-required.gif -------------------------------------------------------------------------------- /client/ios/html5expense/html5expense-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'html5expense' target in the 'html5expense' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /client/android/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /server/api/src/main/resources/messages_en_US.properties: -------------------------------------------------------------------------------- 1 | application.title = HTML 5 Expenses 2 | label.dragFilesHere = You can upload receipts for each expense by dragging a file on to it 3 | label.createNewReport = create a new report 4 | label.or = or 5 | label.selectExistingReport = select an existing report -------------------------------------------------------------------------------- /server/api/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jul 07 12:11:05 EDT 2011 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://repo.gradle.org/gradle/distributions/gradle-1.0-milestone-3-bin.zip 7 | -------------------------------------------------------------------------------- /server/oauth/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 05 12:59:02 CDT 2011 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://repo.gradle.org/gradle/distributions/gradle-1.0-milestone-3-bin.zip 7 | -------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/layouts/standard/footer.jsp: -------------------------------------------------------------------------------- 1 | <%@ page session="false" %> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 | 4 | SpringSource 5 |

Version 1.0.0 (Beta) - © Copyright 2011 SpringSource, a division of VMware

-------------------------------------------------------------------------------- /client/android/res/xml/phonegap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /server/api/src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | debug = true 2 | dataSource.user expenses 3 | dataSource.db expenses 4 | dataSource.password expenses 5 | dataSource.host 127.0.0.1 6 | dataSource.driverClassName org.postgresql.Driver 7 | dataSource.port 5432 8 | dataSource.url jdbc:postgresql://${dataSource.host}:${dataSource.password}/${dataSource.db} 9 | -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/config/demo-data.sql: -------------------------------------------------------------------------------- 1 | insert into App (name, slug, description, organization, website, apiKey, secret, redirectUrl, grantTypes) 2 | values ('Demo', 'demo', 'Demo', 'SpringSource', 'https://www.springsource.org', '09e749d8309f4044', '189309492722aa5a', '', 'password,authorization_code,refresh_token'); 3 | 4 | insert into AppDeveloper (app, developer) values (1, 1); -------------------------------------------------------------------------------- /client/android/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-15 12 | -------------------------------------------------------------------------------- /client/android/src/org/springsource/html5expense/Html5expense.java: -------------------------------------------------------------------------------- 1 | package org.springsource.html5expense; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.phonegap.DroidGap; 6 | 7 | public class Html5expense extends DroidGap { 8 | 9 | @Override 10 | public void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | super.loadUrl("file:///android_asset/www/index.html"); 13 | } 14 | } -------------------------------------------------------------------------------- /client/ios/html5expense/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // html5expense 4 | // 5 | // Created by Roy Clarkson on 8/30/11. 6 | // Copyright VMware, Inc. 2011. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) { 12 | 13 | NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 14 | int retVal = UIApplicationMain(argc, argv, nil, @"AppDelegate"); 15 | [pool release]; 16 | return retVal; 17 | } 18 | -------------------------------------------------------------------------------- /server/api/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | target: 4 | name: html5expenses 5 | url: ${name}.${target-base} 6 | framework: 7 | name: spring 8 | info: 9 | mem: 512M 10 | description: Java SpringSource Spring Application 11 | exec: 12 | mem: 512M 13 | instances: 1 14 | services: 15 | expenses-mongo: 16 | type: :mongodb 17 | expenses-postgresql: 18 | type: :postgresql 19 | -------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/tags/develop/apps/summary.tag: -------------------------------------------------------------------------------- 1 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 2 | <%@ attribute name="value" required="true" rtexprvalue="true" type="com.springsource.oauthservice.develop.AppSummary" %> 3 |
4 | App Icon 5 |

">

6 |

7 |
8 | -------------------------------------------------------------------------------- /server/api/setup.txt: -------------------------------------------------------------------------------- 1 | Here's the sequence you need to setup the database on PostgreSQL, locally. 2 | 3 | /Library/PostgreSQL/9.1/scripts/runpsql.sh; exit 4 | Server [localhost]: 5 | Database [postgres]: 6 | Port [5432]: 7 | Username [postgres]: 8 | Password for user postgres: 9 | psql (9.1.2) 10 | Type "help" for help. 11 | postgres=# create user expenses with password 'expenses' ; create database expenses ; 12 | CREATE ROLE 13 | CREATE DATABASE 14 | postgres=# grant all privileges on database expenses to expenses ; 15 | GRANT 16 | postgres=# 17 | 18 | -------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/layouts/tiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /server/api/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # For JBoss: Avoid to setup Log4J outside $JBOSS_HOME/server/default/deploy/log4j.xml! 2 | # For all other servers: Comment out the Log4J listener in web.xml.old to activate Log4J. 3 | log4j.rootLogger=INFO, logfile 4 | 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n 8 | 9 | 10 | log4j.logger.org.springframework.web=DEBUG, stdout 11 | 12 | log4j.logger.com.springsource.html5expense=DEBUG, stdout 13 | -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | * PhoneGap is available under *either* the terms of the modified BSD license *or* the 3 | * MIT License (2008). See https://opensource.org/licenses/alphabetical for full text. 4 | * 5 | * Copyright (c) 2011, IBM Corporation 6 | */ 7 | 8 | 9 | // accessibility label for recording button 10 | "toggle audio recording" = "toggle audio recording"; 11 | // notification spoken by VoiceOver when timed recording finishes 12 | "timed recording complete" = "timed recording complete"; 13 | // accessibility hint for display of recorded elapsed time 14 | "recorded time in minutes and seconds" = "recorded time in minutes and seconds"; -------------------------------------------------------------------------------- /client/ios/html5expense/Resources/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | * PhoneGap is available under *either* the terms of the modified BSD license *or* the 3 | * MIT License (2008). See https://opensource.org/licenses/alphabetical for full text. 4 | * 5 | * Copyright (c) 2011, IBM Corporation 6 | */ 7 | 8 | // accessibility label for recording button 9 | "toggle audio recording" = "grabación de audio cambiar"; 10 | // notification spoken by VoiceOver when timed recording finishes 11 | "timed recording complete" = "tiempo de grabación completo"; 12 | // accessibility hint for display of recorded elapsed time 13 | "recorded time in minutes and seconds" = "tiempo registrado en minutos y segundos"; -------------------------------------------------------------------------------- /server/api/todo.txt: -------------------------------------------------------------------------------- 1 | This application will have: 2 | - support for receiving eligible charges from external submission types (e.g., a batch file, or through email) 3 | - support for a web UI (connecting through a secure API) 4 | - support for a mobile UI (connecting through a secure API) 5 | - support for business logic using standard transactional services. 6 | - support for pulling information out of an itinerary on tripit and using that to help flesh out line items. 7 | - add support for deploying finished generated texpense report to box 8 | - add support for picking and adding images from box.net to expense report 9 | - add support for picking and adding images from desktop using html5 and storing *to* box.net 10 | -------------------------------------------------------------------------------- /client/shared/www/jquery.currency.1.0.0.min.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2009 Michael Manning (actingthemaggot.com) Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.*/ 2 | (function(A){A.fn.extend({currency:function(B){var C={s:",",d:".",c:2};C=A.extend({},C,B);return this.each(function(){var D=(C.n||A(this).text());D=(typeof D==="number")?D:((/\./.test(D))?parseFloat(D):parseInt(D)),s=D<0?"-":"",i=parseInt(D=Math.abs(+D||0).toFixed(C.c))+"",j=(j=i.length)>3?j%3:0;A(this).text(s+(j?i.substr(0,j)+C.s:"")+i.substr(j).replace(/(\d{3})(?=\d)/g,"$1"+C.s)+(C.c?C.d+Math.abs(D-i).toFixed(C.c).slice(2):""));return this})}})})(jQuery);jQuery.currency=function(){var A=jQuery("").text(arguments[0]).currency(arguments[1]);return A.text()}; -------------------------------------------------------------------------------- /server/api/src/main/resources/oauth.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/views/develop/apps/view.jsp: -------------------------------------------------------------------------------- 1 | <%@ page session="false" %> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 | <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> 4 | <%@ taglib tagdir="/WEB-INF/tags/develop/apps" prefix="apps" %> 5 | 6 |

App Settings

7 | 8 | 9 | 10 |
11 |
API key
12 |
${app.apiKey}
13 |
Secret
14 |
${app.secret}
15 |
16 | 17 |
" method="post"> 18 | 19 |

20 |
21 | 22 | ">Edit details 23 | -------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/layouts/standard/page.jsp: -------------------------------------------------------------------------------- 1 | <%@ page session="false" %> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 | <%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %> 4 | 5 | 6 | 7 | 8 | <tiles:insertAttribute name="title" defaultValue="OAuth Server" /> 9 | 10 | 11 | 12 | 15 |
16 |
17 | 18 |
19 | 22 |
23 | 24 | -------------------------------------------------------------------------------- /client/ios/html5expense/Classes/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // html5expense 4 | // 5 | // Created by Roy Clarkson on 8/30/11. 6 | // Copyright VMware, Inc. 2011. All rights reserved. 7 | // 8 | 9 | #import 10 | #ifdef PHONEGAP_FRAMEWORK 11 | #import 12 | #else 13 | #import "PhoneGapDelegate.h" 14 | #endif 15 | 16 | @interface AppDelegate : PhoneGapDelegate { 17 | 18 | NSString* invokeString; 19 | } 20 | 21 | // invoke string is passed to your app on launch, this is only valid if you 22 | // edit html5expense.plist to add a protocol 23 | // a simple tutorial can be found here : 24 | // https://iphonedevelopertips.com/cocoa/launching-your-own-application-via-a-custom-url-scheme.html 25 | 26 | @property (copy) NSString* invokeString; 27 | 28 | @end 29 | 30 | -------------------------------------------------------------------------------- /server/api/src/main/resources/setup/schema.sql: -------------------------------------------------------------------------------- 1 | -- drop table if exists ELIGIBLE_CHARGE; 2 | -- drop table if exists EXPENSE; 3 | -- drop table if exists EXPENSE_REPORT; 4 | -- create table ELIGIBLE_CHARGE (id serial, amount decimal(19,2), category varchar(255), charge_date timestamp, merchant varchar(255), primary key (id)); 5 | -- create table EXPENSE (id serial, amount decimal(19,2), category varchar(255), chargeId bigint, expense_date timestamp, flag varchar(255), merchant varchar(255), receipt varchar(255), receiptExtension varchar(255), expenseReport_id bigint, primary key (id)); 6 | -- create table EXPENSE_REPORT (id serial, purpose varchar(255), receiptRequiredAmount decimal(19,2), state varchar(255), primary key (id)); 7 | -- alter table EXPENSE add constraint FKDCC054382876AD53 foreign key (expenseReport_id) references EXPENSE_REPORT (id) match full; -------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/views/develop/apps/appFormFragment.jsp: -------------------------------------------------------------------------------- 1 | <%@ page session="false" %> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 | <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> 4 |
5 | Name 6 | 7 | Description 8 | 9 | Organization 10 | 11 | Website 12 | 13 | Callback URL 14 | 15 |
-------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/views/develop/apps/list.jsp: -------------------------------------------------------------------------------- 1 | <%@ page session="false" %> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 | <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> 4 | <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> 5 | <%@ taglib tagdir="/WEB-INF/tags/develop/apps" prefix="apps" %> 6 | 7 |

Apps

8 | 9 | 10 |

You have not registered any apps.

11 |
12 | 13 |

You have registered the following apps:

14 |
15 | 16 | 17 |
    18 |
  • 19 |
20 |
21 | 22 | ">Register App -------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/views/develop/apps/edit.jsp: -------------------------------------------------------------------------------- 1 | <%@ page session="false" %> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 | <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> 4 | <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> 5 | 6 | 7 | 8 |
9 |

Edit App

10 | 11 | 12 |
Unable to save edits. Please fix the errors below and try again.
13 |
14 |
15 |
16 | 17 | 18 | 19 | 20 |

21 |
-------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/views/develop/apps/new.jsp: -------------------------------------------------------------------------------- 1 | <%@ page session="false" %> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 | <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> 4 | <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> 5 | 6 | 7 | 8 |
9 |

Register App

10 | 11 | 12 |
Unable to register. Please fix the errors below and resubmit.
13 |
14 |
15 |

Connect one of your apps by completing the following form.

16 |
17 | 18 | 19 | 20 |

21 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTML5 EXPENSE is no longer actively maintained by VMware, Inc. # 2 | 3 | ## Overview ## 4 | 5 | The HTML5 Expense project is an expense reporting reference app demonstrating HTML5 and cross-platform mobile development. The project includes the following components. 6 | 7 | ### Client ### 8 | 9 | The two native clients share the same web source built using PhoneGap, jQuery, and jQuery Mobile. 10 | 11 | * ANDROID - native Android PhoneGap project files 12 | * iOS - native iOS PhoneGap project files 13 | * SHARED - shared web source and JavaScript 14 | 15 | ### Server ### 16 | 17 | * API - Spring MVC application to which the client makes RESTful requests. 18 | * OAUTH - Spring application built with Spring Security Oauth that manages access to the API. 19 | 20 | ## More Information ## 21 | 22 | View the README files within the client and server directories for more information on each of those components. 23 | -------------------------------------------------------------------------------- /server/oauth/README.md: -------------------------------------------------------------------------------- 1 | # HTML5 EXPENSE - OAUTH # 2 | 3 | ## Overview ## 4 | 5 | The OAuth app is used to authenticate the client. It is deployed separately from the API app. 6 | 7 | ## Project Structure ## 8 | 9 | the web application 10 | 11 | * src - all source code 12 | * main/java - backend java source code 13 | * main/webapp/assets - static sources including css, javascript, and image files 14 | * main/webapp/WEB-INF/views - template sources that generic dynamic views 15 | * test/ - test code 16 | 17 | ## Build the App ## 18 | 19 | To build a deployable web app archive (.war): 20 | 21 | ./gradlew build 22 | 23 | See build/libs/oauth.war 24 | 25 | ## Deploy to Cloud Foundry ## 26 | 27 | vmc push --path build/libs 28 | 29 | The OAuth app requires a PostgreSQL service to be installed in Cloud Foundry. When pushing this to a new application in Cloud Foundry, add a PostgreSQL service and name it "tokendb". -------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/views/develop/apps/tiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /server/api/src/main/resources/setup/demo-data.sql: -------------------------------------------------------------------------------- 1 | insert into ELIGIBLE_CHARGE (id,amount, category, charge_date, merchant) values (nextval('hibernate_sequence'), 242.24, 'travel', now() , 'southwest'); 2 | insert into ELIGIBLE_CHARGE (id,amount, category, charge_date, merchant) values (nextval('hibernate_sequence'), 23.5, 'food', now() , 'starbucks'); 3 | insert into ELIGIBLE_CHARGE (id,amount, category, charge_date, merchant) values (nextval('hibernate_sequence'), 250.0, 'travel', now() , 'united airlines'); 4 | insert into ELIGIBLE_CHARGE (id,amount, category, charge_date, merchant) values (nextval('hibernate_sequence'), 4500.0, 'computer supplies', now() , 'apple computer'); 5 | insert into ELIGIBLE_CHARGE (id,amount, category, charge_date, merchant) values (nextval('hibernate_sequence'), 150.0, 'travel', now() , 'american airlines'); 6 | insert into ELIGIBLE_CHARGE (id,amount, category, charge_date, merchant) values (nextval('hibernate_sequence'), 15.5, 'personal', now() , 'ghostbusters'); -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/config/oauth.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | == NOTICE file corresponding to section 4 d of the Apache License, == 3 | == Version 2.0, in this case for the Spring Android distribution. == 4 | ======================================================================== 5 | 6 | This product includes software developed by 7 | the Apache Software Foundation (https://www.apache.org). 8 | 9 | The end-user documentation included with a redistribution, if any, 10 | must include the following acknowledgement: 11 | 12 | "This product includes software developed by SpringSource 13 | (https://www.springsource.com)." 14 | 15 | Alternatively, this acknowledgement may appear in the software itself, 16 | if and wherever such third-party acknowledgements normally appear. 17 | 18 | The names "Spring", "SpringSource", and "html5expense" must 19 | not be used to endorse or promote products derived from this software 20 | without prior written permission. For written permission, please contact 21 | enquiries@springsource.com. 22 | -------------------------------------------------------------------------------- /client/android/res/xml/plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/config/DataSourceConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.config; 17 | 18 | import com.mongodb.Mongo; 19 | import org.springframework.data.mongodb.MongoDbFactory; 20 | import org.springframework.data.mongodb.core.MongoTemplate; 21 | 22 | import javax.sql.DataSource; 23 | 24 | /** 25 | * 26 | */ 27 | public interface DataSourceConfig { 28 | DataSource dataSource() throws Exception ; 29 | 30 | MongoTemplate mongoTemplate () throws Exception; 31 | } 32 | -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/config/ComponentConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice.config; 17 | 18 | import org.springframework.context.annotation.ComponentScan; 19 | import org.springframework.context.annotation.ComponentScan.Filter; 20 | import org.springframework.context.annotation.Configuration; 21 | 22 | @Configuration 23 | @ComponentScan(basePackages="com.springsource.oauthservice", excludeFilters={ @Filter(Configuration.class)} ) 24 | public class ComponentConfig { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/config/schema.sql: -------------------------------------------------------------------------------- 1 | create table App (id serial, 2 | name varchar not null unique, 3 | slug varchar not null unique, 4 | description varchar not null, 5 | organization varchar, 6 | website varchar, 7 | apiKey varchar not null unique, 8 | secret varchar not null unique, 9 | redirectUrl varchar, 10 | resourceIds varchar, 11 | scope varchar, 12 | grantTypes varchar, 13 | authorities varchar, 14 | primary key (id)); 15 | 16 | create table AppDeveloper (app bigint, 17 | developer varchar, 18 | primary key (app, developer), 19 | foreign key (app) references App(id) on delete cascade); 20 | 21 | -- Changing the names of the oauth_access_token and oauth_refresh_token tables would involve overriding 9 different queries in JdbcOAuth2ProviderTokenServices. 22 | -- Will leave them as-is for now. 23 | create table oauth_access_token ( 24 | token_id VARCHAR(256), 25 | token BYTEA, 26 | authentication BYTEA, 27 | refresh_token VARCHAR(256) 28 | ); 29 | 30 | create table oauth_refresh_token ( 31 | token_id VARCHAR(256), 32 | token BYTEA, 33 | authentication BYTEA 34 | ); 35 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/services/Flag.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.services; 17 | 18 | public class Flag { 19 | 20 | private final Integer expenseId; 21 | 22 | private final String value; 23 | 24 | public Flag(Integer expenseId, String value) { 25 | this.expenseId = expenseId; 26 | this.value = value; 27 | } 28 | 29 | public Integer getExpenseId() { 30 | return expenseId; 31 | } 32 | 33 | public String getValue() { 34 | return value; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/develop/AppRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice.develop; 17 | 18 | import java.util.List; 19 | 20 | public interface AppRepository { 21 | 22 | List findAppSummaries(String developerId); 23 | 24 | App findAppBySlug(String developerId, String slug); 25 | 26 | String createApp(String developerId, AppForm form); 27 | 28 | AppForm getNewAppForm(); 29 | 30 | AppForm getAppForm(String developerId, String slug); 31 | 32 | String updateApp(String developerId, String slug, AppForm form); 33 | 34 | void deleteApp(String developerId, String slug); 35 | } 36 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/State.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense; 17 | 18 | /** 19 | * Various expense report states. 20 | * 21 | * @author Keith Donald 22 | */ 23 | public enum State { 24 | 25 | /** 26 | * The expense report is new and has not yet been submitted. 27 | */ 28 | NEW, 29 | 30 | /** 31 | * The expense report has been submitted and is locked under review. 32 | */ 33 | IN_REVIEW, 34 | 35 | /** 36 | * The expense report was rejected and can be modified. 37 | */ 38 | REJECTED, 39 | 40 | /** 41 | * The expense report has been approved and is now closed. 42 | */ 43 | APPROVED; 44 | } -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/config/DataConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice.config; 17 | 18 | import javax.inject.Inject; 19 | import javax.sql.DataSource; 20 | 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.context.annotation.ImportResource; 24 | import org.springframework.jdbc.core.JdbcTemplate; 25 | 26 | @Configuration 27 | @ImportResource("classpath:com/springsource/oauthservice/config/data.xml") 28 | public class DataConfig { 29 | 30 | @Inject 31 | private DataSource dataSource; 32 | 33 | @Bean 34 | public JdbcTemplate jdbcTemplate() { 35 | return new JdbcTemplate(dataSource); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /client/android/proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers class * extends android.app.Activity { 30 | public void *(android.view.View); 31 | } 32 | 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | 38 | -keep class * implements android.os.Parcelable { 39 | public static final android.os.Parcelable$Creator *; 40 | } 41 | -------------------------------------------------------------------------------- /server/api/src/main/resources/ec-loader.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/controllers/ExpenseReportingViewController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.controllers; 17 | 18 | import com.springsource.html5expense.ExpenseReportingService; 19 | import org.springframework.stereotype.Controller; 20 | import org.springframework.ui.ModelMap; 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import org.springframework.web.bind.annotation.RequestMethod; 23 | 24 | import javax.inject.Inject; 25 | 26 | @Controller 27 | public class ExpenseReportingViewController { 28 | 29 | @Inject 30 | private ExpenseReportingService expenseReportingService; 31 | 32 | @RequestMapping(value = "/home", method = RequestMethod.GET) 33 | public String showExpenses(ModelMap map) throws Exception { 34 | return "receipts"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/api/src/main/resources/security.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/config/data.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | classpath:com/springsource/oauthservice/config/schema.sql 20 | classpath:com/springsource/oauthservice/config/demo-data.sql 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/integrations/EligibleChargeProcessorHeaders.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.integrations; 17 | 18 | /** 19 | * well known headers that need to be present when constructing 20 | * {@link com.springsource.html5expense.EligibleCharge} 21 | * contributions in routing code. 22 | * 23 | * @author Josh Long 24 | */ 25 | public class EligibleChargeProcessorHeaders { 26 | 27 | /** 28 | * Well known header for the date of the Eligible charge 29 | */ 30 | static public final String EC_DATE = "ec_date"; 31 | /** 32 | * well known header for the merchant 33 | */ 34 | static public final String EC_MERCHANT = "ec_merchant"; 35 | 36 | /** 37 | * well known header for the amount 38 | */ 39 | static public final String EC_AMOUNT = "ec_amount"; 40 | 41 | /** 42 | * well known header for the category 43 | */ 44 | static public final String EC_CATEGORY = "ec_category"; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /client/ios/html5expense/html5expense-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIconFiles 6 | 7 | icon.png 8 | icon@2x.png 9 | icon-72.png 10 | 11 | UISupportedInterfaceOrientations~ipad 12 | 13 | UIInterfaceOrientationPortrait 14 | UIInterfaceOrientationLandscapeLeft 15 | UIInterfaceOrientationPortraitUpsideDown 16 | UIInterfaceOrientationLandscapeRight 17 | 18 | UISupportedInterfaceOrientations 19 | 20 | UIInterfaceOrientationPortrait 21 | 22 | CFBundleDevelopmentRegion 23 | English 24 | CFBundleDisplayName 25 | ${PRODUCT_NAME} 26 | CFBundleExecutable 27 | ${EXECUTABLE_NAME} 28 | CFBundleIconFile 29 | icon.png 30 | CFBundleIdentifier 31 | org.springsource.html5expense 32 | CFBundleInfoDictionaryVersion 33 | 6.0 34 | CFBundleName 35 | ${PRODUCT_NAME} 36 | CFBundlePackageType 37 | APPL 38 | CFBundleSignature 39 | ???? 40 | CFBundleVersion 41 | 1.0 42 | LSRequiresIPhoneOS 43 | 44 | NSMainNibFile 45 | 46 | NSMainNibFile~ipad 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/utils/SlugUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice.utils; 17 | 18 | import java.text.Normalizer; 19 | import java.text.Normalizer.Form; 20 | import java.util.Locale; 21 | import java.util.regex.Pattern; 22 | 23 | /** 24 | * Utilities for generating slugs: a short, meaningful name for a resource that can be used in the resource's friendly URL path. 25 | * @author Keith Donald 26 | */ 27 | public class SlugUtils { 28 | 29 | private static final Pattern NONLATIN = Pattern.compile("[^\\w-]"); 30 | 31 | private static final Pattern WHITESPACE = Pattern.compile("[\\s]"); 32 | 33 | /** 34 | * Convert the String input to a slug. 35 | */ 36 | public static String toSlug(String input) { 37 | if (input == null) { 38 | throw new IllegalArgumentException("Input cannot be null"); 39 | } 40 | String nowhitespace = WHITESPACE.matcher(input).replaceAll("-"); 41 | String normalized = Normalizer.normalize(nowhitespace, Form.NFD); 42 | String slug = NONLATIN.matcher(normalized).replaceAll(""); 43 | return slug.toLowerCase(Locale.ENGLISH); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/develop/AppSummary.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice.develop; 17 | 18 | public class AppSummary { 19 | 20 | private final String name; 21 | 22 | private final String iconUrl; 23 | 24 | private final String description; 25 | 26 | private final String slug; 27 | 28 | public AppSummary(String name, String iconUrl, String description, String slug) { 29 | this.name = name; 30 | this.iconUrl = iconUrl; 31 | this.description = description; 32 | this.slug = slug; 33 | } 34 | 35 | /** 36 | * The name of the app. 37 | */ 38 | public String getName() { 39 | return name; 40 | } 41 | 42 | /** 43 | * A link to the application's icon, suitable for display as a thumbnail. 44 | */ 45 | public String getIconUrl() { 46 | return iconUrl; 47 | } 48 | 49 | /** 50 | * Short description of the app. 51 | */ 52 | public String getDescription() { 53 | return description; 54 | } 55 | 56 | /** 57 | * Short, unique, and meaningful key for the application. 58 | * Used in browser URLs that reference the app resource. 59 | */ 60 | public String getSlug() { 61 | return slug; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /client/ios/html5expense/PhoneGap.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TopActivityIndicator 6 | gray 7 | EnableLocation 8 | 9 | EnableViewportScale 10 | 11 | AutoHideSplashScreen 12 | 13 | ShowSplashScreenSpinner 14 | 15 | MediaPlaybackRequiresUserAction 16 | 17 | AllowInlineMediaPlayback 18 | 19 | OpenAllWhitelistURLsInWebView 20 | 21 | ExternalHosts 22 | 23 | 127.0.0.1 24 | localhost 25 | *.phonegap.com 26 | *.cloudfoundry.com 27 | 28 | Plugins 29 | 30 | com.phonegap.accelerometer 31 | PGAccelerometer 32 | com.phonegap.camera 33 | PGCamera 34 | com.phonegap.connection 35 | PGConnection 36 | com.phonegap.contacts 37 | PGContacts 38 | com.phonegap.debugconsole 39 | PGDebugConsole 40 | com.phonegap.file 41 | PGFile 42 | com.phonegap.filetransfer 43 | PGFileTransfer 44 | com.phonegap.geolocation 45 | PGLocation 46 | com.phonegap.notification 47 | PGNotification 48 | com.phonegap.media 49 | PGSound 50 | com.phonegap.mediacapture 51 | PGCapture 52 | com.phonegap.splashscreen 53 | PGSplashScreen 54 | 55 | 56 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/web/CloudAwareApplicationContextInitializer.java: -------------------------------------------------------------------------------- 1 | package com.springsource.html5expense.web; 2 | 3 | import org.cloudfoundry.runtime.env.CloudEnvironment; 4 | import org.springframework.context.ApplicationContextInitializer; 5 | import org.springframework.core.env.ConfigurableEnvironment; 6 | import org.springframework.web.context.ConfigurableWebApplicationContext; 7 | 8 | /** 9 | * 10 | * {@link ApplicationContextInitializer application context initializers} are Spring callback interfaces that let you 11 | * tailor the Spring application context before it's run. This is useful if you want to register custom classes to be run, 12 | * or conditionally set the active profile or anything else. 13 | * 14 | * Here, we're using this callback, which runs right before the {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet} machinery 15 | * instantiates the Spring {@link org.springframework.context.ApplicationContext application context}, to detect whether 16 | * the application's running inside of Cloud Foundry (by asking the {@link org.cloudfoundry.runtime.env.CloudEnvironment#isCloudFoundry() cloud foundry API}) and 17 | * set the active profile accordingly. 18 | * 19 | * 20 | * @author Josh Long 21 | */ 22 | public class CloudAwareApplicationContextInitializer implements ApplicationContextInitializer { 23 | @Override 24 | public void initialize(ConfigurableWebApplicationContext applicationContext) { 25 | CloudEnvironment cloudEnvironment = new CloudEnvironment(); 26 | ConfigurableEnvironment environment = applicationContext.getEnvironment(); 27 | String profile = cloudEnvironment.isCloudFoundry() ? "cloud": "local" ; 28 | environment.setActiveProfiles(profile); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /client/shared/www/device-detection.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var iphone = "iphone"; 18 | var ipod = "ipod"; 19 | var android = "android"; 20 | 21 | var uagent = navigator.userAgent.toLowerCase(); 22 | 23 | function DetectDevice() { 24 | console.log(uagent); 25 | if ((uagent.search(iphone) > -1) || (uagent.search(ipod) > -1)) { 26 | console.log('iOS Device'); 27 | document.write('\x3Cscript type="text/javascript" src="phonegap-ios-1.3.0.js">\x3C/script>'); 28 | } else if (uagent.search(android) > -1) { 29 | console.log('Android Device'); 30 | document.write('\x3Cscript type="text/javascript" src="phonegap-android-1.3.0.js">\x3C/script>'); 31 | } else { 32 | console.log('Browser'); 33 | function deviceready() { 34 | var e = document.createEvent('Events'); 35 | e.initEvent('deviceready'); 36 | document.dispatchEvent(e); 37 | } 38 | if (typeof PhoneGap != 'undefined') { 39 | navigator.camera = {}; 40 | navigator.camera.getPicture = function() { 41 | alert('sorry, camera not supported in the browser: yet!'); 42 | } 43 | window.PhoneGap = {}; 44 | deviceready(); 45 | } 46 | } 47 | } 48 | 49 | DetectDevice(); -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.config; 17 | 18 | import com.springsource.html5expense.security.EndpointTokenServices; 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.context.annotation.Configuration; 21 | import org.springframework.context.annotation.ImportResource; 22 | import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; 23 | import org.springframework.security.web.AuthenticationEntryPoint; 24 | import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; 25 | 26 | @Configuration 27 | @ImportResource("classpath:security.xml") 28 | public class SecurityConfig { 29 | 30 | @Bean 31 | public AuthenticationEntryPoint entryPoint() { 32 | return new Http403ForbiddenEntryPoint(); 33 | } 34 | 35 | // OAuth beans 36 | @Bean 37 | public ResourceServerTokenServices tokenServices() { 38 | // TODO: Pull the authentication endpoint URL from the environment 39 | // Or, if the oauth service becomes a "native" CF service, then this whole bean could be consumed as a CF service. 40 | return new EndpointTokenServices("https://haboauth.cloudfoundry.com/me/authentication"); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/config/LocalDataSourceConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.config; 17 | 18 | import com.mongodb.Mongo; 19 | import org.postgresql.Driver; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.springframework.context.annotation.Profile; 23 | import org.springframework.data.mongodb.core.MongoTemplate; 24 | import org.springframework.jdbc.datasource.SimpleDriverDataSource; 25 | 26 | import javax.sql.DataSource; 27 | 28 | @Configuration 29 | @Profile("local") 30 | public class LocalDataSourceConfig implements DataSourceConfig { 31 | 32 | @Bean 33 | @Override 34 | public DataSource dataSource() throws Exception { 35 | SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); 36 | dataSource.setUrl(String.format("jdbc:postgresql://%s:%s/%s", "127.0.0.1", 5432, "expenses")); 37 | dataSource.setDriverClass(Driver.class); 38 | dataSource.setUsername("expenses"); 39 | dataSource.setPassword("expenses"); 40 | return dataSource; 41 | } 42 | 43 | @Bean 44 | @Override 45 | public MongoTemplate mongoTemplate() throws Exception { 46 | return new MongoTemplate(new Mongo("127.0.0.1"), "expensesfs"); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/integrations/EligibleChargeProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.integrations; 17 | 18 | import com.springsource.html5expense.ExpenseReportingService; 19 | import org.springframework.integration.annotation.Header; 20 | import org.springframework.integration.annotation.ServiceActivator; 21 | 22 | import javax.inject.Inject; 23 | import java.math.BigDecimal; 24 | import java.util.Date; 25 | 26 | /** 27 | * This is the gateway for all incoming eligible charges. There are lots of ways 28 | * to submit eligible charges, but we'll assume that they all converge on this 29 | * endpoint thanks to Spring Integration's normalization and routing prowess. 30 | * 31 | * @author Josh Long 32 | */ 33 | public class EligibleChargeProcessor { 34 | 35 | @Inject 36 | private ExpenseReportingService expenseReportingService; 37 | 38 | @ServiceActivator 39 | public void processNewEligibleCharge(@Header(EligibleChargeProcessorHeaders.EC_DATE) Date date, 40 | @Header(EligibleChargeProcessorHeaders.EC_MERCHANT) String merchant, 41 | @Header(EligibleChargeProcessorHeaders.EC_CATEGORY) String category, 42 | @Header(EligibleChargeProcessorHeaders.EC_AMOUNT) BigDecimal amount) throws Exception { 43 | this.expenseReportingService.createEligibleCharge(date, merchant, category, amount); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice.config; 17 | 18 | import org.springframework.context.annotation.Bean; 19 | import org.springframework.context.annotation.Configuration; 20 | import org.springframework.web.servlet.ViewResolver; 21 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 22 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 23 | import org.springframework.web.servlet.view.UrlBasedViewResolver; 24 | import org.springframework.web.servlet.view.tiles2.TilesConfigurer; 25 | import org.springframework.web.servlet.view.tiles2.TilesView; 26 | 27 | import com.springsource.oauthservice.Bootstrap; 28 | 29 | @Configuration 30 | @EnableWebMvc 31 | public class WebConfig extends WebMvcConfigurerAdapter { 32 | 33 | @Bean(initMethod="go") 34 | public Bootstrap bootstrap() { 35 | return new Bootstrap(); 36 | } 37 | 38 | @Bean 39 | public ViewResolver viewResolver() { 40 | UrlBasedViewResolver viewResolver = new UrlBasedViewResolver(); 41 | viewResolver.setViewClass(TilesView.class); 42 | return viewResolver; 43 | } 44 | 45 | @Bean 46 | public TilesConfigurer tilesConfigurer() { 47 | TilesConfigurer configurer = new TilesConfigurer(); 48 | configurer.setDefinitions(new String[] { 49 | "/WEB-INF/layouts/tiles.xml", 50 | "/WEB-INF/views/**/tiles.xml" 51 | }); 52 | configurer.setCheckRefresh(true); 53 | return configurer; 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/EligibleCharge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense; 17 | 18 | import javax.persistence.Entity; 19 | import javax.persistence.GeneratedValue; 20 | import javax.persistence.Id; 21 | import javax.persistence.Table; 22 | import java.math.BigDecimal; 23 | import java.util.Date; 24 | 25 | /** 26 | * Simple entity to describe a charge that has yet to be reconciled. 27 | */ 28 | @Entity 29 | @Table(name = "ELIGIBLE_CHARGE") 30 | public class EligibleCharge { 31 | 32 | @GeneratedValue(strategy = javax.persistence.GenerationType.AUTO) 33 | @Id 34 | private Long id; 35 | 36 | public Long getId() { 37 | return this.id; 38 | } 39 | 40 | private String merchant, category; 41 | private Date charge_date; 42 | private BigDecimal amount; 43 | 44 | public EligibleCharge() { 45 | } 46 | 47 | public EligibleCharge(Date charge_date, String merchant, String category, BigDecimal bigDecimal) { 48 | this.merchant = merchant; 49 | this.category = category; 50 | this.charge_date = charge_date; 51 | this.amount = bigDecimal; 52 | } 53 | 54 | public Date getDate() { 55 | return charge_date; 56 | } 57 | 58 | public String getMerchant() { 59 | return merchant; 60 | } 61 | 62 | public String getCategory() { 63 | return category; 64 | } 65 | 66 | public BigDecimal getAmount() { 67 | return amount; 68 | } 69 | 70 | public Long getI() { 71 | return this.id; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /client/shared/www/jquery.json-2.3.min.js: -------------------------------------------------------------------------------- 1 | 2 | (function($){var escapeable=/["\\\x00-\x1f\x7f-\x9f]/g,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};$.toJSON=typeof JSON==='object'&&JSON.stringify?JSON.stringify:function(o){if(o===null){return'null';} 3 | var type=typeof o;if(type==='undefined'){return undefined;} 4 | if(type==='number'||type==='boolean'){return''+o;} 5 | if(type==='string'){return $.quoteString(o);} 6 | if(type==='object'){if(typeof o.toJSON==='function'){return $.toJSON(o.toJSON());} 7 | if(o.constructor===Date){var month=o.getUTCMonth()+1,day=o.getUTCDate(),year=o.getUTCFullYear(),hours=o.getUTCHours(),minutes=o.getUTCMinutes(),seconds=o.getUTCSeconds(),milli=o.getUTCMilliseconds();if(month<10){month='0'+month;} 8 | if(day<10){day='0'+day;} 9 | if(hours<10){hours='0'+hours;} 10 | if(minutes<10){minutes='0'+minutes;} 11 | if(seconds<10){seconds='0'+seconds;} 12 | if(milli<100){milli='0'+milli;} 13 | if(milli<10){milli='0'+milli;} 14 | return'"'+year+'-'+month+'-'+day+'T'+ 15 | hours+':'+minutes+':'+seconds+'.'+milli+'Z"';} 16 | if(o.constructor===Array){var ret=[];for(var i=0;i 2 | 5 | 6 | 7 | 8 | contextClass 9 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 10 | 11 | 12 | contextInitializerClasses 13 | com.springsource.html5expense.web.CloudAwareApplicationContextInitializer 14 | 15 | 16 | 17 | contextConfigLocation 18 | com.springsource.html5expense.config.WebConfig 19 | 20 | 21 | 22 | hiddenHttpMethodFilter 23 | org.springframework.web.filter.HiddenHttpMethodFilter 24 | 25 | 26 | hiddenHttpMethodFilter 27 | / 28 | appServlet 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.web.context.ContextLoaderListener 35 | 36 | 37 | 38 | 39 | appServlet 40 | org.springframework.web.servlet.DispatcherServlet 41 | 42 | contextConfigLocation 43 | 44 | 45 | 1 46 | 47 | 48 | 49 | appServlet 50 | / 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/config/security.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/develop/AppForm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice.develop; 17 | 18 | import org.hibernate.validator.constraints.NotEmpty; 19 | 20 | /** 21 | * Model backing the "Register New App" and "Update Existing App" forms. 22 | * @author Craig Walls 23 | */ 24 | public class AppForm { 25 | 26 | @NotEmpty 27 | private String name; 28 | 29 | @NotEmpty 30 | private String description; 31 | 32 | private String organization; 33 | 34 | private String website; 35 | 36 | private String callbackUrl; 37 | 38 | /** 39 | * The name of the app. 40 | */ 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | public void setName(String name) { 46 | this.name = name; 47 | } 48 | 49 | /** 50 | * A short description of the app. 51 | */ 52 | public String getDescription() { 53 | return description; 54 | } 55 | 56 | public void setDescription(String description) { 57 | this.description = description; 58 | } 59 | 60 | /** 61 | * The organization that publishes the app. 62 | */ 63 | public String getOrganization() { 64 | return organization; 65 | } 66 | 67 | public void setOrganization(String organization) { 68 | this.organization = organization; 69 | } 70 | 71 | /** 72 | * The website you can visit to get more information on the app. 73 | */ 74 | public String getWebsite() { 75 | return website; 76 | } 77 | 78 | public void setWebsite(String website) { 79 | this.website = website; 80 | } 81 | 82 | /** 83 | * The URL members should be redirected back to after they connect to the app. 84 | */ 85 | public String getCallbackUrl() { 86 | return callbackUrl; 87 | } 88 | 89 | public void setCallbackUrl(String callbackUrl) { 90 | this.callbackUrl = callbackUrl; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /client/android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /server/api/src/test/java/com/springsource/html5expense/services/TestJpaExpenseReportingService.java: -------------------------------------------------------------------------------- 1 | package com.springsource.html5expense.services; 2 | 3 | import com.springsource.html5expense.EligibleCharge; 4 | import com.springsource.html5expense.Expense; 5 | import com.springsource.html5expense.ExpenseReportingService; 6 | import com.springsource.html5expense.config.ComponentConfig; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.test.context.ActiveProfiles; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | import org.springframework.test.context.support.AnnotationConfigContextLoader; 13 | import org.springframework.test.context.transaction.TransactionConfiguration; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | import javax.inject.Inject; 17 | import java.util.ArrayList; 18 | import java.util.Collection; 19 | import java.util.List; 20 | 21 | import static org.junit.Assert.*; 22 | 23 | /** 24 | * Tests the {@link JpaExpenseReportingService JPA expense reporting service}. 25 | * 26 | * @author Josh Long 27 | */ 28 | @RunWith(SpringJUnit4ClassRunner.class) 29 | @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = {ComponentConfig.class}) 30 | @TransactionConfiguration(defaultRollback = true) 31 | @Transactional 32 | @ActiveProfiles("local") 33 | public class TestJpaExpenseReportingService { 34 | 35 | @Inject 36 | private ExpenseReportingService service; 37 | 38 | private String purpose = "SFO face to face"; 39 | 40 | @Test 41 | public void testCreatingAnExpenseReport() throws Throwable { 42 | Long reportId = this.service.createReport(this.purpose); 43 | assertNotNull(reportId); 44 | assertTrue(reportId > 0); 45 | } 46 | 47 | @Test 48 | public void testCreatingAnExpenseReportExpenses() throws Throwable { 49 | Long reportId = service.createReport(this.purpose); 50 | Collection chargeCollection = this.service.getEligibleCharges(); 51 | List ids = new ArrayList(chargeCollection.size()); 52 | for (EligibleCharge eligibleCharge : chargeCollection) 53 | ids.add(eligibleCharge.getId()); 54 | Collection expenseCollection = service.createExpenses(reportId, ids); 55 | assertEquals(expenseCollection.size(), ids.size()); 56 | assertEquals(expenseCollection.size(), service.getExpensesForExpenseReport(reportId).size()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/Bootstrap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice; 17 | 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | import org.apache.commons.logging.Log; 22 | import org.apache.commons.logging.LogFactory; 23 | import org.cloudfoundry.org.codehaus.jackson.JsonFactory; 24 | import org.cloudfoundry.org.codehaus.jackson.JsonParser; 25 | import org.cloudfoundry.org.codehaus.jackson.map.ObjectMapper; 26 | 27 | /** 28 | * TEMPORARY: Bootstrap to log the value of VCAP_SERVICES so that I can create a DB URL to the bound database service. 29 | * 30 | * @author wallsc 31 | */ 32 | public class Bootstrap { 33 | private static final Log LOG = LogFactory.getLog(Bootstrap.class); 34 | 35 | public void go() throws Exception { 36 | String vcapServices = System.getenv("VCAP_SERVICES"); 37 | LOG.debug("VCAP_SERVICES: " + vcapServices); 38 | // jdbc:postgresql://172.30.48.126:5432/d6f69ba9c3c6349ac830af2973e31b779 39 | 40 | // pull values out and construct JDBC URL 41 | Map credentials = getCredentialsMap(vcapServices); 42 | String dbName = (String) credentials.get("name"); 43 | String host = (String) credentials.get("host"); 44 | Integer port = (Integer) credentials.get("port"); 45 | String username = (String) credentials.get("username"); 46 | String password = (String) credentials.get("password"); 47 | 48 | LOG.debug(" JDBC URL: jdbc:postgresql://" + host + ":" + port + "/" + dbName); 49 | LOG.debug(" DB USERNAME: " + username); 50 | LOG.debug(" DB PASSWORD: " + password); 51 | } 52 | 53 | public Map getCredentialsMap(String vcapServices) throws Exception { 54 | ObjectMapper mapper = new ObjectMapper(); 55 | JsonFactory jsonFactory = mapper.getJsonFactory(); 56 | JsonParser jsonParser = jsonFactory.createJsonParser(vcapServices); 57 | Map map = jsonParser.readValueAs(Map.class); 58 | List pgMap = (List) map.get("postgresql-9.0"); 59 | Map dbMap = (Map) pgMap.get(0); 60 | Map credentialsMap = (Map) dbMap.get("credentials"); 61 | return credentialsMap; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /server/api/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem ## 4 | @rem Gradle startup script for Windows ## 5 | @rem ## 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together. 12 | @rem set GRADLE_OPTS=%GRADLE_OPTS% -Xmx512m 13 | @rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512m 14 | 15 | set DIRNAME=%~dp0 16 | if "%DIRNAME%" == "" set DIRNAME=.\ 17 | 18 | @rem Find java.exe 19 | set JAVA_EXE=java.exe 20 | if not defined JAVA_HOME goto init 21 | 22 | set JAVA_HOME=%JAVA_HOME:"=% 23 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 24 | 25 | if exist "%JAVA_EXE%" goto init 26 | 27 | echo. 28 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 29 | echo. 30 | echo Please set the JAVA_HOME variable in your environment to match the 31 | echo location of your Java installation. 32 | echo. 33 | goto end 34 | 35 | :init 36 | @rem Get command-line arguments, handling Windowz variants 37 | 38 | if not "%OS%" == "Windows_NT" goto win9xME_args 39 | if "%eval[2+2]" == "4" goto 4NT_args 40 | 41 | :win9xME_args 42 | @rem Slurp the command line arguments. 43 | set CMD_LINE_ARGS= 44 | set _SKIP=2 45 | 46 | :win9xME_args_slurp 47 | if "x%~1" == "x" goto execute 48 | 49 | set CMD_LINE_ARGS=%* 50 | goto execute 51 | 52 | :4NT_args 53 | @rem Get arguments from the 4NT Shell from JP Software 54 | set CMD_LINE_ARGS=%$ 55 | 56 | :execute 57 | @rem Setup the command line 58 | 59 | set STARTER_MAIN_CLASS=org.gradle.wrapper.GradleWrapperMain 60 | set CLASSPATH=%DIRNAME%\gradle\wrapper\gradle-wrapper.jar 61 | set WRAPPER_PROPERTIES=%DIRNAME%\gradle\wrapper\gradle-wrapper.properties 62 | 63 | set GRADLE_OPTS=%JAVA_OPTS% %GRADLE_OPTS% -Dorg.gradle.wrapper.properties="%WRAPPER_PROPERTIES%" 64 | 65 | @rem Execute Gradle 66 | "%JAVA_EXE%" %GRADLE_OPTS% -classpath "%CLASSPATH%" %STARTER_MAIN_CLASS% %CMD_LINE_ARGS% 67 | 68 | :end 69 | @rem End local scope for the variables with windows NT shell 70 | if "%ERRORLEVEL%"=="0" goto mainEnd 71 | 72 | if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1 73 | 74 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 75 | rem the _cmd.exe /c_ return code! 76 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit "%ERRORLEVEL%" 77 | exit /b "%ERRORLEVEL%" 78 | 79 | :mainEnd 80 | if "%OS%"=="Windows_NT" endlocal 81 | 82 | :omega -------------------------------------------------------------------------------- /server/oauth/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem ## 4 | @rem Gradle startup script for Windows ## 5 | @rem ## 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together. 12 | @rem set GRADLE_OPTS=%GRADLE_OPTS% -Xmx512m 13 | @rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512m 14 | 15 | set DIRNAME=%~dp0 16 | if "%DIRNAME%" == "" set DIRNAME=.\ 17 | 18 | @rem Find java.exe 19 | set JAVA_EXE=java.exe 20 | if not defined JAVA_HOME goto init 21 | 22 | set JAVA_HOME=%JAVA_HOME:"=% 23 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 24 | 25 | if exist "%JAVA_EXE%" goto init 26 | 27 | echo. 28 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 29 | echo. 30 | echo Please set the JAVA_HOME variable in your environment to match the 31 | echo location of your Java installation. 32 | echo. 33 | goto end 34 | 35 | :init 36 | @rem Get command-line arguments, handling Windowz variants 37 | 38 | if not "%OS%" == "Windows_NT" goto win9xME_args 39 | if "%eval[2+2]" == "4" goto 4NT_args 40 | 41 | :win9xME_args 42 | @rem Slurp the command line arguments. 43 | set CMD_LINE_ARGS= 44 | set _SKIP=2 45 | 46 | :win9xME_args_slurp 47 | if "x%~1" == "x" goto execute 48 | 49 | set CMD_LINE_ARGS=%* 50 | goto execute 51 | 52 | :4NT_args 53 | @rem Get arguments from the 4NT Shell from JP Software 54 | set CMD_LINE_ARGS=%$ 55 | 56 | :execute 57 | @rem Setup the command line 58 | 59 | set STARTER_MAIN_CLASS=org.gradle.wrapper.GradleWrapperMain 60 | set CLASSPATH=%DIRNAME%\gradle\wrapper\gradle-wrapper.jar 61 | set WRAPPER_PROPERTIES=%DIRNAME%\gradle\wrapper\gradle-wrapper.properties 62 | 63 | set GRADLE_OPTS=%JAVA_OPTS% %GRADLE_OPTS% -Dorg.gradle.wrapper.properties="%WRAPPER_PROPERTIES%" 64 | 65 | @rem Execute Gradle 66 | "%JAVA_EXE%" %GRADLE_OPTS% -classpath "%CLASSPATH%" %STARTER_MAIN_CLASS% %CMD_LINE_ARGS% 67 | 68 | :end 69 | @rem End local scope for the variables with windows NT shell 70 | if "%ERRORLEVEL%"=="0" goto mainEnd 71 | 72 | if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1 73 | 74 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 75 | rem the _cmd.exe /c_ return code! 76 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit "%ERRORLEVEL%" 77 | exit /b "%ERRORLEVEL%" 78 | 79 | :mainEnd 80 | if "%OS%"=="Windows_NT" endlocal 81 | 82 | :omega -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice.config; 17 | 18 | import javax.inject.Inject; 19 | import javax.sql.DataSource; 20 | 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.context.annotation.ImportResource; 24 | import org.springframework.security.crypto.encrypt.Encryptors; 25 | import org.springframework.security.crypto.encrypt.TextEncryptor; 26 | import org.springframework.security.oauth2.provider.ClientDetailsService; 27 | import org.springframework.security.oauth2.provider.JdbcClientDetailsService; 28 | import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; 29 | import org.springframework.security.oauth2.provider.token.JdbcTokenStore; 30 | import org.springframework.security.oauth2.provider.token.RandomValueTokenServices; 31 | import org.springframework.security.oauth2.provider.token.TokenStore; 32 | 33 | @Configuration 34 | @ImportResource("classpath:com/springsource/oauthservice/config/security.xml") 35 | public class SecurityConfig { 36 | 37 | @Inject 38 | private DataSource dataSource; 39 | 40 | @Bean 41 | public ClientDetailsService clientDetails() { 42 | JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource); 43 | clientDetailsService.setSelectClientDetailsSql(SELECT_CLIENT_DETAILS_SQL); 44 | return clientDetailsService; 45 | } 46 | 47 | @Bean 48 | public AuthorizationServerTokenServices tokenServices() { 49 | RandomValueTokenServices tokenServices = new RandomValueTokenServices(); 50 | TokenStore tokenStore = new JdbcTokenStore(dataSource); 51 | tokenServices.setTokenStore(tokenStore); 52 | tokenServices.setSupportRefreshToken(true); 53 | return tokenServices; 54 | } 55 | 56 | @Bean 57 | public TextEncryptor textEncryptor() { 58 | return Encryptors.noOpText(); 59 | } 60 | 61 | private static final String SELECT_CLIENT_DETAILS_SQL = "select apiKey, resourceIds, secret, scope, grantTypes, redirectUrl, authorities from App where apiKey = ?"; 62 | 63 | } 64 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/config/CloudDataSourceConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.config; 17 | 18 | import org.cloudfoundry.runtime.env.AbstractServiceInfo; 19 | import org.cloudfoundry.runtime.env.CloudEnvironment; 20 | import org.cloudfoundry.runtime.env.MongoServiceInfo; 21 | import org.cloudfoundry.runtime.env.RdbmsServiceInfo; 22 | import org.cloudfoundry.runtime.service.document.MongoServiceCreator; 23 | import org.cloudfoundry.runtime.service.relational.PostgresqlServiceCreator; 24 | import org.springframework.context.annotation.Bean; 25 | import org.springframework.context.annotation.Configuration; 26 | import org.springframework.context.annotation.Profile; 27 | import org.springframework.data.mongodb.MongoDbFactory; 28 | import org.springframework.data.mongodb.core.MongoTemplate; 29 | import org.springframework.util.Assert; 30 | 31 | import javax.sql.DataSource; 32 | import java.util.List; 33 | 34 | @Profile("cloud") 35 | @Configuration 36 | public class CloudDataSourceConfig implements DataSourceConfig { 37 | 38 | private CloudEnvironment cloudEnvironment = new CloudEnvironment(); 39 | 40 | @Bean 41 | @Override 42 | public DataSource dataSource() throws Exception { 43 | RdbmsServiceInfo rdbmsServiceInfo = requireOneService("RDBMS", RdbmsServiceInfo.class); 44 | return new PostgresqlServiceCreator().createService(rdbmsServiceInfo); 45 | } 46 | 47 | @Bean 48 | @Override 49 | public MongoTemplate mongoTemplate() throws Exception { 50 | MongoServiceInfo mongoServiceInfo = requireOneService("MongoDB", MongoServiceInfo.class); 51 | MongoDbFactory mongoDbFactory = new MongoServiceCreator().createService(mongoServiceInfo); 52 | return new MongoTemplate(mongoDbFactory); 53 | } 54 | 55 | private T requireOneService(String serviceName, Class clazzOfT) throws Exception { 56 | String errMsg = "There must be only one %s service bound to this application. Currently, there are %s %s services."; 57 | List serviceInfoList = cloudEnvironment.getServiceInfos(clazzOfT); 58 | Assert.isTrue(serviceInfoList.size() == 1, String.format(errMsg, serviceName, serviceInfoList.size(), serviceName)); 59 | return serviceInfoList.iterator().next(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/IdentityController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice; 17 | 18 | import java.security.Principal; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | import javax.inject.Inject; 23 | 24 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 25 | import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; 26 | import org.springframework.security.oauth2.provider.token.RandomValueTokenServices; 27 | import org.springframework.stereotype.Controller; 28 | import org.springframework.web.bind.annotation.RequestMapping; 29 | import org.springframework.web.bind.annotation.RequestMethod; 30 | import org.springframework.web.bind.annotation.ResponseBody; 31 | import org.springframework.web.context.request.NativeWebRequest; 32 | 33 | /** 34 | * Identity controller, allowing a client to fetch the identify of the authenticated user. 35 | * Intended to be secured by OAuth2, so that it will only fetch the identity when given a valid access token. 36 | * @author wallsc 37 | */ 38 | @Controller 39 | public class IdentityController { 40 | 41 | private final AuthorizationServerTokenServices tokenServices; 42 | 43 | @Inject 44 | public IdentityController(AuthorizationServerTokenServices tokenServices) { 45 | this.tokenServices = tokenServices; 46 | } 47 | 48 | @RequestMapping(value="/me", method=RequestMethod.GET) 49 | public @ResponseBody Map getIdentity(Principal principal) { 50 | HashMap identityMap = new HashMap(); 51 | identityMap.put("id", principal.getName()); 52 | return identityMap; 53 | } 54 | 55 | @RequestMapping(value="/me/authentication", method=RequestMethod.GET) 56 | public @ResponseBody OAuth2Authentication getIdentity(NativeWebRequest request) { 57 | // TODO: Naively pull the access token from the header for now...if this works, refine this 58 | String authHeader = request.getHeader("Authorization"); 59 | String accessToken = authHeader.split("\\s")[1]; 60 | // TODO - unlike OAuth2ProviderTokenServices, AuthorizationServerTokenServices does not have a loadAuthentication method 61 | // - what should be used here instead? 62 | return ((RandomValueTokenServices) tokenServices).loadAuthentication(accessToken); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /server/oauth/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | contextClass 9 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 10 | 11 | 12 | 13 | 14 | contextConfigLocation 15 | com.springsource.oauthservice.config 16 | 17 | 18 | 19 | 20 | org.springframework.web.context.ContextLoaderListener 21 | 22 | 23 | 24 | 25 | appServlet 26 | org.springframework.web.servlet.DispatcherServlet 27 | 28 | contextConfigLocation 29 | 30 | 31 | 1 32 | 33 | 34 | 35 | appServlet 36 | / 37 | 38 | 39 | 40 | 41 | H2Console 42 | org.h2.server.web.WebServlet 43 | 44 | -webAllowOthers 45 | true 46 | 47 | 2 48 | 49 | 50 | 51 | H2Console 52 | /admin/h2/* 53 | 54 | 55 | 56 | springSecurityFilterChain 57 | org.springframework.web.filter.DelegatingFilterProxy 58 | 59 | 60 | 61 | springSecurityFilterChain 62 | /* 63 | 64 | 65 | 66 | 67 | hiddenHttpMethodFilter 68 | org.springframework.web.filter.HiddenHttpMethodFilter 69 | 70 | 71 | 72 | hiddenHttpMethodFilter 73 | /* 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /server/api/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'war' 2 | apply plugin: 'jetty' 3 | apply plugin: 'idea' 4 | apply plugin: 'eclipse' 5 | 6 | repositories { 7 | mavenRepo urls: 'https://repo.springsource.org/libs-snapshot' 8 | mavenCentral() 9 | } 10 | 11 | configurations { 12 | all*.exclude group: 'commons-logging', module: 'commons-logging' 13 | } 14 | 15 | springVersion = '3.1.0.RELEASE' 16 | cglibVersion = '2.2' 17 | logbackVersion = '0.9.29' 18 | javaxInjectVersion = '1' 19 | jspVersion = '2.1' 20 | jodaTimeVersion = '2.0' 21 | servletVersion = '2.5' 22 | slf4jVersion = '1.6.2' 23 | h2Version = '1.2.144' 24 | junitVersion = '4.7' 25 | jacksonVersion = '1.9.2' 26 | commonsFileuploadVersion = '1.2.2' 27 | commonsIoVersion = '2.0.1' 28 | commonsLangVersion = '2.5' 29 | hibernateVersion = '3.6.5.Final' 30 | cloudFoundryVersion = '0.8.1' 31 | s2OAuthVersion = '1.0.0.M5' 32 | thymeleafVersion = '1.1.4' 33 | springBatchVersion = '2.1.6.RELEASE' 34 | springIntegrationVersion = '2.1.0.RELEASE' 35 | mongoVersion = '1.0.0.RC1' 36 | 37 | dependencies { 38 | compile "joda-time:joda-time:$jodaTimeVersion" 39 | compile "org.hibernate:hibernate-entitymanager:$hibernateVersion" 40 | compile "com.h2database:h2:$h2Version" 41 | compile "cglib:cglib-nodep:$cglibVersion" 42 | compile "ch.qos.logback:logback-classic:$logbackVersion" 43 | compile "org.slf4j:jcl-over-slf4j:$slf4jVersion" 44 | compile "org.springframework:spring-context:$springVersion" 45 | compile "org.springframework:spring-webmvc:$springVersion" 46 | compile "org.springframework:spring-orm:$springVersion" 47 | compile "org.springframework.security.oauth:spring-security-oauth2:$s2OAuthVersion" 48 | compile "org.springframework.integration:spring-integration-jms:$springIntegrationVersion" 49 | compile "org.springframework.batch:spring-batch-core:$springBatchVersion" 50 | compile "javax.inject:javax.inject:$javaxInjectVersion" 51 | compile "org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion" 52 | compile "commons-fileupload:commons-fileupload:$commonsFileuploadVersion" 53 | compile "commons-io:commons-io:$commonsIoVersion" 54 | compile "commons-lang:commons-lang:$commonsLangVersion" 55 | compile "org.cloudfoundry:cloudfoundry-runtime:$cloudFoundryVersion" 56 | compile "org.thymeleaf:thymeleaf-spring3:$thymeleafVersion" 57 | compile "postgresql:postgresql:8.3-603.jdbc3" 58 | compile "org.springframework.data:spring-data-mongodb:$mongoVersion" 59 | testCompile "org.springframework:spring-test:$springVersion" 60 | testCompile "junit:junit:$junitVersion" 61 | providedCompile "javax.servlet:servlet-api:$servletVersion" 62 | providedCompile "javax.servlet.jsp:jsp-api:$jspVersion" 63 | } 64 | 65 | sourceSets { 66 | main { 67 | resources { 68 | srcDirs = ['src/main/java', 'src/main/resources'] 69 | } 70 | } 71 | test { resources { srcDirs = ['src/test/java'] } } 72 | } 73 | 74 | jettyRun.contextPath = '/html5expense' 75 | 76 | task wrapper(type: Wrapper) { 77 | gradleVersion = '1.0-milestone-3' 78 | } 79 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/Expense.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense; 17 | 18 | 19 | import javax.persistence.*; 20 | import java.math.BigDecimal; 21 | import java.util.Date; 22 | 23 | @Entity 24 | @Table(name = "EXPENSE") 25 | public class Expense { 26 | 27 | @GeneratedValue(strategy = javax.persistence.GenerationType.AUTO) 28 | @Id 29 | private Integer id; 30 | 31 | @ManyToOne 32 | private ExpenseReport expenseReport; 33 | 34 | private Date expense_date; 35 | 36 | private String merchant; 37 | 38 | private String category; 39 | 40 | private BigDecimal amount; 41 | 42 | private Long chargeId; 43 | 44 | private String receipt; 45 | 46 | private String receiptExtension; 47 | 48 | private String flag; 49 | 50 | 51 | Expense(ExpenseReport er, Date expense_date, String merchant, String category, BigDecimal amount, Long chargeId) { 52 | this.expense_date = expense_date; 53 | this.expenseReport = er; 54 | this.merchant = merchant; 55 | this.category = category; 56 | this.amount = amount; 57 | this.chargeId = chargeId; 58 | } 59 | 60 | public Integer getId() { 61 | return id; 62 | } 63 | 64 | public boolean isFlagged() { 65 | return flag != null; 66 | } 67 | 68 | public void flag(String flag) { 69 | this.flag = flag; 70 | } 71 | 72 | public String getReceipt() { 73 | return receipt; 74 | } 75 | 76 | public String getReceiptExtension() { 77 | return this.receiptExtension; 78 | } 79 | 80 | public void attachReceipt(String receipt, String extension) { 81 | this.receipt = receipt; 82 | this.receiptExtension = extension; 83 | 84 | if (isFlagged() && this.flag.equals("receiptRequired")) { 85 | this.flag = null; 86 | } 87 | } 88 | 89 | public BigDecimal getAmount() { 90 | return amount; 91 | } 92 | 93 | public String getCategory() { 94 | return category; 95 | } 96 | 97 | public Long getChargeId() { 98 | return chargeId; 99 | } 100 | 101 | public Date getDate() { 102 | return expense_date; 103 | } 104 | 105 | public ExpenseReport getExpenseReport() { 106 | return expenseReport; 107 | } 108 | 109 | public String getFlag() { 110 | return flag; 111 | } 112 | 113 | public String getMerchant() { 114 | return merchant; 115 | } 116 | // hibernate 117 | 118 | Expense() { 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /server/api/src/main/webapp/assets/js/upload.js: -------------------------------------------------------------------------------- 1 | function setupUploader() { 2 | 3 | var dropbox = $('#dropbox'), 4 | message = $('.message', dropbox); 5 | 6 | dropbox.filedrop({ 7 | // The name of the $_FILES entry: 8 | paramname:'pic', 9 | 10 | maxfiles: 20, 11 | maxfilesize: 2, // MBs 12 | url: 'post_file.php', 13 | 14 | uploadFinished:function(i, file, response) { 15 | $.data(file).addClass('done'); 16 | // response is the JSON object that post_file.php returns 17 | }, 18 | 19 | error: function(err, file) { 20 | switch (err) { 21 | case 'BrowserNotSupported': 22 | showMessage('Your browser does not support HTML5 file uploads!'); 23 | break; 24 | case 'TooManyFiles': 25 | alert('Too many files! Please select 5 at most! (configurable)'); 26 | break; 27 | case 'FileTooLarge': 28 | alert(file.name + ' is too large! Please upload files up to 2mb (configurable).'); 29 | break; 30 | default: 31 | alert( err + ""); 32 | break; 33 | } 34 | }, 35 | 36 | // Called before each upload is started 37 | beforeEach: function(file) { 38 | if (!file.type.match(/^image\//)) { 39 | alert('Only images are allowed!'); 40 | 41 | // Returning false will cause the 42 | // file to be rejected 43 | return false; 44 | } 45 | }, 46 | 47 | uploadStarted:function(i, file, len) { 48 | createImage(file); 49 | }, 50 | 51 | progressUpdated: function(i, file, progress) { 52 | $.data(file).find('.progress').width(progress); 53 | } 54 | 55 | }); 56 | 57 | var template = '
' + 58 | '' + 59 | '' + 60 | '' + 61 | '' + 62 | '
' + 63 | '
' + 64 | '
' + 65 | '
'; 66 | 67 | 68 | function createImage(file) { 69 | 70 | var preview = $(template), 71 | image = $('img', preview); 72 | 73 | var reader = new FileReader(); 74 | 75 | image.width = 100; 76 | image.height = 100; 77 | 78 | reader.onload = function(e) { 79 | 80 | // e.target.result holds the DataURL which 81 | // can be used as a source of the image: 82 | 83 | image.attr('src', e.target.result); 84 | }; 85 | 86 | // Reading the file as a DataURL. When finished, 87 | // this will trigger the onload function above: 88 | reader.readAsDataURL(file); 89 | 90 | message.hide(); 91 | preview.appendTo(dropbox); 92 | 93 | // Associating a preview container 94 | // with the file, using jQuery's $.data(): 95 | 96 | $.data(file, preview); 97 | } 98 | 99 | function showMessage(msg) { 100 | message.html(msg); 101 | } 102 | } -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/develop/AppController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice.develop; 17 | 18 | import java.security.Principal; 19 | import java.util.List; 20 | 21 | import javax.inject.Inject; 22 | import javax.validation.Valid; 23 | 24 | import org.springframework.stereotype.Controller; 25 | import org.springframework.ui.Model; 26 | import org.springframework.validation.BindingResult; 27 | import org.springframework.web.bind.annotation.PathVariable; 28 | import org.springframework.web.bind.annotation.RequestMapping; 29 | import org.springframework.web.bind.annotation.RequestMethod; 30 | 31 | @Controller 32 | @RequestMapping("/develop") 33 | public class AppController { 34 | 35 | private final AppRepository appRepository; 36 | 37 | @Inject 38 | public AppController(AppRepository appRepository) { 39 | this.appRepository = appRepository; 40 | } 41 | 42 | @RequestMapping(value="/apps", method=RequestMethod.GET) 43 | public List list(Principal user) { 44 | return appRepository.findAppSummaries(user.getName()); 45 | } 46 | 47 | @RequestMapping(value="/apps/new", method=RequestMethod.GET) 48 | public AppForm newForm() { 49 | return appRepository.getNewAppForm(); 50 | } 51 | 52 | @RequestMapping(value="/apps", method=RequestMethod.POST) 53 | public String create(Principal user, @Valid AppForm form, BindingResult bindingResult) { 54 | if (bindingResult.hasErrors()) { 55 | return "develop/apps/new"; 56 | } 57 | return "redirect:/develop/apps/" + appRepository.createApp(user.getName(), form); 58 | } 59 | 60 | @RequestMapping(value="/apps/{slug}", method=RequestMethod.GET) 61 | public String view(@PathVariable String slug, Principal user, Model model) { 62 | model.addAttribute(appRepository.findAppBySlug(user.getName(), slug)); 63 | model.addAttribute("slug", slug); 64 | return "develop/apps/view"; 65 | } 66 | 67 | @RequestMapping(value="/apps/{slug}", method=RequestMethod.DELETE) 68 | public String delete(@PathVariable String slug, Principal user) { 69 | appRepository.deleteApp(user.getName(), slug); 70 | return "redirect:/develop/apps"; 71 | } 72 | 73 | @RequestMapping(value="/apps/edit/{slug}", method=RequestMethod.GET) 74 | public String editForm(@PathVariable String slug, Principal user, Model model) { 75 | model.addAttribute(appRepository.getAppForm(user.getName(), slug)); 76 | model.addAttribute("slug", slug); 77 | return "develop/apps/edit"; 78 | } 79 | 80 | @RequestMapping(value="/apps/{slug}", method=RequestMethod.PUT) 81 | public String update(@PathVariable String slug, @Valid AppForm form, BindingResult bindingResult, Principal user, Model model) { 82 | if (bindingResult.hasErrors()) { 83 | model.addAttribute("slug", slug); 84 | return "develop/apps/edit"; 85 | } 86 | return "redirect:/develop/apps/" + appRepository.updateApp(user.getName(), slug, form); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/services/utilities/MongoDbGridFsUtilities.java: -------------------------------------------------------------------------------- 1 | package com.springsource.html5expense.services.utilities; 2 | 3 | import com.mongodb.DB; 4 | import com.mongodb.DBObject; 5 | import com.mongodb.MongoException; 6 | import com.mongodb.gridfs.GridFS; 7 | import com.mongodb.gridfs.GridFSDBFile; 8 | import com.mongodb.gridfs.GridFSFile; 9 | import com.mongodb.gridfs.GridFSInputFile; 10 | import org.apache.commons.io.IOUtils; 11 | import org.springframework.dao.DataAccessException; 12 | import org.springframework.data.mongodb.core.DbCallback; 13 | import org.springframework.data.mongodb.core.MongoTemplate; 14 | import org.springframework.stereotype.Component; 15 | import org.springframework.util.Assert; 16 | 17 | import javax.inject.Inject; 18 | import java.io.InputStream; 19 | 20 | /** 21 | * Simple utility class to interface with MongoDB's GridFS 22 | * abstraction, which can be quite handy for storing files. 23 | * 24 | * NB: it's expected that more mature support will eventually be available in the Spring Data Mongo 25 | * project, so this is expected to be phased out, at some point. 26 | * 27 | * @author Josh Long 28 | */ 29 | @Component 30 | public class MongoDbGridFsUtilities { 31 | 32 | private MongoTemplate mongoTemplate; 33 | 34 | @Inject 35 | public MongoDbGridFsUtilities(MongoTemplate mongoTemplate){ 36 | this.mongoTemplate = mongoTemplate; 37 | } 38 | 39 | /** 40 | * A simple utility to write objects to the database 41 | * 42 | * @param bucket the name of the collection to store it into 43 | * @param content the content to write 44 | * @param filename the file name to use for the file (in practice, this could be any String) 45 | * @param metadata the metadata to associate with this information (it's optional) 46 | * @return a {@link GridFSFile} that represents the written data. 47 | */ 48 | public GridFSFile write( final String bucket, final InputStream content, final String filename, final DBObject metadata) { 49 | Assert.notNull(content); 50 | Assert.hasText(filename); 51 | return mongoTemplate.execute(new DbCallback() { 52 | @Override 53 | public GridFSInputFile doInDB(DB db) throws MongoException, DataAccessException { 54 | GridFSInputFile file = gridFs(db, bucket).createFile(content, filename, true); 55 | file.setFilename(filename); 56 | if (null != metadata) 57 | file.setMetaData(metadata); 58 | file.save(); 59 | IOUtils.closeQuietly(content); 60 | return file; 61 | } 62 | }); 63 | } 64 | 65 | /** 66 | * Reads file data from MongoDB 67 | * 68 | * @param bucket the name of the collection to put the file 69 | * @param fileName the name of the file 70 | * @return an InputStream that the application can read from. 71 | */ 72 | public InputStream read( final String bucket, final String fileName) { 73 | return mongoTemplate.executeInSession(new DbCallback() { 74 | @Override 75 | public InputStream doInDB(DB db) throws MongoException, DataAccessException { 76 | GridFS gridFS = gridFs(db, bucket); 77 | GridFSDBFile file = gridFS.findOne(fileName); 78 | return file.getInputStream(); 79 | } 80 | }); 81 | } 82 | 83 | private static GridFS gridFs(DB db, String bucket) { 84 | return new GridFS(db, bucket); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /client/ios/html5expense/Classes/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // html5expense 4 | // 5 | // Created by Roy Clarkson on 8/30/11. 6 | // Copyright VMware, Inc. 2011. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #ifdef PHONEGAP_FRAMEWORK 11 | #import 12 | #else 13 | #import "PhoneGapViewController.h" 14 | #endif 15 | 16 | @implementation AppDelegate 17 | 18 | @synthesize invokeString; 19 | 20 | - (id) init 21 | { 22 | /** If you need to do any extra app-specific initialization, you can do it here 23 | * -jm 24 | **/ 25 | return [super init]; 26 | } 27 | 28 | /** 29 | * This is main kick off after the app inits, the views and Settings are setup here. (preferred - iOS4 and up) 30 | */ 31 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 32 | { 33 | 34 | NSArray *keyArray = [launchOptions allKeys]; 35 | if ([launchOptions objectForKey:[keyArray objectAtIndex:0]]!=nil) 36 | { 37 | NSURL *url = [launchOptions objectForKey:[keyArray objectAtIndex:0]]; 38 | self.invokeString = [url absoluteString]; 39 | NSLog(@"html5expense launchOptions = %@",url); 40 | } 41 | 42 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 43 | } 44 | 45 | // this happens while we are running ( in the background, or from within our own app ) 46 | // only valid if html5expense.plist specifies a protocol to handle 47 | - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url 48 | { 49 | // must call super so all plugins will get the notification, and their handlers will be called 50 | // super also calls into javascript global function 'handleOpenURL' 51 | return [super application:application handleOpenURL:url]; 52 | } 53 | 54 | -(id) getCommandInstance:(NSString*)className 55 | { 56 | /** You can catch your own commands here, if you wanted to extend the gap: protocol, or add your 57 | * own app specific protocol to it. -jm 58 | **/ 59 | return [super getCommandInstance:className]; 60 | } 61 | 62 | /** 63 | Called when the webview finishes loading. This stops the activity view and closes the imageview 64 | */ 65 | - (void)webViewDidFinishLoad:(UIWebView *)theWebView 66 | { 67 | // only valid if html5expense.plist specifies a protocol to handle 68 | if(self.invokeString) 69 | { 70 | // this is passed before the deviceready event is fired, so you can access it in js when you receive deviceready 71 | NSString* jsString = [NSString stringWithFormat:@"var invokeString = \"%@\";", self.invokeString]; 72 | [theWebView stringByEvaluatingJavaScriptFromString:jsString]; 73 | } 74 | return [ super webViewDidFinishLoad:theWebView ]; 75 | } 76 | 77 | - (void)webViewDidStartLoad:(UIWebView *)theWebView 78 | { 79 | return [ super webViewDidStartLoad:theWebView ]; 80 | } 81 | 82 | /** 83 | * Fail Loading With Error 84 | * Error - If the webpage failed to load display an error with the reason. 85 | */ 86 | - (void)webView:(UIWebView *)theWebView didFailLoadWithError:(NSError *)error 87 | { 88 | return [ super webView:theWebView didFailLoadWithError:error ]; 89 | } 90 | 91 | /** 92 | * Start Loading Request 93 | * This is where most of the magic happens... We take the request(s) and process the response. 94 | * From here we can re direct links and other protocalls to different internal methods. 95 | */ 96 | - (BOOL)webView:(UIWebView *)theWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 97 | { 98 | return [ super webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType ]; 99 | } 100 | 101 | 102 | - (BOOL) execute:(InvokedUrlCommand*)command 103 | { 104 | return [ super execute:command]; 105 | } 106 | 107 | - (void)dealloc 108 | { 109 | [ super dealloc ]; 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/ExpenseReportingService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense; 17 | 18 | import java.io.InputStream; 19 | import java.math.BigDecimal; 20 | import java.util.Collection; 21 | import java.util.Date; 22 | import java.util.List; 23 | 24 | 25 | /** 26 | * Manages user expense reports. 27 | * 28 | * @author Keith Donald 29 | * @author Josh Long 30 | */ 31 | public interface ExpenseReportingService { 32 | 33 | void deleteExpenseReport(Long expenseReportId); 34 | 35 | InputStream retrieveReceipt(Integer expenseId); 36 | 37 | void updateExpenseReportPurpose(Long reportId, String title); 38 | 39 | Collection getExpensesForExpenseReport(Long reportId); 40 | 41 | /** 42 | * Responsible for installing new {@link EligibleCharge}s into the database 43 | */ 44 | EligibleCharge createEligibleCharge(Date date, String merchant, String category, BigDecimal amt); 45 | 46 | /** 47 | * Creates a new expense report. 48 | * 49 | * @param purpose the purpose for this report, e.g., "Palo Alto Face to Face Meeting" 50 | * @return the unique ID of the expense report 51 | */ 52 | Long createReport(String purpose); 53 | 54 | void restoreEligibleCharges(List expenseIds); 55 | 56 | /** 57 | * Retrieves the charges that are eligible to be expensed. 58 | * The user is expected to add one or more of these charges to the report. 59 | * 60 | * @return the list of eligible charges 61 | */ 62 | Collection getEligibleCharges(); 63 | 64 | /** 65 | * Adds the selected charges to the expense report. 66 | * Creates and returns a new expense for each charge. 67 | * 68 | * @param reportId the expense report id 69 | * @param chargeIds the eligible charge ids 70 | * @return an expense for each charge 71 | */ 72 | Collection createExpenses(Long reportId, List chargeIds); 73 | 74 | /** 75 | * Attach a receipt to an expense. 76 | * 77 | * @param reportId the expense report id 78 | * @param receiptBytes the receipt data as a byte array 79 | * @param ext the extension of the uploaded media 80 | * @return a pointer to the receipt 81 | */ 82 | String attachReceipt(Long reportId, Integer expenseId, String ext, byte[] receiptBytes); 83 | 84 | /** 85 | * Submit the expense report for approval. 86 | * 87 | * @param reportId the id of the report to file 88 | */ 89 | void submitReport(Long reportId); 90 | 91 | /** 92 | * Returns all the expense reports the user has open. 93 | * An open report is not under review and is not closed. 94 | * It can be edited by the user and {@link #submitReport(Long) submitted}. 95 | * 96 | * @return the user's open expense reports 97 | */ 98 | List getOpenReports(); 99 | 100 | /** 101 | * Returns all the expense reports the user has submitted. 102 | * A submitted report is under review or approved. 103 | * 104 | * @return the user's submitted expense reports 105 | */ 106 | List getSubmittedReports(); 107 | 108 | ExpenseReport getExpenseReport(Long reportId); 109 | 110 | Expense getExpense(Integer expenseId); 111 | } -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/security/EndpointTokenServices.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.security; 17 | 18 | import org.codehaus.jackson.map.ObjectMapper; 19 | import org.springframework.http.HttpEntity; 20 | import org.springframework.http.HttpHeaders; 21 | import org.springframework.http.HttpMethod; 22 | import org.springframework.http.ResponseEntity; 23 | import org.springframework.http.converter.HttpMessageConverter; 24 | import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; 25 | import org.springframework.security.core.AuthenticationException; 26 | import org.springframework.security.oauth2.common.OAuth2AccessToken; 27 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 28 | import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; 29 | import org.springframework.web.client.RestTemplate; 30 | 31 | import java.util.List; 32 | import java.util.Set; 33 | 34 | /** 35 | * Implementation of OAuth2ProviderTokenServices that loads authentication via the OAuth service's authentication endpoint. 36 | * 37 | * @author wallsc 38 | */ 39 | public class EndpointTokenServices implements ResourceServerTokenServices { 40 | 41 | private final String oauthAuthenticationUrl; 42 | 43 | private final RestTemplate restTemplate; 44 | 45 | public EndpointTokenServices(String oauthAuthenticationUrl) { 46 | this.oauthAuthenticationUrl = oauthAuthenticationUrl; 47 | 48 | this.restTemplate = new RestTemplate(); 49 | List> messageConverters = restTemplate.getMessageConverters(); 50 | for (HttpMessageConverter httpMessageConverter : messageConverters) { 51 | if (httpMessageConverter.getClass().equals(MappingJacksonHttpMessageConverter.class)) { 52 | MappingJacksonHttpMessageConverter jsonConverter = (MappingJacksonHttpMessageConverter) httpMessageConverter; 53 | ObjectMapper mapper = new ObjectMapper(); 54 | mapper.getDeserializationConfig().addMixInAnnotations(OAuth2Authentication.class, OAuth2AuthenticationMixin.class); 55 | jsonConverter.setObjectMapper(mapper); 56 | } 57 | } 58 | } 59 | 60 | 61 | public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException { 62 | // TODO: Probably should catch REST client exceptions and rethrow as AuthenticationException 63 | HttpHeaders headers = new HttpHeaders(); 64 | headers.add("Authorization", "Bearer " + accessToken); 65 | HttpEntity requestEntity = new HttpEntity(headers); 66 | ResponseEntity response = restTemplate.exchange(oauthAuthenticationUrl, HttpMethod.GET, requestEntity, OAuth2Authentication.class); 67 | 68 | OAuth2Authentication oauth2Authentication = response.getBody(); 69 | return oauth2Authentication; 70 | } 71 | 72 | public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { 73 | throw new UnsupportedOperationException("Can't create an access token through this implementation."); 74 | } 75 | 76 | public OAuth2AccessToken refreshAccessToken(String refreshToken, Set scope) throws AuthenticationException { 77 | throw new UnsupportedOperationException("Can't refresh an access token through this implementation."); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/config/ComponentConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.config; 17 | 18 | import com.springsource.html5expense.EligibleCharge; 19 | import com.springsource.html5expense.services.JpaExpenseReportingService; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.ComponentScan; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.context.annotation.Import; 24 | import org.springframework.core.io.ClassPathResource; 25 | import org.springframework.jdbc.datasource.init.DataSourceInitializer; 26 | import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; 27 | import org.springframework.orm.jpa.JpaTransactionManager; 28 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 29 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 30 | import org.springframework.transaction.PlatformTransactionManager; 31 | import org.springframework.transaction.annotation.EnableTransactionManagement; 32 | 33 | import javax.inject.Inject; 34 | import javax.persistence.EntityManagerFactory; 35 | import java.util.HashMap; 36 | import java.util.Map; 37 | 38 | /** 39 | * Configuration for application @Components such as @Services, @Repositories, and @Controllers. 40 | * Loads externalized property values required to configure the various application properties. 41 | * Not much else here, as we rely on @Component scanning in conjunction with @Inject by-type autowiring. 42 | * 43 | * @author Keith Donald 44 | * @author Josh Long 45 | * @author Roy Clarkson 46 | */ 47 | @Configuration 48 | @EnableTransactionManagement 49 | @Import({LocalDataSourceConfig.class, CloudDataSourceConfig.class/*, SecurityConfig.class*/}) 50 | @ComponentScan(basePackageClasses = JpaExpenseReportingService.class) 51 | public class ComponentConfig { 52 | 53 | @Inject 54 | private DataSourceConfig dataSourceConfig; 55 | 56 | @Bean 57 | public DataSourceInitializer dataSourceInitializer() throws Throwable { 58 | ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(); 59 | databasePopulator.setContinueOnError(true); 60 | databasePopulator.setIgnoreFailedDrops(true); 61 | databasePopulator.addScript(new ClassPathResource("/setup/demo-data.sql")); 62 | 63 | DataSourceInitializer dataSourceInitializer = new DataSourceInitializer(); 64 | dataSourceInitializer.setDataSource(this.dataSourceConfig.dataSource()); 65 | dataSourceInitializer.setEnabled(true); 66 | dataSourceInitializer.setDatabasePopulator(databasePopulator); 67 | 68 | return dataSourceInitializer; 69 | } 70 | 71 | @Bean 72 | public PlatformTransactionManager transactionManager() throws Exception { 73 | EntityManagerFactory emf = entityManagerFactory().getObject(); 74 | return new JpaTransactionManager(emf); 75 | } 76 | 77 | @Bean 78 | public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws Exception { 79 | 80 | Map props = new HashMap(); 81 | props.put("hibernate.hbm2ddl.auto", "create-update"); 82 | 83 | LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean(); 84 | emfb.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); 85 | emfb.setDataSource(dataSourceConfig.dataSource()); 86 | emfb.setJpaPropertyMap(props); 87 | emfb.setPackagesToScan(EligibleCharge.class.getPackage().getName()); 88 | 89 | return emfb; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # HTML5 EXPENSE CLIENT # 2 | 3 | Expense reporting reference app demonstrating HTML5 and cross-platform mobile 4 | 5 | ## Environment Setup ## 6 | 7 | ### iOS ### 8 | 9 | Building iOS projects with PhoneGap requires Apple OS X. 10 | 11 | #### Xcode #### 12 | 13 | Install Xcode from the [Mac App Store](https://itunes.apple.com/us/app/xcode/id448457090?mt=12). 14 | 15 | #### PhoneGap #### 16 | 17 | The PhoneGap installer will create a new Xcode project template, which you can use to create new iOS PhoneGap projects. The HTML5 Expense client was created with this template. 18 | 19 | 1. Download version 1.3.0 or newer of [PhoneGap](https://www.phonegap.com/). 20 | 2. Unzip the PhoneGap package. 21 | 3. From the iOS directory, open the PhoneGap-1.3.0.dmg disk image. 22 | 4. Double click the PhoneGap-1.3.0.pkg from within the disk image contents to install. 23 | 24 | ### Android ### 25 | 26 | Building Android projects is supported on Windows, OS X, and Linux. 27 | 28 | #### Eclipse #### 29 | 30 | Download and install [Eclipse](https://www.eclipse.org/downloads/). The [SpringSource Tool Suite](https://www.springsource.com/landing/best-development-tool-enterprise-java) also works quite nicely. 31 | 32 | #### Android SDK #### 33 | 34 | Download and install the [Android SDK](https://developer.android.com/sdk/index.html). 35 | 36 | #### Android Development Tools (ADT) Plugin for Eclipse #### 37 | 38 | Install the [ADT Plugin](https://developer.android.com/sdk/eclipse-adt.html#installing) in Eclipse. 39 | 40 | #### PhoneGap #### 41 | 42 | PhoneGap does not provide an installer for Android projects, however when you create a new project you simply deploy the PhoneGap JAR and JavaScript files into your new project. The HTML5 Expense Android client project directory already has these items included. 43 | 44 | ## Project Structure ## 45 | 46 | The 'shared/www' directory contains the HTML, CSS, and JavaScript that is being used within the iOS and Android Phonegap projects. A symbolic link (symlink) is used within each project to point back to this folder. In the iOS project, the symlink is 'ios/www'. In the Android project it is located at 'android/assets/www'. Symlinks are supported in Mac OS X, Linux, Windows Vista, and Windows 7. This PhoneGap [wiki page](https://github.com/phonegap/phonegap/wiki) describes this solution for shared web resources across device types. 47 | 48 | ## Open the Client Projects ## 49 | 50 | This section describes how to open the iOS and Android projects within Xcode and Eclipse respectively. 51 | 52 | ### iOS ### 53 | 54 | After you have completed the environment setup for Xcode, you can open the iOS client project. 55 | 56 | #### Open Project in Xcode #### 57 | 58 | 1. Open Xcode. 59 | 2. Select File -> Open, or click the Open Other button on the welcome screen. 60 | 3. In the Open dialog, browse to the html5expense/client/ios directory and click Open. 61 | 62 | The project should now build successfully 63 | 64 | ### Android ### 65 | 66 | After you have completed the environment setup for Eclipse, you can open the Android client project. 67 | 68 | #### Import Project into Eclipse #### 69 | 70 | Follow these steps to import the Android client project into Eclipse. 71 | 72 | 1. From the File menu, select New -> Project... 73 | 2. From the New Project dialog, select Android -> Android Project, and click Next. NOTE: if Android is not available, the ADT Plugin for Eclipse is not installed properly. 74 | 3. From the New Android Project dialog, enter html5expense in the Project name field. 75 | 4. Within the Contents section, select the Create project from existing source radio button. 76 | 5. In the Open dialog, browse to the html5expense/client/android directory and click Open. 77 | 6. All the remaining fields in the New Android Project dialog should now be populated. 78 | 7. Click Finish. 79 | 80 | #### Configure Build Path #### 81 | 82 | That completes the steps to import the project. Note the html5expense project listed in the Eclipse Package Explorer on the left side of the screen. Also note the small red X over the project. Html5expense will not build without another small modification. Follow these steps to complete the project setup in Eclipse. 83 | 84 | 1. Right click (Command click on OS X) the html5expense project in Eclipse. 85 | 2. Navigate to Build Path -> Configure Build Path. 86 | 3. Select the Libraries tab. 87 | 4. Click Add JARs... 88 | 5. In the JAR Selection dialog, navigate to html5expense/libs/phonegap-1.3.0.jar and click OK. The PhoneGap jar should now be listed in the "JARs and class folders on the build path" section. 89 | 6. Click OK. 90 | 91 | The project should now build successfully, and the red X should disappear from the project listing. 92 | 93 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.config; 17 | 18 | import com.springsource.html5expense.controllers.ExpenseReportingApiController; 19 | import org.springframework.beans.factory.annotation.Value; 20 | import org.springframework.context.MessageSource; 21 | import org.springframework.context.annotation.*; 22 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; 23 | import org.springframework.context.support.ResourceBundleMessageSource; 24 | import org.springframework.web.method.support.HandlerMethodReturnValueHandler; 25 | import org.springframework.web.multipart.commons.CommonsMultipartResolver; 26 | import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; 27 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 28 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 29 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 30 | import org.thymeleaf.TemplateMode; 31 | import org.thymeleaf.spring3.SpringTemplateEngine; 32 | import org.thymeleaf.spring3.view.ThymeleafViewResolver; 33 | import org.thymeleaf.templateresolver.ServletContextTemplateResolver; 34 | 35 | import java.util.List; 36 | 37 | @Configuration 38 | @ComponentScan(basePackageClasses = ExpenseReportingApiController.class) 39 | @Import(ComponentConfig.class) 40 | @PropertySource("/config.properties") 41 | @EnableWebMvc 42 | public class WebConfig extends WebMvcConfigurerAdapter { 43 | 44 | @Value("${debug}") 45 | private boolean debug; 46 | 47 | private int maxUploadSizeInMb = 20 * 1024 * 1024; 48 | 49 | @Bean(name = "multipartResolver") 50 | public CommonsMultipartResolver commonsMultipartResolver() { 51 | CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(); 52 | commonsMultipartResolver.setMaxUploadSize(maxUploadSizeInMb); 53 | return commonsMultipartResolver; 54 | } 55 | 56 | @Override 57 | public void configureDefaultServletHandling( 58 | DefaultServletHandlerConfigurer configurer) { 59 | configurer.enable(); 60 | } 61 | 62 | @Override 63 | public void addViewControllers(ViewControllerRegistry registry) { 64 | registry.addViewController("/").setViewName("receipts"); 65 | } 66 | 67 | @Bean 68 | public ThymeleafViewResolver viewResolver() { 69 | ThymeleafViewResolver thymeleafViewResolver = new ThymeleafViewResolver(); 70 | thymeleafViewResolver.setTemplateEngine(this.templateEngine()); 71 | return thymeleafViewResolver; 72 | } 73 | 74 | @Bean 75 | public MessageSource messageSource() { 76 | String[] baseNames = "messages,errors".split(","); 77 | ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource(); 78 | resourceBundleMessageSource.setBasenames(baseNames); 79 | return resourceBundleMessageSource; 80 | } 81 | 82 | @Bean 83 | public ServletContextTemplateResolver servletContextTemplateResolver() { 84 | ServletContextTemplateResolver servletContextTemplateResolver = new ServletContextTemplateResolver(); 85 | servletContextTemplateResolver.setPrefix("/WEB-INF/views/"); 86 | servletContextTemplateResolver.setCacheable(!debug); 87 | servletContextTemplateResolver.setSuffix(".xhtml"); 88 | servletContextTemplateResolver.setTemplateMode(TemplateMode.HTML5); 89 | return servletContextTemplateResolver; 90 | } 91 | 92 | @Bean 93 | public SpringTemplateEngine templateEngine() { 94 | SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine(); 95 | springTemplateEngine.setTemplateResolver(this.servletContextTemplateResolver()); 96 | return springTemplateEngine; 97 | } 98 | 99 | @Bean 100 | public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { 101 | return new PropertySourcesPlaceholderConfigurer(); 102 | } 103 | } -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/ExpenseReport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense; 17 | 18 | import com.springsource.html5expense.services.Flag; 19 | 20 | import javax.persistence.*; 21 | import java.math.BigDecimal; 22 | import java.util.ArrayList; 23 | import java.util.Date; 24 | import java.util.List; 25 | 26 | @Entity 27 | @Table(name = "EXPENSE_REPORT") 28 | public class ExpenseReport { 29 | 30 | @GeneratedValue(strategy = javax.persistence.GenerationType.AUTO) 31 | @Id 32 | private Long id; 33 | 34 | private String purpose; 35 | 36 | @OneToMany(mappedBy = "expenseReport") 37 | private List expenses = new ArrayList(); 38 | 39 | private BigDecimal receiptRequiredAmount = new BigDecimal("25.00"); 40 | 41 | @Enumerated(EnumType.STRING) 42 | private State state = State.NEW; 43 | 44 | public void setPurpose(String purpose) { 45 | this.purpose = purpose; 46 | } 47 | 48 | public State getState() { 49 | return this.state; 50 | } 51 | 52 | public ExpenseReport(String purpose) { 53 | this.purpose = purpose; 54 | } 55 | 56 | public ExpenseReport(Long id, String purpose) { 57 | this.id = id; 58 | this.purpose = purpose; 59 | } 60 | 61 | public Long getId() { 62 | return id; 63 | } 64 | 65 | public String getPurpose() { 66 | return purpose; 67 | } 68 | 69 | public boolean isOpen() { 70 | return state == State.NEW || state == State.REJECTED; 71 | } 72 | 73 | public boolean isSubmitted() { 74 | return state == State.APPROVED || state == State.IN_REVIEW; 75 | } 76 | 77 | public Expense createExpense(EligibleCharge charge) { 78 | return this.createExpense(charge.getDate(), charge.getMerchant(), 79 | charge.getCategory(), charge.getAmount(), charge.getI()); 80 | } 81 | 82 | private Expense createExpense(Date date, String merchant, String category, BigDecimal amount, Long chargeId) { 83 | assertOpen(); 84 | Expense expense = new Expense(this, date, merchant, category, amount, chargeId); 85 | if (expense.getAmount().compareTo(receiptRequiredAmount) == 1) 86 | expense.flag("receiptRequired"); 87 | this.expenses.add(expense); 88 | return expense; 89 | } 90 | 91 | public void attachReceipt(Integer expenseId, String receipt, String key) { 92 | assertOpen(); 93 | getExpense(expenseId).attachReceipt(receipt, key); 94 | } 95 | 96 | public void markInReview() { 97 | assertOpen(); 98 | if (isFlagged()) { 99 | throw new IllegalStateException("Report is flagged"); 100 | } 101 | this.state = State.IN_REVIEW; 102 | } 103 | 104 | public void markRejected(List flags) { 105 | assertInReview(); 106 | for (Flag flag : flags) { 107 | getExpense(flag.getExpenseId()).flag(flag.getValue()); 108 | } 109 | this.state = State.REJECTED; 110 | } 111 | 112 | public void markApproved() { 113 | assertInReview(); 114 | this.state = State.APPROVED; 115 | } 116 | 117 | private void assertOpen() { 118 | if (!isOpen()) { 119 | throw new IllegalStateException("Report not open"); 120 | } 121 | } 122 | 123 | private void assertInReview() { 124 | if (state != State.IN_REVIEW) { 125 | throw new IllegalStateException("Report not in review"); 126 | } 127 | } 128 | 129 | private boolean isFlagged() { 130 | for (Expense expense : expenses) { 131 | if (expense.isFlagged()) { 132 | return true; 133 | } 134 | } 135 | return false; 136 | } 137 | 138 | private Expense getExpense(Integer id) { 139 | for (Expense expense : expenses) { 140 | if (expense.getId().equals(id)) { 141 | return expense; 142 | } 143 | } 144 | throw new IllegalArgumentException("No such expense"); 145 | } 146 | 147 | // Hibernate 148 | 149 | ExpenseReport() { 150 | } 151 | 152 | } -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/security/OAuth2AuthenticationMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.security; 17 | 18 | import com.springsource.html5expense.security.OAuth2AuthenticationMixin.OAuth2AuthenticationDeserializer; 19 | import org.codehaus.jackson.JsonNode; 20 | import org.codehaus.jackson.JsonParser; 21 | import org.codehaus.jackson.JsonProcessingException; 22 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 23 | import org.codehaus.jackson.map.DeserializationContext; 24 | import org.codehaus.jackson.map.JsonDeserializer; 25 | import org.codehaus.jackson.map.annotate.JsonDeserialize; 26 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 27 | import org.springframework.security.core.GrantedAuthority; 28 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 29 | import org.springframework.security.oauth2.provider.ClientToken; 30 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 31 | 32 | import java.io.IOException; 33 | import java.util.HashSet; 34 | import java.util.Iterator; 35 | import java.util.Set; 36 | 37 | @JsonIgnoreProperties(ignoreUnknown = true) 38 | @JsonDeserialize(using = OAuth2AuthenticationDeserializer.class) 39 | public class OAuth2AuthenticationMixin { 40 | 41 | public static class OAuth2AuthenticationDeserializer extends JsonDeserializer { 42 | @Override 43 | public OAuth2Authentication deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { 44 | JsonNode tree = jp.readValueAsTree(); 45 | ClientToken clientAuthentication = deserializeClientAuthentication(tree); 46 | UsernamePasswordAuthenticationToken userAuthentication = deserializeUserAuthentication(tree); 47 | OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(clientAuthentication, userAuthentication); 48 | oAuth2Authentication.setAuthenticated(true); 49 | return oAuth2Authentication; 50 | } 51 | 52 | private ClientToken deserializeClientAuthentication(JsonNode treeNode) { 53 | JsonNode clientAuthenticationNode = treeNode.get("clientAuthentication"); 54 | String clientId = clientAuthenticationNode.get("clientId").getValueAsText(); 55 | JsonNode resourceIdsNode = clientAuthenticationNode.get("resourceIds"); 56 | Set resourceIds = new HashSet(resourceIdsNode.size()); 57 | for (Iterator resourceIdsIt = resourceIdsNode.getElements(); resourceIdsIt.hasNext(); ) { 58 | resourceIds.add(resourceIdsIt.next().getValueAsText()); 59 | } 60 | String clientSecret = clientAuthenticationNode.get("clientSecret").getValueAsText(); 61 | 62 | JsonNode scopeNode = clientAuthenticationNode.get("scope"); 63 | Set scope = new HashSet(scopeNode.size()); 64 | for (Iterator scopeIt = scopeNode.getElements(); scopeIt.hasNext(); ) { 65 | scope.add(scopeIt.next().getValueAsText()); 66 | } 67 | Set authorities = getAuthorities(clientAuthenticationNode); 68 | return new ClientToken(clientId, resourceIds, clientSecret, scope, authorities); 69 | 70 | } 71 | 72 | private UsernamePasswordAuthenticationToken deserializeUserAuthentication(JsonNode treeNode) { 73 | JsonNode userAuthenticationNode = treeNode.get("userAuthentication"); 74 | String username = userAuthenticationNode.get("principal").get("username").getValueAsText(); 75 | Set authorities = getAuthorities(userAuthenticationNode); 76 | UsernamePasswordAuthenticationToken userAuthentication = new UsernamePasswordAuthenticationToken(username, null, authorities); 77 | return userAuthentication; 78 | } 79 | 80 | private Set getAuthorities(JsonNode parentNode) { 81 | JsonNode authoritiesNode = parentNode.get("authorities"); 82 | Set authorities = new HashSet(authoritiesNode.size()); 83 | for (Iterator authoritiesIt = authoritiesNode.getElements(); authoritiesIt.hasNext(); ) { 84 | JsonNode authorityNode = authoritiesIt.next(); 85 | authorities.add(new SimpleGrantedAuthority(authorityNode.get("authority").getValueAsText())); 86 | } 87 | return authorities; 88 | } 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /client/shared/www/jquery.json-2.3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery JSON Plugin 3 | * version: 2.3 (2011-09-17) 4 | * 5 | * This document is licensed as free software under the terms of the 6 | * MIT License: https://www.opensource.org/licenses/mit-license.php 7 | * 8 | * Brantley Harris wrote this plugin. It is based somewhat on the JSON.org 9 | * website's https://www.json.org/json2.js, which proclaims: 10 | * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that 11 | * I uphold. 12 | * 13 | * It is also influenced heavily by MochiKit's serializeJSON, which is 14 | * copyrighted 2005 by Bob Ippolito. 15 | */ 16 | 17 | (function( $ ) { 18 | 19 | var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g, 20 | meta = { 21 | '\b': '\\b', 22 | '\t': '\\t', 23 | '\n': '\\n', 24 | '\f': '\\f', 25 | '\r': '\\r', 26 | '"' : '\\"', 27 | '\\': '\\\\' 28 | }; 29 | 30 | /** 31 | * jQuery.toJSON 32 | * Converts the given argument into a JSON respresentation. 33 | * 34 | * @param o {Mixed} The json-serializble *thing* to be converted 35 | * 36 | * If an object has a toJSON prototype, that will be used to get the representation. 37 | * Non-integer/string keys are skipped in the object, as are keys that point to a 38 | * function. 39 | * 40 | */ 41 | $.toJSON = typeof JSON === 'object' && JSON.stringify 42 | ? JSON.stringify 43 | : function( o ) { 44 | 45 | if ( o === null ) { 46 | return 'null'; 47 | } 48 | 49 | var type = typeof o; 50 | 51 | if ( type === 'undefined' ) { 52 | return undefined; 53 | } 54 | if ( type === 'number' || type === 'boolean' ) { 55 | return '' + o; 56 | } 57 | if ( type === 'string') { 58 | return $.quoteString( o ); 59 | } 60 | if ( type === 'object' ) { 61 | if ( typeof o.toJSON === 'function' ) { 62 | return $.toJSON( o.toJSON() ); 63 | } 64 | if ( o.constructor === Date ) { 65 | var month = o.getUTCMonth() + 1, 66 | day = o.getUTCDate(), 67 | year = o.getUTCFullYear(), 68 | hours = o.getUTCHours(), 69 | minutes = o.getUTCMinutes(), 70 | seconds = o.getUTCSeconds(), 71 | milli = o.getUTCMilliseconds(); 72 | 73 | if ( month < 10 ) { 74 | month = '0' + month; 75 | } 76 | if ( day < 10 ) { 77 | day = '0' + day; 78 | } 79 | if ( hours < 10 ) { 80 | hours = '0' + hours; 81 | } 82 | if ( minutes < 10 ) { 83 | minutes = '0' + minutes; 84 | } 85 | if ( seconds < 10 ) { 86 | seconds = '0' + seconds; 87 | } 88 | if ( milli < 100 ) { 89 | milli = '0' + milli; 90 | } 91 | if ( milli < 10 ) { 92 | milli = '0' + milli; 93 | } 94 | return '"' + year + '-' + month + '-' + day + 'T' + 95 | hours + ':' + minutes + ':' + seconds + 96 | '.' + milli + 'Z"'; 97 | } 98 | if ( o.constructor === Array ) { 99 | var ret = []; 100 | for ( var i = 0; i < o.length; i++ ) { 101 | ret.push( $.toJSON( o[i] ) || 'null' ); 102 | } 103 | return '[' + ret.join(',') + ']'; 104 | } 105 | var name, 106 | val, 107 | pairs = []; 108 | for ( var k in o ) { 109 | type = typeof k; 110 | if ( type === 'number' ) { 111 | name = '"' + k + '"'; 112 | } else if (type === 'string') { 113 | name = $.quoteString(k); 114 | } else { 115 | // Keys must be numerical or string. Skip others 116 | continue; 117 | } 118 | type = typeof o[k]; 119 | 120 | if ( type === 'function' || type === 'undefined' ) { 121 | // Invalid values like these return undefined 122 | // from toJSON, however those object members 123 | // shouldn't be included in the JSON string at all. 124 | continue; 125 | } 126 | val = $.toJSON( o[k] ); 127 | pairs.push( name + ':' + val ); 128 | } 129 | return '{' + pairs.join( ',' ) + '}'; 130 | } 131 | }; 132 | 133 | /** 134 | * jQuery.evalJSON 135 | * Evaluates a given piece of json source. 136 | * 137 | * @param src {String} 138 | */ 139 | $.evalJSON = typeof JSON === 'object' && JSON.parse 140 | ? JSON.parse 141 | : function( src ) { 142 | return eval('(' + src + ')'); 143 | }; 144 | 145 | /** 146 | * jQuery.secureEvalJSON 147 | * Evals JSON in a way that is *more* secure. 148 | * 149 | * @param src {String} 150 | */ 151 | $.secureEvalJSON = typeof JSON === 'object' && JSON.parse 152 | ? JSON.parse 153 | : function( src ) { 154 | 155 | var filtered = 156 | src 157 | .replace( /\\["\\\/bfnrtu]/g, '@' ) 158 | .replace( /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 159 | .replace( /(?:^|:|,)(?:\s*\[)+/g, ''); 160 | 161 | if ( /^[\],:{}\s]*$/.test( filtered ) ) { 162 | return eval( '(' + src + ')' ); 163 | } else { 164 | throw new SyntaxError( 'Error parsing JSON, source is not valid.' ); 165 | } 166 | }; 167 | 168 | /** 169 | * jQuery.quoteString 170 | * Returns a string-repr of a string, escaping quotes intelligently. 171 | * Mostly a support function for toJSON. 172 | * Examples: 173 | * >>> jQuery.quoteString('apple') 174 | * "apple" 175 | * 176 | * >>> jQuery.quoteString('"Where are we going?", she asked.') 177 | * "\"Where are we going?\", she asked." 178 | */ 179 | $.quoteString = function( string ) { 180 | if ( string.match( escapeable ) ) { 181 | return '"' + string.replace( escapeable, function( a ) { 182 | var c = meta[a]; 183 | if ( typeof c === 'string' ) { 184 | return c; 185 | } 186 | c = a.charCodeAt(); 187 | return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); 188 | }) + '"'; 189 | } 190 | return '"' + string + '"'; 191 | }; 192 | 193 | })( jQuery ); 194 | -------------------------------------------------------------------------------- /server/oauth/src/main/java/com/springsource/oauthservice/develop/JdbcAppRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.oauthservice.develop; 17 | 18 | import java.sql.ResultSet; 19 | import java.sql.SQLException; 20 | import java.util.List; 21 | 22 | import javax.inject.Inject; 23 | 24 | import org.springframework.jdbc.core.JdbcTemplate; 25 | import org.springframework.jdbc.core.RowMapper; 26 | import org.springframework.security.crypto.encrypt.TextEncryptor; 27 | import org.springframework.security.crypto.keygen.KeyGenerators; 28 | import org.springframework.security.crypto.keygen.StringKeyGenerator; 29 | import org.springframework.stereotype.Repository; 30 | import org.springframework.transaction.annotation.Transactional; 31 | 32 | import com.springsource.oauthservice.utils.SlugUtils; 33 | 34 | @Repository 35 | public class JdbcAppRepository implements AppRepository { 36 | 37 | private JdbcTemplate jdbcTemplate; 38 | 39 | private TextEncryptor encryptor; 40 | 41 | private StringKeyGenerator keyGenerator = KeyGenerators.string(); 42 | 43 | @Inject 44 | public JdbcAppRepository(JdbcTemplate jdbcTemplate, TextEncryptor encryptor) { 45 | this.jdbcTemplate = jdbcTemplate; 46 | this.encryptor = encryptor; 47 | } 48 | 49 | public List findAppSummaries(String developerId) { 50 | return jdbcTemplate.query(SELECT_APPS, appSummaryMapper, developerId); 51 | } 52 | 53 | public App findAppBySlug(String developerId, String slug) { 54 | return jdbcTemplate.queryForObject(SELECT_APP_BY_SLUG, appMapper, developerId, slug); 55 | } 56 | 57 | public String updateApp(String developerId, String slug, AppForm form) { 58 | String newSlug = createSlug(form.getName()); 59 | jdbcTemplate.update(UPDATE_APP_FORM, form.getName(), newSlug, form.getDescription(), form.getOrganization(), form.getWebsite(), form.getCallbackUrl(), developerId, slug); 60 | return newSlug; 61 | } 62 | 63 | public void deleteApp(String developerId, String slug) { 64 | jdbcTemplate.update(DELETE_APP, developerId, slug); 65 | } 66 | 67 | public AppForm getNewAppForm() { 68 | return new AppForm(); 69 | } 70 | 71 | public AppForm getAppForm(String developerId, String slug) { 72 | return jdbcTemplate.queryForObject(SELECT_APP_FORM, appFormMapper, developerId, slug); 73 | } 74 | 75 | @Transactional 76 | public String createApp(String developerId, AppForm form) { 77 | String slug = createSlug(form.getName()); 78 | String encryptedApiKey = encryptor.encrypt(keyGenerator.generateKey()); 79 | String encryptedSecret = encryptor.encrypt(keyGenerator.generateKey()); 80 | jdbcTemplate.update(INSERT_APP, form.getName(), slug, form.getDescription(), form.getOrganization(), form.getWebsite(), encryptedApiKey, encryptedSecret, form.getCallbackUrl()); 81 | Long appId = jdbcTemplate.queryForLong("select lastVal()"); 82 | jdbcTemplate.update(INSERT_APP_DEVELOPER, appId, developerId); 83 | return slug; 84 | } 85 | 86 | private String createSlug(String appName) { 87 | return SlugUtils.toSlug(appName); 88 | } 89 | 90 | private static final String SELECT_APPS = "select a.name, a.slug, a.description from App a inner join AppDeveloper d on a.id = d.app where d.developer = ?"; 91 | 92 | private static final String SELECT_APP_BY_SLUG = "select a.name, a.slug, a.description, a.apiKey, a.secret, a.redirectUrl from App a inner join AppDeveloper d on a.id = d.app where d.developer = ? and a.slug = ?"; 93 | 94 | private static final String SELECT_APP_FORM = "select a.name, a.description, a.organization, a.website, a.redirectUrl from App a inner join AppDeveloper d on a.id = d.app where d.developer = ? and a.slug = ?"; 95 | 96 | private static final String UPDATE_APP_FORM = "update App set name = ?, slug = ?, description = ?, organization = ?, website = ?, redirectUrl = ? where exists(select 1 from AppDeveloper where developer = ?) and slug = ?"; 97 | 98 | private static final String DELETE_APP = "delete from App where exists(select 1 from AppDeveloper where developer = ?) and slug = ?"; 99 | 100 | private static final String INSERT_APP = "insert into App (name, slug, description, organization, website, apiKey, secret, redirectUrl, grantTypes) values (?, ?, ?, ?, ?, ?, ?, ?, 'password,authorization_code,refresh_token')"; 101 | 102 | private static final String INSERT_APP_DEVELOPER = "insert into AppDeveloper (app, developer) values (?, ?)"; 103 | 104 | private RowMapper appMapper = new RowMapper() { 105 | public App mapRow(ResultSet rs, int rowNum) throws SQLException { 106 | return new App(appSummaryMapper.mapRow(rs, rowNum), encryptor.decrypt(rs.getString("apiKey")), encryptor.decrypt(rs.getString("secret")), rs.getString("redirectUrl")); 107 | } 108 | }; 109 | 110 | private RowMapper appSummaryMapper = new RowMapper() { 111 | public AppSummary mapRow(ResultSet rs, int rowNum) throws SQLException { 112 | // TODO this is currently hardcoded 113 | String iconUrl = "https://images.greenhouse.springsource.org/apps/icon-default-app.png"; 114 | return new AppSummary(rs.getString("name"), iconUrl, rs.getString("description"), rs.getString("slug")); 115 | } 116 | }; 117 | 118 | private RowMapper appFormMapper = new RowMapper() { 119 | public AppForm mapRow(ResultSet rs, int rowNum) throws SQLException { 120 | AppForm form = new AppForm(); 121 | form.setName(rs.getString("name")); 122 | form.setDescription(rs.getString("description")); 123 | form.setOrganization(rs.getString("organization")); 124 | form.setWebsite(rs.getString("website")); 125 | form.setCallbackUrl(rs.getString("redirectUrl")); 126 | return form; 127 | } 128 | }; 129 | } 130 | -------------------------------------------------------------------------------- /server/api/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################################################## 4 | ## ## 5 | ## Gradle wrapper script for UN*X ## 6 | ## ## 7 | ############################################################################## 8 | 9 | # Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together. 10 | # GRADLE_OPTS="$GRADLE_OPTS -Xmx512m" 11 | # JAVA_OPTS="$JAVA_OPTS -Xmx512m" 12 | 13 | GRADLE_APP_NAME=Gradle 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set JAVA_HOME if it's not already set. 46 | if [ -z "$JAVA_HOME" ] ; then 47 | if $darwin ; then 48 | [ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home" 49 | [ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home" 50 | else 51 | javaExecutable="`which javac`" 52 | [ -z "$javaExecutable" -o "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ] && die "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME." 53 | # readlink(1) is not available as standard on Solaris 10. 54 | readLink=`which readlink` 55 | [ `expr "$readLink" : '\([^ ]*\)'` = "no" ] && die "JAVA_HOME not set and readlink not available, please set JAVA_HOME." 56 | javaExecutable="`readlink -f \"$javaExecutable\"`" 57 | javaHome="`dirname \"$javaExecutable\"`" 58 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 59 | export JAVA_HOME="$javaHome" 60 | fi 61 | fi 62 | 63 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 64 | if $cygwin ; then 65 | [ -n "$JAVACMD" ] && JAVACMD=`cygpath --unix "$JAVACMD"` 66 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 67 | fi 68 | 69 | STARTER_MAIN_CLASS=org.gradle.wrapper.GradleWrapperMain 70 | CLASSPATH=`dirname "$0"`/gradle/wrapper/gradle-wrapper.jar 71 | WRAPPER_PROPERTIES=`dirname "$0"`/gradle/wrapper/gradle-wrapper.properties 72 | # Determine the Java command to use to start the JVM. 73 | if [ -z "$JAVACMD" ] ; then 74 | if [ -n "$JAVA_HOME" ] ; then 75 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 76 | # IBM's JDK on AIX uses strange locations for the executables 77 | JAVACMD="$JAVA_HOME/jre/sh/java" 78 | else 79 | JAVACMD="$JAVA_HOME/bin/java" 80 | fi 81 | else 82 | JAVACMD="java" 83 | fi 84 | fi 85 | if [ ! -x "$JAVACMD" ] ; then 86 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | if [ -z "$JAVA_HOME" ] ; then 92 | warn "JAVA_HOME environment variable is not set" 93 | fi 94 | 95 | # Increase the maximum file descriptors if we can. 96 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 97 | MAX_FD_LIMIT=`ulimit -H -n` 98 | if [ $? -eq 0 ] ; then 99 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 100 | MAX_FD="$MAX_FD_LIMIT" 101 | fi 102 | ulimit -n $MAX_FD 103 | if [ $? -ne 0 ] ; then 104 | warn "Could not set maximum file descriptor limit: $MAX_FD" 105 | fi 106 | else 107 | warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT" 108 | fi 109 | fi 110 | 111 | # For Darwin, add GRADLE_APP_NAME to the JAVA_OPTS as -Xdock:name 112 | if $darwin; then 113 | JAVA_OPTS="$JAVA_OPTS -Xdock:name=$GRADLE_APP_NAME" 114 | # we may also want to set -Xdock:image 115 | fi 116 | 117 | # For Cygwin, switch paths to Windows format before running java 118 | if $cygwin ; then 119 | JAVA_HOME=`cygpath --path --mixed "$JAVA_HOME"` 120 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 121 | 122 | # We build the pattern for arguments to be converted via cygpath 123 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 124 | SEP="" 125 | for dir in $ROOTDIRSRAW ; do 126 | ROOTDIRS="$ROOTDIRS$SEP$dir" 127 | SEP="|" 128 | done 129 | OURCYGPATTERN="(^($ROOTDIRS))" 130 | # Add a user-defined pattern to the cygpath arguments 131 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 132 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 133 | fi 134 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 135 | i=0 136 | for arg in "$@" ; do 137 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 138 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 139 | 140 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 141 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 142 | else 143 | eval `echo args$i`="\"$arg\"" 144 | fi 145 | i=$((i+1)) 146 | done 147 | case $i in 148 | (0) set -- ;; 149 | (1) set -- "$args0" ;; 150 | (2) set -- "$args0" "$args1" ;; 151 | (3) set -- "$args0" "$args1" "$args2" ;; 152 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 153 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 154 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 155 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 156 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 157 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 158 | esac 159 | fi 160 | 161 | GRADLE_APP_BASE_NAME=`basename "$0"` 162 | 163 | exec "$JAVACMD" $JAVA_OPTS $GRADLE_OPTS \ 164 | -classpath "$CLASSPATH" \ 165 | -Dorg.gradle.appname="$GRADLE_APP_BASE_NAME" \ 166 | -Dorg.gradle.wrapper.properties="$WRAPPER_PROPERTIES" \ 167 | $STARTER_MAIN_CLASS \ 168 | "$@" 169 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/config/BatchConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.config; 17 | 18 | import com.springsource.html5expense.integrations.EligibleChargeProcessor; 19 | import com.springsource.html5expense.integrations.EligibleChargeProcessorHeaders; 20 | import org.springframework.batch.core.Job; 21 | import org.springframework.batch.core.JobParameters; 22 | import org.springframework.batch.core.JobParametersBuilder; 23 | import org.springframework.batch.core.launch.JobLauncher; 24 | import org.springframework.batch.core.launch.support.SimpleJobLauncher; 25 | import org.springframework.batch.core.repository.JobRepository; 26 | import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; 27 | import org.springframework.batch.item.ItemWriter; 28 | import org.springframework.batch.item.file.FlatFileItemReader; 29 | import org.springframework.batch.item.file.mapping.DefaultLineMapper; 30 | import org.springframework.batch.item.file.mapping.PassThroughFieldSetMapper; 31 | import org.springframework.batch.item.file.transform.DefaultFieldSet; 32 | import org.springframework.batch.item.file.transform.DelimitedLineTokenizer; 33 | import org.springframework.batch.item.file.transform.FieldSet; 34 | import org.springframework.beans.factory.annotation.Autowired; 35 | import org.springframework.beans.factory.annotation.Qualifier; 36 | import org.springframework.beans.factory.annotation.Value; 37 | import org.springframework.context.annotation.*; 38 | import org.springframework.core.io.FileSystemResource; 39 | import org.springframework.integration.Message; 40 | import org.springframework.integration.MessageChannel; 41 | import org.springframework.integration.support.MessageBuilder; 42 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 43 | 44 | import javax.inject.Inject; 45 | import java.io.File; 46 | import java.math.BigDecimal; 47 | import java.util.Date; 48 | import java.util.List; 49 | 50 | /** 51 | * @author Josh Long 52 | */ 53 | @Configuration 54 | @Import({DataSourceConfig.class, ComponentConfig.class}) 55 | @ImportResource("/ec-loader.xml") 56 | public class BatchConfig { 57 | 58 | @Inject 59 | private DataSourceConfig dataSourceConfig; 60 | 61 | @Inject 62 | private ComponentConfig componentConfig; 63 | 64 | @Autowired 65 | @Qualifier("newEligibleCharges") 66 | private MessageChannel channel; 67 | 68 | private File batchFileDirectory; 69 | 70 | @Autowired 71 | public void setBatchFileDirectory(@Value("#{ systemProperties['user.home'] }") String userHome) throws Exception { 72 | batchFileDirectory = new File(userHome, "in"); 73 | if (!batchFileDirectory.exists()) 74 | batchFileDirectory.mkdirs(); 75 | 76 | } 77 | 78 | @Bean 79 | public SimpleJobLauncher jobLauncher() throws Exception { 80 | SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); 81 | jobLauncher.setJobRepository((JobRepository) this.jobRepository().getObject()); 82 | return jobLauncher; 83 | } 84 | 85 | @Bean 86 | public EligibleChargeProcessor eligibleChargeProcessor() { 87 | return new EligibleChargeProcessor(); 88 | } 89 | 90 | @Bean 91 | @Scope("step") 92 | public FlatFileItemReader reader(@Value("#{jobParameters[file]}") String resource) { 93 | 94 | File f = new File(this.batchFileDirectory, resource + ".csv"); 95 | 96 | DelimitedLineTokenizer del = new DelimitedLineTokenizer(); 97 | del.setNames("date,amount,category,merchant".split(",")); 98 | del.setDelimiter(DelimitedLineTokenizer.DELIMITER_COMMA); 99 | 100 | DefaultLineMapper
defaultLineMapper = new DefaultLineMapper
(); 101 | defaultLineMapper.setLineTokenizer(del); 102 | defaultLineMapper.setFieldSetMapper(new PassThroughFieldSetMapper()); 103 | defaultLineMapper.afterPropertiesSet(); 104 | 105 | FlatFileItemReader
fileItemReader = new FlatFileItemReader
(); 106 | fileItemReader.setLineMapper(defaultLineMapper); 107 | fileItemReader.setResource(new FileSystemResource(f)); 108 | 109 | return fileItemReader; 110 | } 111 | 112 | @Bean 113 | public ItemWriter writer() { 114 | return new MessageSendingItemWriter(this.channel); 115 | } 116 | 117 | @Bean 118 | public JobRepositoryFactoryBean jobRepository() throws Exception { 119 | JobRepositoryFactoryBean bean = new JobRepositoryFactoryBean(); 120 | bean.setTransactionManager(new DataSourceTransactionManager(dataSourceConfig.dataSource())); 121 | bean.setDataSource(dataSourceConfig.dataSource()); 122 | return bean; 123 | } 124 | 125 | public static class MessageSendingItemWriter implements ItemWriter { 126 | 127 | private MessageChannel channel; 128 | 129 | public MessageSendingItemWriter(MessageChannel channel) { 130 | this.channel = channel; 131 | } 132 | 133 | @Override 134 | public void write(List defaultFieldSets) throws Exception { 135 | for (DefaultFieldSet defaultFieldSet : defaultFieldSets) { 136 | Date date = defaultFieldSet.readDate(0); 137 | BigDecimal bigDecimal = defaultFieldSet.readBigDecimal(1); 138 | String category = defaultFieldSet.readString(2); 139 | String merchant = defaultFieldSet.readString(3); 140 | 141 | Message msg = MessageBuilder.withPayload(category) 142 | .setHeader(EligibleChargeProcessorHeaders.EC_AMOUNT, bigDecimal) 143 | .setHeader(EligibleChargeProcessorHeaders.EC_CATEGORY, category) 144 | .setHeader(EligibleChargeProcessorHeaders.EC_MERCHANT, merchant) 145 | .setHeader(EligibleChargeProcessorHeaders.EC_DATE, date) 146 | .build(); 147 | this.channel.send(msg); 148 | 149 | } 150 | } 151 | } 152 | 153 | 154 | public static void main(String args[]) throws Exception { 155 | 156 | AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BatchConfig.class); 157 | 158 | Job job = annotationConfigApplicationContext.getBean("read-eligible-charges", Job.class); 159 | 160 | JobParametersBuilder builder = new JobParametersBuilder(); 161 | builder.addString("file", "foo"); 162 | builder.addLong("uid", System.currentTimeMillis()); 163 | JobParameters jobParameters = builder.toJobParameters(); 164 | 165 | JobLauncher jobLauncher = annotationConfigApplicationContext.getBean(JobLauncher.class); 166 | jobLauncher.run(job, jobParameters); 167 | 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /server/api/src/main/java/com/springsource/html5expense/controllers/ExpenseReportingApiController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.springsource.html5expense.controllers; 17 | 18 | import com.springsource.html5expense.EligibleCharge; 19 | import com.springsource.html5expense.Expense; 20 | import com.springsource.html5expense.ExpenseReport; 21 | import com.springsource.html5expense.ExpenseReportingService; 22 | import org.apache.commons.io.IOUtils; 23 | import org.apache.commons.logging.Log; 24 | import org.apache.commons.logging.LogFactory; 25 | import org.springframework.http.HttpStatus; 26 | import org.springframework.stereotype.Controller; 27 | import org.springframework.web.bind.annotation.*; 28 | import org.springframework.web.multipart.MultipartFile; 29 | 30 | import javax.inject.Inject; 31 | import javax.servlet.http.HttpServletRequest; 32 | import javax.servlet.http.HttpServletResponse; 33 | import java.io.InputStream; 34 | import java.io.OutputStream; 35 | import java.util.Arrays; 36 | import java.util.Collection; 37 | import java.util.List; 38 | 39 | /** 40 | * @author Roy Clarkson 41 | * @author Josh Long 42 | */ 43 | @Controller 44 | @RequestMapping("/reports") 45 | public class ExpenseReportingApiController { 46 | 47 | private Log log = LogFactory.getLog(getClass()); 48 | 49 | @Inject 50 | private ExpenseReportingService service; 51 | 52 | @ResponseStatus(HttpStatus.OK) 53 | @RequestMapping(method = RequestMethod.DELETE, value = "/expenses/{expenseId}") 54 | public void restoreExpenseToEligibleCharge(@PathVariable("expenseId") Integer expenseId) { 55 | Expense ex = service.getExpense(expenseId); 56 | service.restoreEligibleCharges(Arrays.asList(ex.getId())); 57 | } 58 | 59 | 60 | @RequestMapping(method = RequestMethod.GET, value = "/{reportId}/expenses", produces = "application/json") 61 | @ResponseBody 62 | public Collection expenseForExpenseReport(HttpServletRequest request, @PathVariable("reportId") Long reportId) { 63 | return this.service.getExpensesForExpenseReport(reportId); 64 | } 65 | 66 | /** 67 | * Create a new {@link com.springsource.html5expense.ExpenseReport} with an associated description for the purpose 68 | * 69 | * @param purpose the reason for the expense report. i.e. conference, business meal, etc. 70 | * @return the ID of the new expense report 71 | */ 72 | @RequestMapping(method = RequestMethod.POST) 73 | @ResponseBody 74 | public Long createReport(@RequestParam(required = true) String purpose) { 75 | return service.createReport(purpose); 76 | } 77 | 78 | /** 79 | * Retrieve a list of charges that can be associated with an {@link com.springsource.html5expense.ExpenseReport}. 80 | * These charges are not currently associated with any other expense report. 81 | * 82 | * @return collection of {@link com.springsource.html5expense.EligibleCharge} objects 83 | */ 84 | @RequestMapping(value = "/eligible-charges", method = RequestMethod.GET, produces = "application/json") 85 | @ResponseBody 86 | public Collection getEligibleCharges() { 87 | return service.getEligibleCharges(); 88 | } 89 | 90 | private String buildMimeTypeForExpense(Expense e) { 91 | String ext = e.getReceiptExtension(); 92 | String mime = null; 93 | if (ext.equalsIgnoreCase("jpg") || ext.equalsIgnoreCase("jpeg")) { 94 | mime = "image/jpeg"; 95 | } else if (ext.equalsIgnoreCase("gif")) 96 | mime = "image/gif"; 97 | else 98 | mime = "application/binary"; 99 | return mime; 100 | } 101 | 102 | 103 | @RequestMapping(value = "/receipts/{expenseId}") 104 | public void renderMedia(HttpServletResponse httpServletResponse, OutputStream os, @PathVariable("expenseId") Integer expenseId) { 105 | 106 | Expense expense = service.getExpense(expenseId); 107 | httpServletResponse.setContentType(buildMimeTypeForExpense(expense)); 108 | InputStream is = service.retrieveReceipt(expenseId); 109 | try { 110 | IOUtils.copyLarge(is, os); 111 | } catch (Exception e1) { 112 | log.error(e1); 113 | } finally { 114 | IOUtils.closeQuietly(is); 115 | IOUtils.closeQuietly(os); 116 | } 117 | 118 | /* 119 | try { 120 | is = new FileInputStream(f); 121 | httpServletResponse.setContentLength((int) f.length()); 122 | IOUtils.copyLarge(is, os); 123 | } catch (Exception e1) { 124 | log.error(e1); 125 | } finally { 126 | if (is != null) 127 | IOUtils.closeQuietly(is); 128 | if (os != null) 129 | IOUtils.closeQuietly(os); 130 | }*/ 131 | } 132 | 133 | 134 | @RequestMapping(value = "/{reportId}/expenses", method = RequestMethod.POST, produces = "application/json") 135 | @ResponseBody 136 | public Collection createExpenses(@PathVariable Long reportId, @RequestParam(required = true, value = "chargeId") Long chargeId) { 137 | return service.createExpenses(reportId, Arrays.asList(chargeId)); 138 | } 139 | 140 | private String findExtensionFromFileName(String fn) { 141 | int lPosOfPeriod = fn.lastIndexOf("."); 142 | if (lPosOfPeriod != -1 && !fn.endsWith(".")) { 143 | return fn.substring(lPosOfPeriod + 1); 144 | } 145 | return null; 146 | } 147 | 148 | @RequestMapping(value = "/receipts", method = RequestMethod.POST) 149 | @ResponseBody 150 | public String attachReceipt(@RequestParam("reportId") Long reportId, @RequestParam("expenseId") Integer expenseId, @RequestParam("file") MultipartFile file) { 151 | try { 152 | byte[] bytesForImage = file.getBytes(); 153 | String ext = findExtensionFromFileName(file.getOriginalFilename()); 154 | if (ext != null) { 155 | ext = ext.trim().toLowerCase(); 156 | } 157 | return service.attachReceipt(reportId, expenseId, ext, bytesForImage); 158 | } catch (Throwable th) { 159 | if (log.isErrorEnabled()) { 160 | log.error("Something went wrong trying to write the file out.", th); 161 | } 162 | } 163 | return null; 164 | } 165 | 166 | 167 | /** 168 | * Finalizes and submits the {@link com.springsource.html5expense.ExpenseReport} for review 169 | * 170 | * @param reportId the ID of the {@link com.springsource.html5expense.ExpenseReport} 171 | */ 172 | @RequestMapping(value = "/{reportId}", method = RequestMethod.POST) 173 | @ResponseStatus(value = HttpStatus.OK) 174 | public void submitReport(@PathVariable("reportId") Long reportId) { 175 | service.submitReport(reportId); 176 | } 177 | 178 | @RequestMapping(value = "/{reportId}", method = RequestMethod.GET) 179 | @ResponseBody 180 | public ExpenseReport getReport(@PathVariable("reportId") Long reportId) { 181 | return service.getExpenseReport(reportId); 182 | } 183 | 184 | @RequestMapping(value = "/{reportId}", method = RequestMethod.DELETE) 185 | @ResponseStatus(HttpStatus.OK) 186 | public void deleteReport(@PathVariable("reportId") Long reportId) { 187 | this.service.deleteExpenseReport(reportId); 188 | } 189 | 190 | /** 191 | * Retrieves all of the open, or incomplete, expense reports for the user 192 | * 193 | * @return list of {@link com.springsource.html5expense.ExpenseReport} objects 194 | */ 195 | @RequestMapping(method = RequestMethod.GET, produces = "application/json") 196 | @ResponseBody 197 | public List getOpenReports() { 198 | return service.getOpenReports(); 199 | } 200 | 201 | /** 202 | * Retrieves all of the submitted expense reports for the user 203 | * 204 | * @return list of {@link ExpenseReport} objects 205 | */ 206 | @RequestMapping(value = "/submitted", method = RequestMethod.GET, produces = "application/json") 207 | public 208 | @ResponseBody 209 | List getSubmittedReports() { 210 | return service.getSubmittedReports(); 211 | } 212 | 213 | @RequestMapping(method = RequestMethod.POST, value = "/{reportId}/purpose") 214 | @ResponseStatus(value = HttpStatus.OK) 215 | public void updateReportPurpose(@PathVariable("reportId") Long reportId, String title) { 216 | service.updateExpenseReportPurpose(reportId, title); 217 | } 218 | 219 | @RequestMapping(method = RequestMethod.GET, value = "/open-reports") 220 | @ResponseBody 221 | public Collection openReports() { 222 | return service.getOpenReports(); 223 | } 224 | 225 | } 226 | -------------------------------------------------------------------------------- /server/api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | 7 | org.springsource 8 | html5expenses 9 | HTML5 Expenses 10 | 11 | war 12 | 13 | 1.0.0 14 | 15 | 16 | 2.1.6.RELEASE 17 | 1.6 18 | 3.1.0.RELEASE 19 | 0.8.1 20 | 2.1.0.RELEASE 21 | 1.0.0.M5 22 | 23 | 24 | 25 | 26 | 27 | org.mortbay.jetty 28 | servlet-api 29 | 3.0.20100224 30 | 31 | 32 | org.springframework.data 33 | spring-data-mongodb 34 | 1.0.0.RC1 35 | 36 | 37 | postgresql 38 | postgresql 39 | 8.3-603.jdbc3 40 | 41 | 42 | org.thymeleaf 43 | thymeleaf-spring3 44 | 1.1.4 45 | 46 | 47 | org.cloudfoundry 48 | cloudfoundry-runtime 49 | ${cloudfoundry.version} 50 | 51 | 52 | org.springframework 53 | spring-jdbc 54 | 3.1.0.RELEASE 55 | 56 | 57 | org.springframework 58 | spring-expression 59 | 3.1.0.RELEASE 60 | 61 | 62 | org.springframework 63 | spring-beans 64 | 3.1.0.RELEASE 65 | 66 | 67 | org.springframework 68 | spring-jms 69 | 3.1.0.RELEASE 70 | 71 | 72 | 73 | 74 | 75 | 76 | org.springframework.data 77 | spring-data-mongodb 78 | 79 | 80 | postgresql 81 | postgresql 82 | 83 | 84 | org.cloudfoundry 85 | cloudfoundry-runtime 86 | 87 | 88 | org.thymeleaf 89 | thymeleaf-spring3 90 | 91 | 92 | 93 | log4j 94 | log4j 95 | 1.2.9 96 | 97 | 98 | 99 | org.hibernate 100 | hibernate-entitymanager 101 | 3.6.5.Final 102 | 103 | 104 | commons-fileupload 105 | commons-fileupload 106 | 1.2.1 107 | 108 | 109 | commons-lang 110 | commons-lang 111 | 2.5 112 | 113 | 114 | com.h2database 115 | h2 116 | 1.2.144 117 | 118 | 119 | cglib 120 | cglib-nodep 121 | 2.2 122 | 123 | 124 | org.springframework 125 | spring-context 126 | ${org.springframework-version} 127 | 128 | 129 | commons-io 130 | commons-io 131 | 1.3 132 | 133 | 134 | org.springframework 135 | spring-webmvc 136 | ${org.springframework-version} 137 | 138 | 139 | org.springframework 140 | spring-orm 141 | ${org.springframework-version} 142 | 143 | 144 | javax.inject 145 | javax.inject 146 | 1 147 | 148 | 149 | org.codehaus.jackson 150 | jackson-mapper-asl 151 | 1.8.5 152 | 153 | 154 | 155 | org.springframework.batch 156 | spring-batch-core 157 | ${spring.batch.version} 158 | 159 | 160 | 161 | org.springframework.integration 162 | spring-integration-jms 163 | ${spring-integration.version} 164 | 165 | 166 | org.springframework 167 | spring-test 168 | ${org.springframework-version} 169 | 170 | 171 | org.springframework.security.oauth 172 | spring-security-oauth2 173 | ${spring-security-oauth2.version} 174 | 175 | 176 | junit 177 | junit 178 | 4.7 179 | test 180 | 181 | 182 | org.mortbay.jetty 183 | servlet-api 184 | provided 185 | 186 | 187 | 188 | 189 | 190 | org.apache.maven.plugins 191 | maven-compiler-plugin 192 | 2.3.2 193 | 194 | ${java-version} 195 | ${java-version} 196 | 197 | 198 | 199 | org.apache.maven.plugins 200 | maven-war-plugin 201 | 2.1.1 202 | 203 | false 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | milestone 212 | https://maven.springframework.org/milestone 213 | 214 | 215 | release 216 | https://maven.springframework.org/release 217 | 218 | 219 | snapshot 220 | https://maven.springframework.org/snapshot 221 | 222 | 223 | sonatype-nexus-snapshots 224 | Sonatype Nexus Snapshots 225 | https://oss.sonatype.org/content/repositories/snapshots 226 | 227 | true 228 | 229 | 230 | 231 | --------------------------------------------------------------------------------