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

SpiNNakerManchester / JavaSpiNNaker / 6824

18 Jul 2025 02:37PM UTC coverage: 36.199% (+0.007%) from 36.192%
6824

push

github

rowleya
Make things actually do the right thing in the API including docs

1898 of 5876 branches covered (32.3%)

Branch coverage included in aggregate %.

8927 of 24028 relevant lines covered (37.15%)

0.73 hits per line

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

71.43
/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
         * @return A factory instance
247
         */
248
        @Bean
249
        @Prototype
250
        @Role(ROLE_INFRASTRUCTURE)
251
        JAXRSServerFactoryBean rawFactory(SpringBus bus,
252
                        ProtocolUpgraderInterceptor protocolCorrector,
253
                        OpenApiFeature openApiFeature) {
254
                var factory = new JAXRSServerFactoryBean();
2✔
255
                factory.setStaticSubresourceResolution(true);
2✔
256
                factory.setAddress("/");
2✔
257
                factory.setBus(bus);
2✔
258
                factory.setProviders(List.of(
2✔
259
                                ctx.getBeansWithAnnotation(Provider.class).values()));
2✔
260
                factory.setFeatures(List.of(openApiFeature));
2✔
261
                factory.setInInterceptors(List
2✔
262
                                .of(new JAXRSBeanValidationInInterceptor(), protocolCorrector));
2✔
263
                return factory;
2✔
264
        }
265

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

283
                private static final String FORWARDED_PROTOCOL = "x-forwarded-proto";
284

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

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

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

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

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

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

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

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

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

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

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

415
        @Autowired
416
        private ApplicationContext ctx;
417

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

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

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

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

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

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

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

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

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

501
        /**
502
         * Spring Boot entry point.
503
         *
504
         * @param args
505
         *            Command line arguments.
506
         */
507
        public static void main(String[] args) {
508
                SpringApplication.run(ServiceConfig.class, args);
×
509
        }
×
510
}
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