001/*
002 * (C) Copyright 2016 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Stephane Lacoin at Nuxeo (aka matic)
016 */
017package org.nuxeo.connect.tools.report;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.net.Socket;
022import java.util.Arrays;
023import java.util.HashSet;
024import java.util.Iterator;
025import java.util.Set;
026
027import org.nuxeo.connect.tools.report.ReportConfiguration.Contribution;
028import org.nuxeo.ecm.core.management.statuses.NuxeoInstanceIdentifierHelper;
029import org.nuxeo.runtime.RuntimeServiceEvent;
030import org.nuxeo.runtime.RuntimeServiceListener;
031import org.nuxeo.runtime.api.Framework;
032import org.nuxeo.runtime.management.ResourcePublisher;
033import org.nuxeo.runtime.model.ComponentContext;
034import org.nuxeo.runtime.model.ComponentInstance;
035import org.nuxeo.runtime.model.DefaultComponent;
036import org.nuxeo.runtime.reload.ReloadService;
037import org.nuxeo.runtime.services.event.Event;
038import org.nuxeo.runtime.services.event.EventListener;
039import org.nuxeo.runtime.services.event.EventService;
040
041/**
042 * Reports aggregator, exposed as a service in the runtime.
043 *
044 * @since 8.3
045 */
046public class ReportComponent extends DefaultComponent {
047
048    public interface Runner {
049
050        void run(OutputStream out, Set<String> names) throws IOException;
051
052        Set<String> list();
053    }
054
055    public static ReportComponent instance;
056
057    public ReportComponent() {
058        instance = this;
059    }
060
061    ReloadListener reloadListener;
062
063    class ReloadListener implements EventListener {
064        final ComponentContext context;
065
066        ReloadListener(ComponentContext context) {
067            this.context = context;
068        }
069
070        @Override
071        public void handleEvent(Event event) {
072            if (ReloadService.AFTER_RELOAD_EVENT_ID.equals(event.getId())) {
073                applicationStarted(context);
074                return;
075            } else if (ReloadService.BEFORE_RELOAD_EVENT_ID.equals(event.getId())) {
076                applicationStopped(context);
077            }
078        }
079
080        @Override
081        public boolean aboutToHandleEvent(Event event) {
082            return true;
083        }
084    }
085
086    @Override
087    public void activate(ComponentContext context) {
088        Framework.addListener(new RuntimeServiceListener() {
089
090            @Override
091            public void handleEvent(RuntimeServiceEvent event) {
092                if (RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP != event.id) {
093                    return;
094                }
095                Framework.removeListener(this);
096                Framework.getService(EventService.class).removeListener(ReloadService.RELOAD_TOPIC, reloadListener);
097            }
098        });
099        Framework.getService(EventService.class).addListener(ReloadService.RELOAD_TOPIC, reloadListener = new ReloadListener(context));
100    }
101
102    final ReportConfiguration configuration = new ReportConfiguration();
103
104    final Service service = new Service();
105
106    class Service implements ReportRunner {
107        @Override
108        public Set<String> list() {
109            Set<String> names = new HashSet<>();
110            for (Contribution contrib : configuration) {
111                names.add(contrib.name);
112            }
113            return names;
114        }
115
116        @Override
117        public void run(OutputStream out, Set<String> names) throws IOException {
118            out.write('{');
119            out.write('"');
120            out.write(NuxeoInstanceIdentifierHelper.getServerInstanceName().getBytes());
121            out.write('"');
122            out.write(':');
123            out.write('{');
124            Iterator<Contribution> iterator = configuration.iterator(names);
125            while (iterator.hasNext()) {
126                Contribution contrib = iterator.next();
127                out.write('"');
128                out.write(contrib.name.getBytes());
129                out.write('"');
130                out.write(':');
131                contrib.writer.write(out);
132                if (iterator.hasNext()) {
133                    out.write(',');
134                }
135                out.flush();
136            }
137            out.write('}');
138            out.write('}');
139            out.flush();
140        }
141    }
142
143    final Management management = new Management();
144
145    public class Management implements ReportServer {
146
147        @Override
148        public void run(String host, int port, String... names) throws IOException {
149            ClassLoader tcl = Thread.currentThread().getContextClassLoader();
150            Thread.currentThread().setContextClassLoader(Management.class.getClassLoader());
151            try (Socket sock = new Socket(host, port)) {
152                try (OutputStream sink =
153                        sock.getOutputStream()) {
154                    service.run(sink, new HashSet<>(Arrays.asList(names)));
155                }
156            } catch (IOException cause) {
157                throw cause;
158            } finally {
159                Thread.currentThread().setContextClassLoader(tcl);
160            }
161        }
162
163    }
164
165    @Override
166    public void applicationStarted(ComponentContext context) {
167        instance = this;
168        Framework.getService(ResourcePublisher.class).registerResource("connect-report", "connect-report", ReportServer.class, management);
169        Framework.addListener(new RuntimeServiceListener() {
170
171            @Override
172            public void handleEvent(RuntimeServiceEvent event) {
173                if (event.id != RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP) {
174                    return;
175                }
176                Framework.removeListener(this);
177                applicationStopped(context);
178            }
179
180        });
181    }
182
183    protected void applicationStopped(ComponentContext context) {
184        Framework.getService(ResourcePublisher.class).unregisterResource("connect-report", "connect-report");
185    }
186
187    @Override
188    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
189        if (contribution instanceof Contribution) {
190            configuration.addContribution((Contribution) contribution);
191        } else {
192            throw new IllegalArgumentException(String.format("unknown contribution of type %s in %s", contribution.getClass(), contributor));
193        }
194    }
195
196    @Override
197    public <T> T getAdapter(Class<T> adapter) {
198        if (adapter.isAssignableFrom(Service.class)) {
199            return adapter.cast(service);
200        }
201        return super.getAdapter(adapter);
202    }
203
204}