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

SpiNNakerManchester / JavaSpiNNaker / 14382497709

10 Apr 2025 02:05PM UTC coverage: 38.261% (-0.03%) from 38.289%
14382497709

push

github

rowleya
Add licenses

9191 of 24022 relevant lines covered (38.26%)

1.15 hits per line

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

75.9
/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 javax.ws.rs.core.MediaType.TEXT_PLAIN;
22
import static javax.ws.rs.core.Response.status;
23
import static javax.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 javax.annotation.PostConstruct;
37
import javax.servlet.ServletRequest;
38
import javax.sql.DataSource;
39
import javax.validation.ValidationException;
40
import javax.ws.rs.ApplicationPath;
41
import javax.ws.rs.core.Application;
42
import javax.ws.rs.core.Response;
43
import javax.ws.rs.ext.ExceptionMapper;
44
import javax.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.jaxrs.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 {
3✔
114
        static {
115
                // DISABLE IPv6 SUPPORT; SpiNNaker can't use it and it's a pain
116
                setProperty("java.net.preferIPv4Stack", "false");
3✔
117
        }
118

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

121
        @Bean(name = "mainDatasource")
122
        @Role(ROLE_INFRASTRUCTURE)
123
        @ConfigurationProperties(prefix = "spalloc.datasource")
124
        DataSource mainDatasource() {
125
                return DataSourceBuilder.create().build();
3✔
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();
3✔
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);
3✔
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);
3✔
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);
3✔
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
3✔
194
                        = new ThreadPoolTaskScheduler();
195
                threadPoolTaskScheduler.setPoolSize(numThreads);
3✔
196
                threadPoolTaskScheduler.setThreadNamePrefix(
3✔
197
                        "ThreadPoolTaskScheduler");
198
                return threadPoolTaskScheduler;
3✔
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()
3✔
212
                                .disable(WRITE_DATES_AS_TIMESTAMPS)
3✔
213
                                .propertyNamingStrategy(KEBAB_CASE).build();
3✔
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();
3✔
227
                provider.setMapper(mapper);
3✔
228
                return provider;
3✔
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);
3✔
246
                }
3✔
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 {
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
3✔
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 factory
305
         *            A factory used to make servers.
306
         * @return The REST service core, configured.
307
         */
308
        @Bean(destroyMethod = "destroy")
309
        @ConditionalOnWebApplication
310
        @DependsOn("JSONProvider")
311
        Server jaxRsServer(SpallocServiceAPI service, AdminAPI adminService,
312
                        Executor executor, SpringBus bus,
313
                        ProtocolUpgraderInterceptor protocolCorrector) {
314
                var factory = new JAXRSServerFactoryBean();
3✔
315
                factory.setServiceBeans(List.of(service, adminService));
3✔
316
                factory.setStaticSubresourceResolution(true);
3✔
317
                factory.setAddress("/");
3✔
318
                factory.setBus(bus);
3✔
319
                factory.setProviders(List.of(
3✔
320
                                ctx.getBeansWithAnnotation(Provider.class).values()));
3✔
321
                factory.setFeatures(List.of(new OpenApiFeature()));
3✔
322
                factory.setInInterceptors(List.of(
3✔
323
                                new JAXRSBeanValidationInInterceptor(), protocolCorrector));
324
                var s = factory.create();
3✔
325
                s.getEndpoint().setExecutor(executor);
3✔
326
                return s;
3✔
327
        }
328

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

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

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

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

380
        @Autowired
381
        private ApplicationContext ctx;
382

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

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

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

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

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

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

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

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

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

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