summaryrefslogtreecommitdiffstats
path: root/config-model/src/main/java/com/yahoo/vespa/model/container/jersey/RestApiContext.java
blob: 390539b643a74535f733b8e86a37d017ae93e77d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container.jersey;

import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.di.config.JerseyBundlesConfig;
import com.yahoo.container.di.config.JerseyInjectionConfig;
import com.yahoo.container.di.config.JerseyInjectionConfig.Inject;
import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import com.yahoo.vespa.model.container.component.Component;
import com.yahoo.vespa.model.container.component.SimpleComponent;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.logging.Logger;

/**
 * @author gjoranv
 */
public class RestApiContext extends SimpleComponent implements
        JerseyBundlesConfig.Producer,
        JerseyInjectionConfig.Producer
{
    private static final Logger log = Logger.getLogger(RestApi.class.getName());
    public static final String CONTAINER_CLASS = "com.yahoo.container.di.config.RestApiContext";

    private final List<BundleInfo> bundles = new ArrayList<>();

    // class name -> componentId
    private final Map<String, String> injectComponentForClass = new LinkedHashMap<>();

    private final String bindingPath;

    private ApplicationContainerCluster containerCluster;

    public RestApiContext(AbstractConfigProducer<?> ancestor, String bindingPath) {
        super(componentModel(bindingPath));
        this.bindingPath = bindingPath;

        if (ancestor instanceof ApplicationContainerCluster)
            containerCluster = (ApplicationContainerCluster)ancestor;

    }

    private static ComponentModel componentModel(String bindingPath) {
        return new ComponentModel(BundleInstantiationSpecification.getFromStrings(
                CONTAINER_CLASS + "-" + RestApi.idFromPath(bindingPath),
                CONTAINER_CLASS,
                null));
    }

    @Override
    public void getConfig(JerseyBundlesConfig.Builder builder) {
        builder.bundles(createBundlesConfig(bundles));
    }

    private List<JerseyBundlesConfig.Bundles.Builder> createBundlesConfig(List<BundleInfo> bundles) {
        List<JerseyBundlesConfig.Bundles.Builder> builders = new ArrayList<>();
        for (BundleInfo b : bundles) {
            builders.add(
                    new JerseyBundlesConfig.Bundles.Builder()
                            .spec(b.spec)
                            .packages(b.getPackagesToScan())
            );
        }
        return builders;
    }

    public void addBundles(Collection<BundleInfo> newBundles) {
        bundles.addAll(newBundles);
    }

    @Override
    public void getConfig(JerseyInjectionConfig.Builder builder) {
        for (Map.Entry<String, String> i : injectComponentForClass.entrySet()) {
            builder.inject(new Inject.Builder()
                                   .forClass(i.getKey())
                                   .instance(i.getValue()));
        }
    }

    @Override
    public void validate() throws Exception {
        super.validate();

        if (bundles.isEmpty())
            log.warning("No bundles in rest-api '" + bindingPath +
                                "' - components will only be loaded from classpath.");
    }

    public void prepare() {
        if (containerCluster == null) return;

        containerCluster.getAllComponents().stream().
                filter(isCycleGeneratingComponent.negate()).
                forEach(this::inject);
    }


    /*
     * Example problem
     *
     * RestApiContext -> ApplicationStatusHandler -> ComponentRegistry<HttpServer> -> JettyHttpServer -> ComponentRegistry<Jersey2Servlet> -> RestApiContext
     */
    private Predicate<Component> isCycleGeneratingComponent = component -> {
        switch (component.getClassId().getName()) {
            case CONTAINER_CLASS:
            case Jersey2Servlet.CLASS:
            case "com.yahoo.jdisc.http.server.jetty.JettyHttpServer":
            case "com.yahoo.container.handler.observability.ApplicationStatusHandler":
                return true;
            default:
                return false;
        }
    };

    public static class BundleInfo {
        // SymbolicName[:Version]
        public final String spec;

        private final List<String> packagesToScan = new ArrayList<>();

        public BundleInfo(String spec) {
            this.spec = spec;
        }

        public List<String> getPackagesToScan() {
            return packagesToScan;
        }

        public void addPackageToScan(String pkg) {
            packagesToScan.add(pkg);
        }
    }

}