diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..cb08ff73c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +**/target/ +**/.settings/ +**/.project +**/.classpath +**/.DS_Store +**/*.pyc +**/*.pmd +**/*.fpr +**/sdkdist.zip +**/*.ipr +**/*.iws +**/bin/ +**/*.iml +**/*.idea +**/.cache +**/.cache-main +**/WebContent +**/META-INF/application.xml +**/*.sublime-project +**/*.sublime-workspace +**/pom.xml.versionsBackup +*.sh +**/sdkdist* +**/example* +**/tmprepo +**/scp/ +**/node_modules +**/credentials.json +**/reports +/e2e-tests/*.log +.org.chromium.Chromium.* \ No newline at end of file diff --git a/Costcenter-Controller-CF/CreateReport.js b/Costcenter-Controller-CF/CreateReport.js new file mode 100644 index 0000000000..937410ce59 --- /dev/null +++ b/Costcenter-Controller-CF/CreateReport.js @@ -0,0 +1,12 @@ +const report = require('cucumber-html-report'); +report.create({ + source: 'reports/e2e/cucumber.json', // source json + dest: 'reports/e2e/prod', // target directory (will create if not exists) + name: 'report.html', // report file name (will be index.html if not exists) + title: 'Cucumber Report', // Title for default template. (default is Cucumber Report) + component: 'My Component', // Subtitle for default template. (default is empty) + screenshots: 'reports/e2e/screenshots', // Path to the directory of screenshots. Optional. + maxScreenshots: 10 // Max number of screenshots to save (default is 1000) +}) +.then(console.log) +.catch(console.error); \ No newline at end of file diff --git a/Costcenter-Controller-CF/Jenkinsfile b/Costcenter-Controller-CF/Jenkinsfile new file mode 100644 index 0000000000..57a2a13d7b --- /dev/null +++ b/Costcenter-Controller-CF/Jenkinsfile @@ -0,0 +1,7 @@ +#!/usr/bin/env groovy + +node { + deleteDir() + sh "git clone --depth 1 https://github.com/SAP/cloud-s4-sdk-pipeline.git pipelines" + load './pipelines/s4sdk-pipeline.groovy' +} diff --git a/Costcenter-Controller-CF/README.md b/Costcenter-Controller-CF/README.md new file mode 100644 index 0000000000..cf355cdc05 --- /dev/null +++ b/Costcenter-Controller-CF/README.md @@ -0,0 +1,61 @@ +# CostCenter Controller CF +[![Build Status](http://mo-878278750.mo.sap.corp:8080/buildStatus/icon?job=partner/Costcenter-Controller-CF/master)](http://mo-878278750.mo.sap.corp:8080/job/partner/job/Costcenter-Controller-CF/job/master/) + +## Build +```shell +mvn clean compile +``` + +## Test +```shell +mvn test +``` + +### Start Local Server +```shell +mvn spring-boot:run +``` + +## Run E2E Tests against Local Server +```shell +npm install +ENV_LAUNCH_URL=http://localhost:8080 npm run ci-e2e +``` + +## Shutdown local Server +```shell +curl -X POST localhost:8080/shutdown +``` + +## Deploy to CF +```shell +cf api https://api.cf.eu10.hana.ondemand.com +cf login +mvn clean package && cf push +``` + +## E2E Tests +Configuration reads hosts from environment, so set URL like this: +```shell +export ENV_LAUNCH_URL=https://costcenter-cf-spring-test.cfapps.eu10.hana.ondemand.com +``` + +- Run Tests +```shell +npm install +npm run ci-e2e +``` + +## Edit manifest.yml + +- Host +- Endpoints + +``` +https://costcenter-cf-spring-.cfapps.sap.hana.ondemand.com/index.html +``` + +## Useful CF Commands +```shell +cf logs costcenter-cf-spring --recent +``` diff --git a/Costcenter-Controller-CF/application.properties b/Costcenter-Controller-CF/application.properties new file mode 100644 index 0000000000..52914fb78d --- /dev/null +++ b/Costcenter-Controller-CF/application.properties @@ -0,0 +1,5 @@ +#No auth protected +endpoints.shutdown.sensitive=false + +#Enable shutdown endpoint +endpoints.shutdown.enabled=true \ No newline at end of file diff --git a/Costcenter-Controller-CF/application/pom.xml b/Costcenter-Controller-CF/application/pom.xml new file mode 100644 index 0000000000..b6ad919ca2 --- /dev/null +++ b/Costcenter-Controller-CF/application/pom.xml @@ -0,0 +1,83 @@ + + + + 4.0.0 + + Cost Center Controller - Application + costcenter-cf-spring - Application + + costcenter-cf-spring-application + 1.0-SNAPSHOT + + + com.sap.cloud.sdk.showcase + costcenter-cf-spring + 1.0-SNAPSHOT + + + + + + com.sap.cloud.s4hana.cloudplatform + scp-cf + + + com.sap.cloud.s4hana + s4hana-all + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-actuator + + + + org.projectlombok + lombok + provided + + + + + org.slf4j + slf4j-api + compile + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + 1.5.2.RELEASE + + false + + + + + repackage + + + + + + + diff --git a/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/Application.java b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/Application.java new file mode 100644 index 0000000000..f0d874c128 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/Application.java @@ -0,0 +1,23 @@ +package com.sap.cloud.sdk.tutorial; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.boot.web.support.SpringBootServletInitializer; + +@SpringBootApplication +@ServletComponentScan( "com.sap.cloud.sdk" ) +public class Application extends SpringBootServletInitializer +{ + @Override + protected SpringApplicationBuilder configure( final SpringApplicationBuilder application ) + { + return application.sources(Application.class); + } + + public static void main( final String[] args ) + { + SpringApplication.run(Application.class, args); + } +} diff --git a/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/command/CreateCostCenterCommand.java b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/command/CreateCostCenterCommand.java new file mode 100644 index 0000000000..450bc85a38 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/command/CreateCostCenterCommand.java @@ -0,0 +1,70 @@ +package com.sap.cloud.sdk.tutorial.command; + +import com.netflix.config.ConfigurationManager; + +import java.util.List; + +import com.sap.cloud.sdk.s4hana.config.S4HanaConfig; +import com.sap.cloud.sdk.s4hana.connectivity.ErpCommand; +import com.sap.cloud.sdk.s4hana.connectivity.ErpConfigContext; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.services.CostCenterService; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.structures.CostCenterCreateInput; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.structures.ReturnParameter; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.types.CostCenter; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.types.CostCenterManager; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.types.CurrencyKey; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.types.IndicatorForCostCenterType; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.types.SetId; +import com.sap.cloud.sdk.s4hana.serialization.ErpBoolean; +import com.sap.cloud.sdk.tutorial.models.CostCenterDetails; + +import static com.sap.cloud.sdk.s4hana.config.S4HanaConfig.BAPI_SERIALIZATION_STRATEGY; + +public class CreateCostCenterCommand extends ErpCommand> +{ + private final CostCenterDetails details; + private final boolean testRun; + + public CreateCostCenterCommand( final ErpConfigContext configContext, final CostCenterDetails costCenterDetails, final boolean isTestRun) + { + super(CreateCostCenterCommand.class, configContext); + this.details = costCenterDetails; + this.testRun = isTestRun; + + // for now use JSON + ConfigurationManager.getConfigInstance().setProperty( + BAPI_SERIALIZATION_STRATEGY, + S4HanaConfig.RemoteFunctionSerializationStrategy.JSON); + } + + @Override + protected List run() + throws Exception + { + final List returnValues = + CostCenterService + .createMultiple( + // Required parameter: ControllingArea + details.getControllingArea(), + // Required parameter: CostCenter Input + CostCenterCreateInput + .builder() + .validFrom(CostCenterDetails.asLocalDate(details.getValidFrom())) + .validTo(CostCenterDetails.asLocalDate(details.getValidTo())) + .costcenter(new CostCenter(details.getId().getValue())) + .name("SAP dummy") + .currency(CurrencyKey.of("EUR")) + .descript(details.getDescription()) + .costcenterType(IndicatorForCostCenterType.of(details.getCategory())) + .personInCharge(CostCenterManager.of(details.getPersonResponsible())) + .costctrHierGrp(SetId.of(details.getCostCenterGroup())) + .compCode(details.getCompanyCode()) + .profitCtr(details.getProfitCenter()) + .build()) + .testRun(new ErpBoolean(testRun)) + .execute(getConfigContext()) + .getMessages(); + + return returnValues; + } +} diff --git a/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/command/GetCostCenterCommand.java b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/command/GetCostCenterCommand.java new file mode 100644 index 0000000000..32128aca26 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/command/GetCostCenterCommand.java @@ -0,0 +1,64 @@ +package com.sap.cloud.sdk.tutorial.command; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import org.slf4j.Logger; + +import java.util.List; + +import com.sap.cloud.sdk.cloudplatform.cache.CacheKey; +import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory; +import com.sap.cloud.sdk.odatav2.connectivity.ODataQuery; +import com.sap.cloud.sdk.odatav2.connectivity.ODataQueryBuilder; +import com.sap.cloud.sdk.odatav2.connectivity.ODataQueryResult; +import com.sap.cloud.sdk.s4hana.connectivity.CachingErpCommand; +import com.sap.cloud.sdk.s4hana.connectivity.ErpConfigContext; +import com.sap.cloud.sdk.s4hana.connectivity.exception.QueryExecutionException; +import com.sap.cloud.sdk.tutorial.models.CostCenterDetails; + + +public class GetCostCenterCommand extends CachingErpCommand> { + private static final Logger logger = CloudLoggerFactory.getLogger(GetCostCenterCommand.class); + + public GetCostCenterCommand(final ErpConfigContext configContext) { + super(GetCostCenterCommand.class, configContext); + } + + private static final Cache> cache = + CacheBuilder.newBuilder().concurrencyLevel(10).build(); + + @Override + protected Cache> getCache() { + return cache; + } + + @Override + protected CacheKey getCommandCacheKey() { + return super.getCommandCacheKey().append(getConfigContext()); + } + + @Override + protected List runCacheable() throws QueryExecutionException { + try { + final ODataQuery query = ODataQueryBuilder + .withEntity("/sap/opu/odata/sap/FCO_PI_COST_CENTER", "CostCenterCollection") + .select("CostCenterID", + "CostCenterDescription", + "Status", + "CompanyCode", + "Category", + "ValidityStartDate", + "ValidityEndDate") + .build(); + + final ODataQueryResult result = query.execute(getErpEndpoint()); + return result.asList(CostCenterDetails.class); + } catch (final Exception e) { + throw new QueryExecutionException("Failed to get CostCenters from OData by using cached command.", e); + } + } + + public void resetCache() { + cache.invalidate(getCommandCacheKey()); + } +} diff --git a/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/command/HealthCheckCommand.java b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/command/HealthCheckCommand.java new file mode 100644 index 0000000000..82f1172d83 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/command/HealthCheckCommand.java @@ -0,0 +1,47 @@ +package com.sap.cloud.sdk.tutorial.command; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import java.util.concurrent.TimeUnit; + +import com.sap.cloud.sdk.cloudplatform.cache.CacheKey; +import com.sap.cloud.sdk.odatav2.connectivity.ODataQueryBuilder; +import com.sap.cloud.sdk.s4hana.connectivity.CachingErpCommand; +import com.sap.cloud.sdk.s4hana.connectivity.ErpConfigContext; +import com.sap.cloud.sdk.tutorial.models.CostCenterDetails; + +public class HealthCheckCommand extends CachingErpCommand +{ + private static final Cache cache = + CacheBuilder.newBuilder() + .concurrencyLevel(10) + .expireAfterAccess(1, TimeUnit.MINUTES) + .build(); + + public HealthCheckCommand( final ErpConfigContext configContext ) { + super(HealthCheckCommand.class, configContext); + } + + @Override + protected Cache getCache() { + return cache; + } + + @Override + protected Boolean runCacheable() throws Exception { + return !ODataQueryBuilder + .withEntity("/sap/opu/odata/sap/FCO_PI_COST_CENTER", "CostCenterCollection") + .select("CostCenterID") + .top(1) + .build() + .execute(getConfigContext()) + .asList(CostCenterDetails.class) + .isEmpty(); + } + + @Override + protected Boolean getFallback() { + return false; + } +} diff --git a/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/controllers/CostCenterController.java b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/controllers/CostCenterController.java new file mode 100644 index 0000000000..c4bbf0e820 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/controllers/CostCenterController.java @@ -0,0 +1,73 @@ +package com.sap.cloud.sdk.tutorial.controllers; + +import org.slf4j.Logger; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Locale; + +import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory; +import com.sap.cloud.sdk.s4hana.connectivity.ErpConfigContext; +import com.sap.cloud.sdk.s4hana.connectivity.ErpDestination; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.structures.ReturnParameter; +import com.sap.cloud.sdk.s4hana.serialization.SapClient; +import com.sap.cloud.sdk.tutorial.command.CreateCostCenterCommand; +import com.sap.cloud.sdk.tutorial.command.GetCostCenterCommand; +import com.sap.cloud.sdk.tutorial.models.CostCenterDetails; + +@RestController +public class CostCenterController +{ + private static final Logger logger = CloudLoggerFactory.getLogger(CostCenterController.class); + + private ErpConfigContext getErpConfigContext( final String sapClient ){ + final ErpConfigContext config = new ErpConfigContext( + ErpDestination.getDefaultName(), + new SapClient(sapClient), + Locale.ENGLISH); + return config; + } + + @RequestMapping( value = "api/v1/rest/client/{sapClient:[\\d]+}/costcenters", method = RequestMethod.GET ) + public ResponseEntity> getCostCenter( + @PathVariable final String sapClient ) + { + try { + final GetCostCenterCommand getCostCenterCommand = new GetCostCenterCommand(getErpConfigContext(sapClient)); + final List costCenterDetails = getCostCenterCommand.execute(); + + return ResponseEntity.ok(costCenterDetails); + } + catch( final Exception e ) { + logger.error(e.getMessage(), e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + @RequestMapping( value = "api/v1/rest/client/{sapClient:[\\d]+}/costcenters", method = RequestMethod.POST ) + public ResponseEntity> postCostCenter( + @PathVariable final String sapClient, + @RequestBody final CostCenterDetails details, + @RequestParam(defaultValue = "false") final boolean testRun ) + { + try { + final ErpConfigContext context = getErpConfigContext(sapClient); + final List result = new CreateCostCenterCommand(context, details, testRun).execute(); + + // before returning, reset cache for future get-command execution + new GetCostCenterCommand(context).resetCache(); + return ResponseEntity.ok(result); + } + catch( final Exception e ) { + logger.error(e.getMessage(), e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } +} diff --git a/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/controllers/ODataHealthIndicator.java b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/controllers/ODataHealthIndicator.java new file mode 100644 index 0000000000..2473f82af7 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/controllers/ODataHealthIndicator.java @@ -0,0 +1,48 @@ +package com.sap.cloud.sdk.tutorial.controllers; + +import org.slf4j.Logger; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +import java.util.Arrays; +import java.util.Locale; + +import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory; +import com.sap.cloud.sdk.s4hana.connectivity.ErpConfigContext; +import com.sap.cloud.sdk.s4hana.connectivity.ErpDestination; +import com.sap.cloud.sdk.s4hana.serialization.SapClient; +import com.sap.cloud.sdk.tutorial.command.HealthCheckCommand; + +@Component +public class ODataHealthIndicator implements HealthIndicator { + private static final Logger logger = CloudLoggerFactory.getLogger(ODataHealthIndicator.class); + + @Override + public Health health() { + final SapClient sapClient = new SapClient("SAPCLIENT-NUMBER"); // adjust SAP client to your respective S/4HANA system + final String problem = checkForProblem(sapClient); + if (problem != null) { + return Health.down().withDetail("Error", problem).build(); + } + return Health.up().build(); + } + + private String checkForProblem(final String sapClient) { + try { + final ErpConfigContext config = new ErpConfigContext( + ErpDestination.getDefaultName(), + new SapClient(sapClient), + Locale.ENGLISH); + + final HealthCheckCommand healthCheckCommand = new HealthCheckCommand(config); + Assert.isTrue(healthCheckCommand.execute(), "Empty OData result."); + return null; + } + catch( final Exception e ) { + logger.error("Could not complete health check for OData Service.", e); + return e.getMessage() + Arrays.toString(e.getStackTrace()); + } + } +} diff --git a/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/models/BapiResultMessage.java b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/models/BapiResultMessage.java new file mode 100644 index 0000000000..5074f79bbc --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/models/BapiResultMessage.java @@ -0,0 +1,39 @@ +package com.sap.cloud.sdk.tutorial.models; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +import com.sap.cloud.sdk.s4hana.connectivity.rfc.BapiQueryResult; +import com.sap.cloud.sdk.s4hana.serialization.RemoteFunctionMessage; + +@Data +public class BapiResultMessage { + + public enum BapiResultMessageType { SUCCESS, INFORMATION, ERROR, WARNING } + + private String text; + private String clasz; + private String number; + private BapiResultMessageType type; + + + public static List createMesages(final BapiQueryResult costCenterDetails) { + final List messages = new ArrayList<>(); + messages.addAll(createMesages(costCenterDetails.getSuccessMessages(), BapiResultMessageType.SUCCESS)); + return messages; + } + + private static List createMesages(final List remoteMessages, final BapiResultMessageType type) { + final List messages = new ArrayList<>(); + for(final RemoteFunctionMessage remoteMessage : remoteMessages) { + final BapiResultMessage message = new BapiResultMessage(); + message.setClasz(remoteMessage.getMessageClass().getValue()); + message.setText(remoteMessage.getMessageText()); + message.setNumber(remoteMessage.getMessageNumber().getValue()); + messages.add(message); + } + return messages; + } +} diff --git a/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/models/CostCenterDetails.java b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/models/CostCenterDetails.java new file mode 100644 index 0000000000..9583543ce7 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/models/CostCenterDetails.java @@ -0,0 +1,71 @@ +package com.sap.cloud.sdk.tutorial.models; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.slf4j.Logger; + +import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory; +import com.sap.cloud.sdk.result.ElementName; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.types.CompanyCode; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.types.ControllingArea; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.types.CostCenter; +import com.sap.cloud.sdk.s4hana.datamodel.bapi.types.ProfitCenter; + +@Data +@Accessors(chain = true) +public class CostCenterDetails +{ + private static final Logger logger = CloudLoggerFactory.getLogger(CostCenterDetails.class); + private static final DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd"); + + + /* + * for listing + */ + @ElementName("CostCenterID") + private CostCenter id; + + @ElementName("CostCenterDescription") + private String description; + + @ElementName("Status") + private String status; + + /* + * for deleting + */ + @ElementName("ValidityStartDate") + private String validFrom; + + @ElementName("ValidityEndDate") + private String validTo; + + /* + * for creating + */ + @ElementName("CompanyCode") + private CompanyCode companyCode; + + @ElementName("Category") + private String category; + + @ElementName("ControllingArea") + private ControllingArea controllingArea; + + private String personResponsible; + + private String costCenterGroup; + + private ProfitCenter profitCenter; + + public static String asDateString(final String input) { + return "/Date(" + dtf.parseDateTime(input).getMillis() + ")/"; + } + + public static LocalDate asLocalDate(final String edmDate) { + return new LocalDate(Long.valueOf(edmDate.substring(6, edmDate.length() - 2))); + } +} diff --git a/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/servlets/HelloWorldServlet.java b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/servlets/HelloWorldServlet.java new file mode 100644 index 0000000000..ec3a02e6b8 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/java/com/sap/cloud/sdk/tutorial/servlets/HelloWorldServlet.java @@ -0,0 +1,28 @@ +package com.sap.cloud.sdk.tutorial.servlets; + +import org.slf4j.Logger; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory; + +@WebServlet( "/hello" ) +public class HelloWorldServlet extends HttpServlet +{ + private static final long serialVersionUID = 3203196792634761929L; + private static final Logger logger = CloudLoggerFactory.getLogger(HelloWorldServlet.class); + + @Override + protected void doGet(final HttpServletRequest request, final HttpServletResponse response ) + throws ServletException, + IOException + { + logger.info("I am running!"); + response.getWriter().write("Hello World!"); + } +} diff --git a/Costcenter-Controller-CF/application/src/main/resources/application.yml b/Costcenter-Controller-CF/application/src/main/resources/application.yml new file mode 100644 index 0000000000..22c78e959f --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/resources/application.yml @@ -0,0 +1,8 @@ +logging: + level: + com.sap.cloud.sdk.tutorial: DEBUG + com.sap.cloud.sdk: INFO + root: WARN + +server: + port: 8080 diff --git a/Costcenter-Controller-CF/application/src/main/resources/static/Component.js b/Costcenter-Controller-CF/application/src/main/resources/static/Component.js new file mode 100644 index 0000000000..60831c3959 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/resources/static/Component.js @@ -0,0 +1,27 @@ +sap.ui.define([ + "sap/ui/core/UIComponent", + "sap/ui/Device", + "costcenter_app/model/models" +], function (UIComponent, Device, models) { + "use strict"; + + return UIComponent.extend("costcenter_app.Component", { + + metadata: { + manifest: "json" + }, + + /** + * The component is initialized by UI5 automatically during the startup of the app and calls the init method once. + * @public + * @override + */ + init: function () { + // call the base component's init function + UIComponent.prototype.init.apply(this, arguments); + + // set the device model + this.setModel(models.createDeviceModel(), "device"); + } + }); +}); \ No newline at end of file diff --git a/Costcenter-Controller-CF/application/src/main/resources/static/controller/costcenters.controller.js b/Costcenter-Controller-CF/application/src/main/resources/static/controller/costcenters.controller.js new file mode 100644 index 0000000000..460fd6c8f2 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/resources/static/controller/costcenters.controller.js @@ -0,0 +1,46 @@ +sap.ui.define([ + "sap/ui/core/mvc/Controller", + "sap/ui/model/json/JSONModel", + "sap/m/MessageToast", + "sap/ui/commons/MessageBox", + "costcenter_app/service/costcenters" +], function (Controller, JSONModel, MessageToast, MessageBox, CostCenterService) { + "use strict"; + + return Controller.extend("costcenter_app.controller.costcenters", { + + + createCostCenter: function () { + var that = this; + + var id = this.getView().byId("ccID").getValue(); + var description = this.getView().byId("ccLT").getValue(); + + CostCenterService.createCostCenter(id, description, "715").then(function (costCenters) { + that.loadCostCenters(); + }) + .fail(function () { + MessageToast.show("Creating Costcenter failed!"); + }); + }, + + loadCostCenters: function () { + var that = this; + CostCenterService.getCostCenters("715").then(function (costCenters) { + costCenters = costCenters + .filter(function (a) { + return a.status != "DELETED"; + }) + ; + var model = new JSONModel(costCenters); + that.getView().setModel(model, "costCenter"); + }).fail(function () { + MessageToast.show("Loading Costcenters failed!"); + }); + }, + + onInit: function () { + this.loadCostCenters(); + }, + }); +}); diff --git a/Costcenter-Controller-CF/application/src/main/resources/static/css/style.css b/Costcenter-Controller-CF/application/src/main/resources/static/css/style.css new file mode 100644 index 0000000000..52debcd91e --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/resources/static/css/style.css @@ -0,0 +1,5 @@ +/* Enter your custom styles here */ +tr > *:nth-last-child(2) { + width: 70px; + text-align: center !important; +} \ No newline at end of file diff --git a/Costcenter-Controller-CF/application/src/main/resources/static/i18n/i18n.properties b/Costcenter-Controller-CF/application/src/main/resources/static/i18n/i18n.properties new file mode 100644 index 0000000000..96e0eb8b73 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/resources/static/i18n/i18n.properties @@ -0,0 +1,3 @@ +title=Cost Center Controller +appTitle = Cost Center Controller +appDescription=Showcase Example for S/4HANA Cloud SDK + Spring + OData + BAPI + Maven \ No newline at end of file diff --git a/Costcenter-Controller-CF/application/src/main/resources/static/index.html b/Costcenter-Controller-CF/application/src/main/resources/static/index.html new file mode 100644 index 0000000000..7e1aedd5cd --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/resources/static/index.html @@ -0,0 +1,35 @@ + + + + + + + + costcenter_app + + + + + + + + + + + + diff --git a/Costcenter-Controller-CF/application/src/main/resources/static/manifest.json b/Costcenter-Controller-CF/application/src/main/resources/static/manifest.json new file mode 100644 index 0000000000..a56bb68c59 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/resources/static/manifest.json @@ -0,0 +1,75 @@ +{ + "_version": "1.5.0", + "sap.app": { + "id": "costcenter_app", + "type": "application", + "i18n": "i18n/i18n.properties", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "sourceTemplate": { + "id": "ui5template.basicSAPUI5ApplicationProject", + "version": "1.40.12" + } + }, + + "sap.ui": { + "technology": "UI5", + "icons": { + "icon": "", + "favIcon": "", + "phone": "", + "phone@2": "", + "tablet": "", + "tablet@2": "" + }, + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + }, + "supportedThemes": [ + "sap_hcb", + "sap_belize" + + ] + }, + + "sap.ui5": { + "rootView": { + "viewName": "costcenter_app.view.costcenters", + "type": "XML" + }, + "dependencies": { + "minUI5Version": "1.30.0", + "libs": { + "sap.ui.core": {}, + "sap.m": {}, + "sap.ui.layout": {}, + "sap.ushell": {}, + "sap.collaboration": {}, + "sap.ui.comp": {}, + "sap.uxap": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "costcenter_app.i18n.i18n" + } + } + }, + "resources": { + "css": [{ + "uri": "css/style.css" + }] + } + } +} \ No newline at end of file diff --git a/Costcenter-Controller-CF/application/src/main/resources/static/model/models.js b/Costcenter-Controller-CF/application/src/main/resources/static/model/models.js new file mode 100644 index 0000000000..216fce0765 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/resources/static/model/models.js @@ -0,0 +1,15 @@ +sap.ui.define([ + "sap/ui/model/json/JSONModel", + "sap/ui/Device" +], function (JSONModel, Device) { + "use strict"; + + return { + + createDeviceModel: function () { + var oModel = new JSONModel(Device); + oModel.setDefaultBindingMode("OneWay"); + return oModel; + } + }; +}); \ No newline at end of file diff --git a/Costcenter-Controller-CF/application/src/main/resources/static/service/costcenters.js b/Costcenter-Controller-CF/application/src/main/resources/static/service/costcenters.js new file mode 100644 index 0000000000..7a3e674997 --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/resources/static/service/costcenters.js @@ -0,0 +1,38 @@ +sap.ui.define([], function () { + "use strict"; + + return { + getCostCenters: function (sapClient) { + // Call API to retrieve list of cost centers for sap client 715 + return jQuery.get("/api/v1/rest/client/" + sapClient + "/costcenters") + .then(function (data) { + return data; + }); + }, + createCostCenter: function (id, text, sapClient) { + // Create cost center object containing all predefined attributes + var costCenter = { + "controllingArea": "A000", + "validFrom": "/Date(1451606400000)/", + "validTo": "/Date(1483142400000)/", + "name": "Cost Center 01", + "category": "E", + "personResponsible": "USER", + "costCenterGroup": "0001", + "companyCode": "1010", + "profitCenter": "YB101" + }; + + costCenter.id = id; + costCenter.description = text; + costCenter.sapClient = sapClient; + + return jQuery.ajax({ + type: "POST", + url: "/api/v1/rest/client/" + sapClient + "/costcenters", + data: JSON.stringify(costCenter), + contentType: "application/json" + }) + } + } +}); diff --git a/Costcenter-Controller-CF/application/src/main/resources/static/view/costcenters.view.xml b/Costcenter-Controller-CF/application/src/main/resources/static/view/costcenters.view.xml new file mode 100644 index 0000000000..4f1dd54baf --- /dev/null +++ b/Costcenter-Controller-CF/application/src/main/resources/static/view/costcenters.view.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + +