Skip to content
Permalink
Browse files
BATCHEE-152 spring-boot/spring-batch batchee UI wiring
  • Loading branch information
rmannibucau committed Oct 25, 2021
1 parent 5f508cb commit 0dc978b163ac5f5064affff4dfb06d9d7b4abe0b
Showing 6 changed files with 583 additions and 0 deletions.
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>batchee-tools</artifactId>
<groupId>org.apache.batchee</groupId>
<version>1.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-boot-batchee-ui</artifactId>
<name>BatchEE :: Tools :: Spring Boot UI</name>

<properties>
<spring-boot.version>2.5.6</spring-boot.version>
</properties>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>batchee-servlet-embedded</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <!-- for jsp -->
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>9.0.54</version>
</dependency>

<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-core</artifactId>
<version>4.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>${spring-boot.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
<scope>provided</scope>
</dependency>

<!-- for now we still use junit4 so stick to it -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,95 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.batchee.spring.ui;

import javax.batch.operations.JobOperator;
import javax.batch.operations.JobSecurityException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

import static java.util.Collections.enumeration;
import static java.util.Collections.singleton;
import static java.util.stream.Collectors.toSet;

public class BatchEEJobOperator extends DelegatingJobOperator {
private final String[] defaultJobNames;

public BatchEEJobOperator(final JobOperator builtIn,
final String... defaultJobNames) {
super(builtIn);
this.defaultJobNames = defaultJobNames;
}

@Override
public Set<String> getJobNames() throws JobSecurityException {
final Set<String> builtInNames = super.getJobNames();
return defaultJobNames.length == 0 ?
builtInNames :
Stream.concat(Stream.of(defaultJobNames), builtInNames.stream()).collect(toSet());
}

public static class Context {
private Context() {
// no-op
}

public static <T> T withFacade(final JobOperator operator, final Function<JobOperator, T> task) {
JobOperatorFacade.current = operator;
final Thread thread = Thread.currentThread();
final ClassLoader loader = thread.getContextClassLoader();
thread.setContextClassLoader(new ClassLoader(loader) {
// don't use default spring JobOperator impl, it is not accurate and loads a global context - we don't want it
@Override
public Enumeration<URL> getResources(final String name) throws IOException {
if ("META-INF/services/javax.batch.operations.JobOperator".equals(name)) {
return enumeration(singleton(new URL("embed://", null, -1, "", new URLStreamHandler() {
@Override
protected URLConnection openConnection(final URL u) {
return new URLConnection(u) {
@Override
public void connect() {
// no-op
}

@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(JobOperatorFacade.class.getName().getBytes(StandardCharsets.UTF_8));
}
};
}
})));
}
return super.getResources(name);
}
});
try {
return task.apply(operator);
} finally {
thread.setContextClassLoader(loader);
}
}
}
}
@@ -0,0 +1,109 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.batchee.spring.ui;

import org.apache.batchee.servlet.JBatchController;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.jsr.JsrJobParametersConverter;
import org.springframework.batch.core.jsr.launch.JsrJobOperator;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

import javax.batch.operations.JobOperator;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.sql.DataSource;
import java.util.List;

@Configuration(proxyBeanMethods = false)
public class BatchEEUI {
@Bean
@ConditionalOnMissingBean(value = JobOperator.class)
public JobOperator operator(final JobExplorer jobExplorer,
final JobRepository jobRepository,
final PlatformTransactionManager transactionManager,
final DataSource dataSource,
final Properties properties,
@Autowired(required = false) final List<Job> jobs) throws Exception {
final JsrJobParametersConverter jobParametersConverter = new JsrJobParametersConverter(dataSource);
jobParametersConverter.afterPropertiesSet();
return new BatchEEJobOperator(
new JsrJobOperator(jobExplorer, jobRepository, jobParametersConverter, transactionManager),
getJobNames(properties, jobs));
}

@Bean
public ServletRegistrationBean<JBatchController> batcheeUI(final JobOperator operator, final Properties properties) {
final ServletRegistrationBean<JBatchController> registrationBean = new ServletRegistrationBean<>(new JBatchController() {
@Override
public void init(final ServletConfig config) {
// force operator to be this one since spring JsrJobOperator loads an "external" context we don't want at all
BatchEEJobOperator.Context.withFacade(operator, op -> {
try {
super.init(config);
return null;
} catch (final ServletException e) {
throw new IllegalStateException(e);
}
});
}
}.defaultScan(false).mapping(properties.getMapping()), properties.getMapping());
registrationBean.setLoadOnStartup(1);
registrationBean.setAsyncSupported(true);
return registrationBean;
}

private String[] getJobNames(final Properties properties, final List<Job> jobs) {
try {
return jobs != null && properties.getDefaultBatchNames().length == 0 ?
jobs.stream().map(Job::getName).toArray(String[]::new) :
properties.getDefaultBatchNames();
} catch (final RuntimeException re) {
return properties.getDefaultBatchNames();
}
}

@Configuration
@ConfigurationProperties(prefix = "batchee.ui")
public static class Properties {
private String mapping = "/batchee/*";
private String[] defaultBatchNames = new String[0];

public String[] getDefaultBatchNames() {
return defaultBatchNames;
}

public void setDefaultBatchNames(final String[] defaultBatchNames) {
this.defaultBatchNames = defaultBatchNames;
}

public void setMapping(final String mapping) {
this.mapping = mapping;
}

public String getMapping() {
return mapping;
}
}
}

0 comments on commit 0dc978b

Please sign in to comment.