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

SpiNNakerManchester / JavaSpiNNaker / 6821

17 Jul 2025 02:25PM UTC coverage: 36.192% (-0.04%) from 36.227%
6821

Pull #1223

github

web-flow
Merge branch 'master' into fix_swagger
Pull Request #1223: Fix swagger

1895 of 5876 branches covered (32.25%)

Branch coverage included in aggregate %.

6 of 7 new or added lines in 2 files covered. (85.71%)

6 existing lines in 1 file now uncovered.

8925 of 24020 relevant lines covered (37.16%)

0.74 hits per line

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

69.07
/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.validation.JAXRSBeanValidationInInterceptor;
53
import org.apache.cxf.message.Message;
54
import org.apache.cxf.phase.AbstractPhaseInterceptor;
55
import org.slf4j.Logger;
56
import org.springframework.beans.factory.annotation.Autowired;
57
import org.springframework.beans.factory.annotation.Qualifier;
58
import org.springframework.beans.factory.annotation.Value;
59
import org.springframework.boot.SpringApplication;
60
import org.springframework.boot.autoconfigure.SpringBootApplication;
61
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
62
import org.springframework.boot.context.properties.ConfigurationProperties;
63
import org.springframework.boot.context.properties.EnableConfigurationProperties;
64
import org.springframework.boot.jdbc.DataSourceBuilder;
65
import org.springframework.context.ApplicationContext;
66
import org.springframework.context.annotation.Bean;
67
import org.springframework.context.annotation.DependsOn;
68
import org.springframework.context.annotation.Import;
69
import org.springframework.context.annotation.Primary;
70
import org.springframework.context.annotation.PropertySource;
71
import org.springframework.context.annotation.Role;
72
import org.springframework.jdbc.core.JdbcTemplate;
73
import org.springframework.jdbc.support.JdbcTransactionManager;
74
import org.springframework.scheduling.annotation.EnableAsync;
75
import org.springframework.scheduling.annotation.EnableScheduling;
76
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
77
import org.springframework.stereotype.Component;
78
import org.springframework.transaction.PlatformTransactionManager;
79
import org.springframework.web.servlet.ViewResolver;
80
import org.springframework.web.servlet.view.AbstractUrlBasedView;
81
import org.springframework.web.servlet.view.InternalResourceViewResolver;
82

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

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

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

119
        private static final Logger log = getLogger(ServiceConfig.class);
2✔
120

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

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

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

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

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

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

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

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

231
        /**
232
         * Handles the upgrade of the CXF endpoint protocol to HTTPS when the
233
         * service is behind a reverse proxy like nginx which might be handling
234
         * SSL/TLS for us. In theory, CXF should do this itself; this is the kind of
235
         * obscure rubbish that frameworks are supposed to handle for us. In
236
         * practice, it doesn't. Yuck.
237
         *
238
         * @author Donal Fellows
239
         */
240
        @Component
241
        @Role(ROLE_SUPPORT)
242
        static class ProtocolUpgraderInterceptor
243
                        extends AbstractPhaseInterceptor<Message> {
244
                ProtocolUpgraderInterceptor() {
245
                        super(RECEIVE);
2✔
246
                }
2✔
247

248
                private static final String FORWARDED_PROTOCOL = "x-forwarded-proto";
249

250
                private static final String ENDPOINT_ADDRESS =
251
                                "org.apache.cxf.transport.endpoint.address";
252

253
                @SuppressWarnings("unchecked")
254
                private Map<String, List<String>> getHeaders(Message message) {
255
                        // If we've got one of these in the message, it's of this type
256
                        return (Map<String, List<String>>) message
×
257
                                        .getOrDefault(PROTOCOL_HEADERS, Map.of());
×
258
                }
259

260
                @Override
261
                public void handleMessage(Message message) throws Fault {
NEW
262
                        var headers = getHeaders(message);
×
263
                        if (headers.getOrDefault(FORWARDED_PROTOCOL, List.of())
×
264
                                        .contains("https")) {
×
265
                                upgradeEndpointProtocol(
×
266
                                                (ServletRequest) message.get(HTTP_REQUEST));
×
267
                        }
268
                }
×
269

270
                /**
271
                 * Upgrade the endpoint address if necessary; it's dumb that it might
272
                 * need it, but we're being careful.
273
                 */
274
                private void upgradeEndpointProtocol(ServletRequest request) {
275
                        var addr = (String) request.getAttribute(ENDPOINT_ADDRESS);
×
276
                        if (addr != null && addr.startsWith("http:")) {
×
277
                                request.setAttribute(ENDPOINT_ADDRESS,
×
278
                                                addr.replace("http:", "https:"));
×
279
                        }
280
                }
×
281
        }
282

283
        @Provider
284
        @Component
285
        @Role(ROLE_INFRASTRUCTURE)
286
        static class ValidationExceptionMapper
2✔
287
                        implements ExceptionMapper<ValidationException> {
288
                @Override
289
                public Response toResponse(ValidationException exception) {
290
                        var message = exception.getMessage().replaceAll(".*:\\s*", "");
×
291
                        return status(BAD_REQUEST).type(TEXT_PLAIN).entity(message).build();
×
292
                }
293
        }
294

295
        /**
296
         * The JAX-RS interface. Note that this is only used when not in test mode.
297
         *
298
         * @param service
299
         *            The service implementation
300
         * @param adminService
301
         *            The admin service
302
         * @param executor
303
         *            The thread pool
304
         * @param bus
305
         *            The Spring bus (think VengaBus but without the music)
306
         * @param protocolCorrector
307
         *            Attempts to make the protocol work correctly
308
         * @return The REST service core, configured.
309
         */
310
        @Bean(destroyMethod = "destroy")
311
        @ConditionalOnWebApplication
312
        @DependsOn("JSONProvider")
313
        Server jaxRsServer(SpallocServiceAPI service, AdminAPI adminService,
314
                        Executor executor, SpringBus bus,
315
                        ProtocolUpgraderInterceptor protocolCorrector) {
316
                var factory = new JAXRSServerFactoryBean();
2✔
317
                factory.setServiceBeans(List.of(service, adminService));
2✔
318
                factory.setStaticSubresourceResolution(true);
2✔
319
                factory.setAddress("/");
2✔
320
                factory.setBus(bus);
2✔
321
                factory.setProviders(List.of(
2✔
322
                                ctx.getBeansWithAnnotation(Provider.class).values()));
2✔
323
                factory.setFeatures(List.of(new OpenApiFeature()));
2✔
324
                factory.setInInterceptors(List.of(
2✔
325
                                new JAXRSBeanValidationInInterceptor(), protocolCorrector));
326
                var s = factory.create();
2✔
327
                s.getEndpoint().setExecutor(executor);
2✔
328
                return s;
2✔
329
        }
330

331
        /**
332
         * Used for making paths to things in the service in contexts where we can't
333
         * ask for the current request session to help. An example of such a context
334
         * is in configuring the access control rules on the paths, which has to be
335
         * done prior to any message session existing.
336
         *
337
         * @author Donal Fellows
338
         */
339
        @Component
340
        @Role(ROLE_SUPPORT)
341
        public static final class URLPathMaker {
2✔
342
                @Value("${spring.mvc.servlet.path}")
343
                private String mvcServletPath;
344

345
                @Value("${cxf.path}")
346
                private String cxfPath;
347

348
                /**
349
                 * Create a full local URL for the system components, bearing in mind
350
                 * the deployment configuration.
351
                 *
352
                 * @param suffix
353
                 *            The URL suffix; <em>should not</em> start with {@code /}
354
                 * @return The full local URL (absolute path, without protocol or host)
355
                 */
356
                public String systemUrl(String suffix) {
357
                        var prefix = mvcServletPath;
2✔
358
                        if (!prefix.endsWith("/")) {
2!
359
                                prefix += "/";
2✔
360
                        }
361
                        prefix += "system/";
2✔
362
                        return prefix + suffix;
2✔
363
                }
364

365
                /**
366
                 * Create a full local URL for web service components, bearing in mind
367
                 * the deployment configuration.
368
                 *
369
                 * @param suffix
370
                 *            The URL suffix; <em>should not</em> start with {@code /}
371
                 * @return The full local URL (absolute path, without protocol or host)
372
                 */
373
                public String serviceUrl(String suffix) {
374
                        var prefix = cxfPath;
2✔
375
                        if (!prefix.endsWith("/")) {
2!
376
                                prefix += "/";
2✔
377
                        }
378
                        return prefix + suffix;
2✔
379
                }
380
        }
381

382
        @Autowired
383
        private ApplicationContext ctx;
384

385
        // Exported so we can use a short name in SpEL in @Scheduled annotations
386
        @Bean
387
        @Role(ROLE_SUPPORT)
388
        AllocatorProperties allocatorProperties(SpallocProperties properties) {
389
                return properties.getAllocator();
2✔
390
        }
391

392
        // Exported so we can use a short name in SpEL in @Scheduled annotations
393
        @Bean
394
        @Role(ROLE_SUPPORT)
395
        KeepaliveProperties keepaliveProperties(SpallocProperties properties) {
396
                return properties.getKeepalive();
2✔
397
        }
398

399
        // Exported so we can use a short name in SpEL in @Scheduled annotations
400
        @Bean
401
        @Role(ROLE_SUPPORT)
402
        HistoricalDataProperties historyProperties(SpallocProperties properties) {
403
                return properties.getHistoricalData();
2✔
404
        }
405

406
        // Exported so we can use a short name in SpEL in @Scheduled annotations
407
        @Bean
408
        @Role(ROLE_SUPPORT)
409
        QuotaProperties quotaProperties(SpallocProperties properties) {
410
                return properties.getQuota();
2✔
411
        }
412

413
        // Exported so we can use a short name in SpEL in @Scheduled annotations
414
        @Bean
415
        @Role(ROLE_SUPPORT)
416
        TxrxProperties txrxProperties(SpallocProperties properties) {
417
                return properties.getTransceiver();
2✔
418
        }
419

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

427
        @Bean
428
        @Role(ROLE_SUPPORT)
429
        ViewResolver jspViewResolver() {
430
                var bean = new InternalResourceViewResolver() {
2✔
431
                        @Override
432
                        protected AbstractUrlBasedView buildView(String viewName)
433
                                        throws Exception {
434
                                var v = super.buildView(viewName);
2✔
435
                                var path = v.getUrl();
2✔
436
                                if (path.startsWith("/WEB-INF/views/system")) {
2!
437
                                        var path2 = path.replaceFirst("/system", "");
×
438
                                        log.debug("rewrote [{}] to [{}]", path, path2);
×
439
                                        v.setUrl(path2);
×
440
                                }
441
                                return v;
2✔
442
                        }
443
                };
444
                bean.setPrefix("/WEB-INF/views/");
2✔
445
                bean.setSuffix(".jsp");
2✔
446
                return bean;
2✔
447
        }
448

449
        /**
450
         * Log what beans are actually there, ignoring the bits and pieces of
451
         * framework. Useful for debugging!
452
         */
453
        private void logBeans() {
454
                if (log.isDebugEnabled()) {
2!
455
                        log.debug("beans defined: {}",
2✔
456
                                        List.of(ctx.getBeanDefinitionNames()));
2✔
457
                }
458
        }
2✔
459

460
        /**
461
         * Sets up everything before we enter service.
462
         */
463
        @PostConstruct
464
        private void readyForService() {
465
                logBeans();
2✔
466
        }
2✔
467

468
        /**
469
         * Spring Boot entry point.
470
         *
471
         * @param args
472
         *            Command line arguments.
473
         */
474
        public static void main(String[] args) {
475
                SpringApplication.run(ServiceConfig.class, args);
×
476
        }
×
477
}
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