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 static ReportComponent instance;
049
050    public ReportComponent() {
051        instance = this;
052    }
053
054    ReloadListener reloadListener;
055
056    class ReloadListener implements EventListener {
057        final ComponentContext context;
058
059        ReloadListener(ComponentContext context) {
060            this.context = context;
061        }
062
063        @Override
064        public void handleEvent(Event event) {
065            if (ReloadService.AFTER_RELOAD_EVENT_ID.equals(event.getId())) {
066                applicationStarted(context);
067            } else if (ReloadService.BEFORE_RELOAD_EVENT_ID.equals(event.getId())) {
068                applicationStopped(context);
069            }
070        }
071
072    }
073
074    @Override
075    public void activate(ComponentContext context) {
076        Framework.addListener(new RuntimeServiceListener() {
077
078            @Override
079            public void handleEvent(RuntimeServiceEvent event) {
080                if (RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP != event.id) {
081                    return;
082                }
083                Framework.removeListener(this);
084                Framework.getService(EventService.class).removeListener(ReloadService.RELOAD_TOPIC, reloadListener);
085            }
086        });
087        Framework.getService(EventService.class).addListener(ReloadService.RELOAD_TOPIC,
088                reloadListener = new ReloadListener(context));
089    }
090
091    final ReportConfiguration configuration = new ReportConfiguration();
092
093    final Service service = new Service();
094
095    class Service implements ReportRunner {
096        @Override
097        public Set<String> list() {
098            Set<String> names = new HashSet<>();
099            for (Contribution contrib : configuration) {
100                names.add(contrib.name);
101            }
102            return names;
103        }
104
105        @Override
106        public void run(OutputStream out, Set<String> names) throws IOException {
107            out.write('{');
108            out.write('"');
109            out.write(NuxeoInstanceIdentifierHelper.getServerInstanceName().getBytes());
110            out.write('"');
111            out.write(':');
112            out.write('{');
113            Iterator<Contribution> iterator = configuration.iterator(names);
114            while (iterator.hasNext()) {
115                Contribution contrib = iterator.next();
116                out.write('"');
117                out.write(contrib.name.getBytes());
118                out.write('"');
119                out.write(':');
120                contrib.writer.write(out);
121                if (iterator.hasNext()) {
122                    out.write(',');
123                }
124                out.flush();
125            }
126            out.write('}');
127            out.write('}');
128            out.flush();
129        }
130    }
131
132    final Management management = new Management();
133
134    public class Management implements ReportServer {
135
136        @Override
137        public void run(String host, int port, String... names) throws IOException {
138            ClassLoader tcl = Thread.currentThread().getContextClassLoader();
139            Thread.currentThread().setContextClassLoader(Management.class.getClassLoader());
140            try (Socket sock = new Socket(host, port); OutputStream sink = sock.getOutputStream()) {
141                service.run(sink, new HashSet<>(Arrays.asList(names)));
142            } finally {
143                Thread.currentThread().setContextClassLoader(tcl);
144            }
145        }
146
147    }
148
149    @Override
150    public void applicationStarted(ComponentContext context) {
151        instance = this;
152        Framework.getService(ResourcePublisher.class).registerResource("connect-report", "connect-report",
153                ReportServer.class, management);
154        Framework.addListener(new RuntimeServiceListener() {
155
156            @Override
157            public void handleEvent(RuntimeServiceEvent event) {
158                if (event.id != RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP) {
159                    return;
160                }
161                Framework.removeListener(this);
162                applicationStopped(context);
163            }
164
165        });
166    }
167
168    protected void applicationStopped(ComponentContext context) {
169        Framework.getService(ResourcePublisher.class).unregisterResource("connect-report", "connect-report");
170    }
171
172    @Override
173    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
174        if (contribution instanceof Contribution) {
175            configuration.addContribution((Contribution) contribution);
176        } else {
177            throw new IllegalArgumentException(
178                    String.format("unknown contribution of type %s in %s", contribution.getClass(), contributor));
179        }
180    }
181
182    @Override
183    public <T> T getAdapter(Class<T> adapter) {
184        if (adapter.isAssignableFrom(Service.class)) {
185            return adapter.cast(service);
186        }
187        return super.getAdapter(adapter);
188    }
189
190}