• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SpiNNakerManchester / JavaSpiNNaker / 6934

20 Aug 2025 08:47AM UTC coverage: 36.209% (-0.02%) from 36.232%
6934

push

github

web-flow
Merge pull request #1281 from SpiNNakerManchester/bump_more_versions

Bump more versions

1899 of 5884 branches covered (32.27%)

Branch coverage included in aggregate %.

34 of 49 new or added lines in 8 files covered. (69.39%)

24 existing lines in 6 files now uncovered.

8942 of 24056 relevant lines covered (37.17%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.52
/SpiNNaker-allocserv/src/main/java/uk/ac/manchester/spinnaker/alloc/ServiceConfig.java
1
/*
2
 * Copyright (c) 2014 The University of Manchester
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 uk.ac.manchester.spinnaker.alloc;
17

18
import static com.fasterxml.jackson.databind.PropertyNamingStrategies.KEBAB_CASE;
19
import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS;
20
import static java.lang.System.setProperty;
21
import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN;
22
import static jakarta.ws.rs.core.Response.status;
23
import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST;
24
import static org.apache.cxf.message.Message.PROTOCOL_HEADERS;
25
import static org.apache.cxf.phase.Phase.RECEIVE;
26
import static org.apache.cxf.transport.http.AbstractHTTPDestination.HTTP_REQUEST;
27
import static org.slf4j.LoggerFactory.getLogger;
28
import static org.springframework.beans.factory.config.BeanDefinition.ROLE_APPLICATION;
29
import static org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE;
30
import static org.springframework.beans.factory.config.BeanDefinition.ROLE_SUPPORT;
31

32
import java.util.List;
33
import java.util.Map;
34
import java.util.concurrent.Executor;
35

36
import jakarta.annotation.PostConstruct;
37
import jakarta.servlet.ServletRequest;
38
import javax.sql.DataSource;
39
import jakarta.validation.ValidationException;
40
import jakarta.ws.rs.ApplicationPath;
41
import jakarta.ws.rs.core.Application;
42
import jakarta.ws.rs.core.Response;
43
import jakarta.ws.rs.ext.ExceptionMapper;
44
import jakarta.ws.rs.ext.Provider;
45

46
import org.apache.cxf.bus.spring.SpringBus;
47
import org.apache.cxf.endpoint.Server;
48
import org.apache.cxf.interceptor.Fault;
49
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
50
import org.apache.cxf.jaxrs.openapi.OpenApiFeature;
51
import org.apache.cxf.jaxrs.spring.JaxRsConfig;
52
import org.apache.cxf.jaxrs.swagger.ui.SwaggerUiConfig;
53
import org.apache.cxf.jaxrs.validation.JAXRSBeanValidationInInterceptor;
54
import org.apache.cxf.message.Message;
55
import org.apache.cxf.phase.AbstractPhaseInterceptor;
56
import org.slf4j.Logger;
57
import org.springframework.beans.factory.annotation.Autowired;
58
import org.springframework.beans.factory.annotation.Qualifier;
59
import org.springframework.beans.factory.annotation.Value;
60
import org.springframework.boot.SpringApplication;
61
import org.springframework.boot.autoconfigure.SpringBootApplication;
62
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
63
import org.springframework.boot.context.properties.ConfigurationProperties;
64
import org.springframework.boot.context.properties.EnableConfigurationProperties;
65
import org.springframework.boot.jdbc.DataSourceBuilder;
66
import org.springframework.context.ApplicationContext;
67
import org.springframework.context.annotation.Bean;
68
import org.springframework.context.annotation.DependsOn;
69
import org.springframework.context.annotation.Import;
70
import org.springframework.context.annotation.Primary;
71
import org.springframework.context.annotation.PropertySource;
72
import org.springframework.context.annotation.Role;
73
import org.springframework.jdbc.core.JdbcTemplate;
74
import org.springframework.jdbc.support.JdbcTransactionManager;
75
import org.springframework.scheduling.annotation.EnableAsync;
76
import org.springframework.scheduling.annotation.EnableScheduling;
77
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
78
import org.springframework.stereotype.Component;
79
import org.springframework.transaction.PlatformTransactionManager;
80
import org.springframework.web.servlet.ViewResolver;
81
import org.springframework.web.servlet.view.AbstractUrlBasedView;
82
import org.springframework.web.servlet.view.InternalResourceViewResolver;
83

84
import com.fasterxml.jackson.databind.ObjectMapper;
85
import com.fasterxml.jackson.databind.json.JsonMapper;
86
import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
87

88
import uk.ac.manchester.spinnaker.alloc.SpallocProperties.AllocatorProperties;
89
import uk.ac.manchester.spinnaker.alloc.SpallocProperties.AuthProperties;
90
import uk.ac.manchester.spinnaker.alloc.SpallocProperties.HistoricalDataProperties;
91
import uk.ac.manchester.spinnaker.alloc.SpallocProperties.KeepaliveProperties;
92
import uk.ac.manchester.spinnaker.alloc.SpallocProperties.QuotaProperties;
93
import uk.ac.manchester.spinnaker.alloc.SpallocProperties.TxrxProperties;
94
import uk.ac.manchester.spinnaker.alloc.admin.AdminAPI;
95
import uk.ac.manchester.spinnaker.alloc.model.Prototype;
96
import uk.ac.manchester.spinnaker.alloc.security.SecurityConfig;
97
import uk.ac.manchester.spinnaker.alloc.web.MvcConfig;
98
import uk.ac.manchester.spinnaker.alloc.web.SpallocServiceAPI;
99

100
/**
101
 * Builds the Spring beans in the application that are not auto-detected. There
102
 * are no public methods in this class that can be called by non-framework code.
103
 *
104
 * @see SecurityConfig
105
 * @author Donal Fellows
106
 */
107
@Import({JaxRsConfig.class, MvcConfig.class, SecurityConfig.class})
108
@PropertySource("classpath:service.properties")
109
@EnableScheduling
110
@EnableAsync
111
@SpringBootApplication
112
@ApplicationPath("spalloc")
113
@Role(ROLE_APPLICATION)
114
@EnableConfigurationProperties(SpallocProperties.class)
115
public class ServiceConfig extends Application {
2✔
116
        static {
117
                // DISABLE IPv6 SUPPORT; SpiNNaker can't use it and it's a pain
118
                setProperty("java.net.preferIPv4Stack", "false");
2✔
119
        }
120

121
        private static final Logger log = getLogger(ServiceConfig.class);
2✔
122

123
        @Bean(name = "mainDatasource")
124
        @Role(ROLE_INFRASTRUCTURE)
125
        @ConfigurationProperties(prefix = "spalloc.datasource")
126
        DataSource mainDatasource() {
127
                return DataSourceBuilder.create().build();
2✔
128
        }
129

130
        @Bean(name = "historicalDatasource")
131
        @Role(ROLE_INFRASTRUCTURE)
132
        @ConfigurationProperties(prefix = "spalloc.historical-data.datasource")
133
        DataSource historicalDatasource() {
134
                return DataSourceBuilder.create().build();
2✔
135
        }
136

137
        /**
138
         * The access to the main database.
139
         *
140
         * @param ds
141
         *            Information from application configuration. (Key:
142
         *            {@code spalloc.datasource}).
143
         * @return Database access API
144
         */
145
        @Bean(name = "mainDatabase")
146
        @Role(ROLE_SUPPORT)
147
        public JdbcTemplate mainDatabase(
148
                        @Qualifier("mainDatasource") DataSource ds) {
149
                return new JdbcTemplate(ds);
2✔
150
        }
151

152
        /**
153
         * The access to the tombstone database.
154
         *
155
         * @param ds
156
         *            Information from application configuration. (Key:
157
         *            {@code spalloc.historical-data.datasource}).
158
         * @return Database access API
159
         */
160
        @Bean(name = "historicalDatabase")
161
        @Role(ROLE_SUPPORT)
162
        public JdbcTemplate historicalDatabase(
163
                        @Qualifier("historicalDatasource") DataSource ds) {
164
                return new JdbcTemplate(ds);
2✔
165
        }
166

167
        /**
168
         * The access to the main database's transaction manager.
169
         *
170
         * @param ds
171
         *            Information from application configuration. (Key:
172
         *            {@code spalloc.datasource}).
173
         * @return Database transaction API
174
         */
175
        @Bean(name = "mainTransactionManager")
176
        @Role(ROLE_SUPPORT)
177
        public PlatformTransactionManager mainTransactionManager(
178
                        @Qualifier("mainDatasource") DataSource ds) {
179
                return new JdbcTransactionManager(ds);
2✔
180
        }
181

182
        /**
183
         * The thread pool. The rest of the application expects there to be a single
184
         * such pool.
185
         *
186
         * @param numThreads
187
         *            The size of the pool. From
188
         *            {@code spring.task.scheduling.pool.size} property.
189
         * @return The set up thread pool bean.
190
         */
191
        @Bean(name = "threadPoolTaskExecutor")
192
        @Primary
193
        public ThreadPoolTaskScheduler threadPoolTaskExecutor(
194
                        @Value("${spring.task.scheduling.pool.size}") int numThreads) {
195
                ThreadPoolTaskScheduler threadPoolTaskScheduler
2✔
196
                        = new ThreadPoolTaskScheduler();
197
                threadPoolTaskScheduler.setPoolSize(numThreads);
2✔
198
                threadPoolTaskScheduler.setThreadNamePrefix(
2✔
199
                        "ThreadPoolTaskScheduler");
200
                return threadPoolTaskScheduler;
2✔
201
        }
202

203
        /**
204
         * Set up mapping of java.util.Instant to/from JSON. Critical!
205
         *
206
         * @return a configured JSON mapper
207
         * @see <a href="https://stackoverflow.com/q/38168507/301832">Stack
208
         *      Overflow</a>
209
         */
210
        @Bean("ObjectMapper")
211
        @Role(ROLE_INFRASTRUCTURE)
212
        JsonMapper mapper() {
213
                return JsonMapper.builder().findAndAddModules()
2✔
214
                                .disable(WRITE_DATES_AS_TIMESTAMPS)
2✔
215
                                .propertyNamingStrategy(KEBAB_CASE).build();
2✔
216
        }
217

218
        /**
219
         * How we map between JSON and Java classes.
220
         *
221
         * @param mapper
222
         *            The core mapper.
223
         * @return A provider.
224
         */
225
        @Bean("JSONProvider")
226
        @Role(ROLE_INFRASTRUCTURE)
227
        JacksonJsonProvider jsonProvider(ObjectMapper mapper) {
228
                var provider = new JacksonJsonProvider();
2✔
229
                provider.setMapper(mapper);
2✔
230
                return provider;
2✔
231
        }
232

233
        /**
234
         * A factory for JAX-RS servers. This is a <em>prototype</em> bean; you get
235
         * a new instance each time.
236
         * <p>
237
         * You should call {@link JAXRSServerFactoryBean#setServiceBeans(List)
238
         * setServiceBeans(...)} on it, and then
239
         * {@link JAXRSServerFactoryBean#create() create()}. You might also need to
240
         * call {@link JAXRSServerFactoryBean#setAddress(String) setAddress(...)}.
241
         *
242
         * @param bus
243
         *            The CXF bus.
244
         * @param protocolCorrector
245
         *            How to correct the protocol
246
         * @param openApiFeature
247
         *            The Open API configuration
248
         * @return A factory instance
249
         */
250
        @Bean
251
        @Prototype
252
        @Role(ROLE_INFRASTRUCTURE)
253
        JAXRSServerFactoryBean rawFactory(SpringBus bus,
254
                        ProtocolUpgraderInterceptor protocolCorrector,
255
                        OpenApiFeature openApiFeature) {
256
                var factory = new JAXRSServerFactoryBean();
2✔
257
                factory.setStaticSubresourceResolution(true);
2✔
258
                factory.setAddress("/");
2✔
259
                factory.setBus(bus);
2✔
260
                factory.setProviders(List.of(
2✔
261
                                ctx.getBeansWithAnnotation(Provider.class).values()));
2✔
262
                factory.setFeatures(List.of(openApiFeature));
2✔
263
                factory.setInInterceptors(List
2✔
264
                                .of(new JAXRSBeanValidationInInterceptor(), protocolCorrector));
2✔
265
                return factory;
2✔
266
        }
267

268
        /**
269
         * Handles the upgrade of the CXF endpoint protocol to HTTPS when the
270
         * service is behind a reverse proxy like nginx which might be handling
271
         * SSL/TLS for us. In theory, CXF should do this itself; this is the kind of
272
         * obscure rubbish that frameworks are supposed to handle for us. In
273
         * practice, it doesn't. Yuck.
274
         *
275
         * @author Donal Fellows
276
         */
277
        @Component
278
        @Role(ROLE_SUPPORT)
279
        static class ProtocolUpgraderInterceptor
280
                        extends AbstractPhaseInterceptor<Message> {
281
                ProtocolUpgraderInterceptor() {
282
                        super(RECEIVE);
2✔
283
                }
2✔
284

285
                private static final String FORWARDED_PROTOCOL = "x-forwarded-proto";
286

287
                private static final String ENDPOINT_ADDRESS =
288
                                "org.apache.cxf.transport.endpoint.address";
289

290
                @SuppressWarnings("unchecked")
291
                private Map<String, List<String>> getHeaders(Message message) {
292
                        // If we've got one of these in the message, it's of this type
293
                        return (Map<String, List<String>>) message
×
294
                                        .getOrDefault(PROTOCOL_HEADERS, Map.of());
×
295
                }
296

297
                @Override
298
                public void handleMessage(Message message) throws Fault {
299
                        var headers = getHeaders(message);
×
300
                        if (headers.getOrDefault(FORWARDED_PROTOCOL, List.of())
×
301
                                        .contains("https")) {
×
302
                                upgradeEndpointProtocol(
×
303
                                                (ServletRequest) message.get(HTTP_REQUEST));
×
304
                        }
305
                }
×
306

307
                /**
308
                 * Upgrade the endpoint address if necessary; it's dumb that it might
309
                 * need it, but we're being careful.
310
                 */
311
                private void upgradeEndpointProtocol(ServletRequest request) {
312
                        var addr = (String) request.getAttribute(ENDPOINT_ADDRESS);
×
313
                        if (addr != null && addr.startsWith("http:")) {
×
314
                                request.setAttribute(ENDPOINT_ADDRESS,
×
315
                                                addr.replace("http:", "https:"));
×
316
                        }
317
                }
×
318
        }
319

320
        @Provider
321
        @Component
322
        @Role(ROLE_INFRASTRUCTURE)
323
        static class ValidationExceptionMapper
2✔
324
                        implements ExceptionMapper<ValidationException> {
325
                @Override
326
                public Response toResponse(ValidationException exception) {
327
                        var message = exception.getMessage().replaceAll(".*:\\s*", "");
×
328
                        return status(BAD_REQUEST).type(TEXT_PLAIN).entity(message).build();
×
329
                }
330
        }
331

332
        @Bean
333
        OpenApiFeature openApiFeature() {
334
                var openApiFeature = new OpenApiFeature();
2✔
335
                openApiFeature.setSwaggerUiConfig(
2✔
336
                                new SwaggerUiConfig().url("openapi.json")
2✔
337
                                .queryConfigEnabled(false));
2✔
338
                openApiFeature.setUseContextBasedConfig(true);
2✔
339
                return openApiFeature;
2✔
340
        }
341

342
        /**
343
         * The JAX-RS interface. Note that this is only used when not in test mode.
344
         *
345
         * @param service
346
         *            The service implementation
347
         * @param adminService
348
         *            The admin service
349
         * @param executor
350
         *            The thread pool
351
         * @param factory
352
         *            A factory used to make servers.
353
         * @return The REST service core, configured.
354
         */
355
        @Bean(destroyMethod = "destroy")
356
        @ConditionalOnWebApplication
357
        @DependsOn("JSONProvider")
358
        Server jaxRsServer(SpallocServiceAPI service, AdminAPI adminService,
359
                        Executor executor, JAXRSServerFactoryBean factory) {
360
                factory.setServiceBeans(List.of(service, adminService));
2✔
361
                var s = factory.create();
2✔
362
                s.getEndpoint().setExecutor(executor);
2✔
363
                return s;
2✔
364
        }
365

366
        /**
367
         * Used for making paths to things in the service in contexts where we can't
368
         * ask for the current request session to help. An example of such a context
369
         * is in configuring the access control rules on the paths, which has to be
370
         * done prior to any message session existing.
371
         *
372
         * @author Donal Fellows
373
         */
374
        @Component
375
        @Role(ROLE_SUPPORT)
376
        public static final class URLPathMaker {
2✔
377
                @Value("${spring.mvc.servlet.path}")
378
                private String mvcServletPath;
379

380
                @Value("${cxf.path}")
381
                private String cxfPath;
382

383
                /**
384
                 * Create a full local URL for the system components, bearing in mind
385
                 * the deployment configuration.
386
                 *
387
                 * @param suffix
388
                 *            The URL suffix; <em>should not</em> start with {@code /}
389
                 * @return The full local URL (absolute path, without protocol or host)
390
                 */
391
                public String systemUrl(String suffix) {
392
                        var prefix = mvcServletPath;
2✔
393
                        if (!prefix.endsWith("/")) {
2!
394
                                prefix += "/";
2✔
395
                        }
396
                        prefix += "system/";
2✔
397
                        return prefix + suffix;
2✔
398
                }
399

400
                /**
401
                 * Create a full local URL for web service components, bearing in mind
402
                 * the deployment configuration.
403
                 *
404
                 * @param suffix
405
                 *            The URL suffix; <em>should not</em> start with {@code /}
406
                 * @return The full local URL (absolute path, without protocol or host)
407
                 */
408
                public String serviceUrl(String suffix) {
409
                        var prefix = cxfPath;
2✔
410
                        if (!prefix.endsWith("/")) {
2!
411
                                prefix += "/";
2✔
412
                        }
413
                        return prefix + suffix;
2✔
414
                }
415
        }
416

417
        @Autowired
418
        private ApplicationContext ctx;
419

420
        // Exported so we can use a short name in SpEL in @Scheduled annotations
421
        @Bean
422
        @Role(ROLE_SUPPORT)
423
        AllocatorProperties allocatorProperties(SpallocProperties properties) {
424
                return properties.getAllocator();
2✔
425
        }
426

427
        // Exported so we can use a short name in SpEL in @Scheduled annotations
428
        @Bean
429
        @Role(ROLE_SUPPORT)
430
        KeepaliveProperties keepaliveProperties(SpallocProperties properties) {
431
                return properties.getKeepalive();
2✔
432
        }
433

434
        // Exported so we can use a short name in SpEL in @Scheduled annotations
435
        @Bean
436
        @Role(ROLE_SUPPORT)
437
        HistoricalDataProperties historyProperties(SpallocProperties properties) {
438
                return properties.getHistoricalData();
2✔
439
        }
440

441
        // Exported so we can use a short name in SpEL in @Scheduled annotations
442
        @Bean
443
        @Role(ROLE_SUPPORT)
444
        QuotaProperties quotaProperties(SpallocProperties properties) {
445
                return properties.getQuota();
2✔
446
        }
447

448
        // Exported so we can use a short name in SpEL in @Scheduled annotations
449
        @Bean
450
        @Role(ROLE_SUPPORT)
451
        TxrxProperties txrxProperties(SpallocProperties properties) {
452
                return properties.getTransceiver();
2✔
453
        }
454

455
        // Exported so we can use a short name in SpEL in @Scheduled annotations
456
        @Bean
457
        @Role(ROLE_SUPPORT)
458
        AuthProperties authProperties(SpallocProperties properties) {
459
                return properties.getAuth();
2✔
460
        }
461

462
        @Bean
463
        @Role(ROLE_SUPPORT)
464
        ViewResolver jspViewResolver() {
465
                var bean = new InternalResourceViewResolver() {
2✔
466
                        @Override
467
                        protected AbstractUrlBasedView buildView(String viewName)
468
                                        throws Exception {
469
                                var v = super.buildView(viewName);
2✔
470
                                var path = v.getUrl();
2✔
471
                                if (path.startsWith("/WEB-INF/views/system")) {
2!
472
                                        var path2 = path.replaceFirst("/system", "");
×
473
                                        log.debug("rewrote [{}] to [{}]", path, path2);
×
474
                                        v.setUrl(path2);
×
475
                                }
476
                                return v;
2✔
477
                        }
478
                };
479
                bean.setPrefix("/WEB-INF/views/");
2✔
480
                bean.setSuffix(".jsp");
2✔
481
                return bean;
2✔
482
        }
483

484
        /**
485
         * Log what beans are actually there, ignoring the bits and pieces of
486
         * framework. Useful for debugging!
487
         */
488
        private void logBeans() {
489
                if (log.isTraceEnabled()) {
2!
NEW
490
                        log.trace("beans defined: {}",
×
UNCOV
491
                                        List.of(ctx.getBeanDefinitionNames()));
×
492
                }
493
        }
2✔
494

495
        /**
496
         * Sets up everything before we enter service.
497
         */
498
        @PostConstruct
499
        private void readyForService() {
500
                logBeans();
2✔
501
        }
2✔
502

503
        /**
504
         * Spring Boot entry point.
505
         *
506
         * @param args
507
         *            Command line arguments.
508
         */
509
        public static void main(String[] args) {
510
                SpringApplication.run(ServiceConfig.class, args);
×
511
        }
×
512
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc