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.getService(EventService.class).addListener(ReloadService.RELOAD_TOPIC, reloadListener = new ReloadListener(context));
089    }
090
091    @Override
092    public void deactivate(ComponentContext context) {
093        Framework.getService(EventService.class).removeListener(ReloadService.RELOAD_TOPIC, reloadListener);
094    }
095
096    final ReportConfiguration configuration = new ReportConfiguration();
097
098    final Service service = new Service();
099
100    class Service implements ReportRunner {
101        @Override
102        public Set<String> list() {
103            Set<String> names = new HashSet<>();
104            for (Contribution contrib : configuration) {
105                names.add(contrib.name);
106            }
107            return names;
108        }
109
110        @Override
111        public void run(OutputStream out, Set<String> names) throws IOException {
112            out.write('{');
113            out.write('"');
114            out.write(NuxeoInstanceIdentifierHelper.getServerInstanceName().getBytes());
115            out.write('"');
116            out.write(':');
117            out.write('{');
118            Iterator<Contribution> iterator = configuration.iterator(names);
119            while (iterator.hasNext()) {
120                Contribution contrib = iterator.next();
121                out.write('"');
122                out.write(contrib.name.getBytes());
123                out.write('"');
124                out.write(':');
125                contrib.writer.write(out);
126                if (iterator.hasNext()) {
127                    out.write(',');
128                }
129                out.flush();
130            }
131            out.write('}');
132            out.write('}');
133            out.flush();
134        }
135    }
136
137    final Management management = new Management();
138
139    public class Management implements ReportServer {
140
141        @Override
142        public void run(String host, int port, String... names) throws IOException {
143            ClassLoader tcl = Thread.currentThread().getContextClassLoader();
144            Thread.currentThread().setContextClassLoader(Runtime.class.getClassLoader());
145            try (Socket sock = new Socket(host, port)) {
146                try (OutputStream sink =
147                        sock.getOutputStream()) {
148                    service.run(sink, new HashSet<>(Arrays.asList(names)));
149                }
150            } catch (IOException cause) {
151                throw cause;
152            } finally {
153                Thread.currentThread().setContextClassLoader(tcl);
154            }
155        }
156
157    }
158
159    @Override
160    public void applicationStarted(ComponentContext context) {
161        instance = this;
162        Framework.getService(ResourcePublisher.class).registerResource("connect-report", "connect-report", ReportServer.class, management);
163        Framework.addListener(new RuntimeServiceListener() {
164
165            @Override
166            public void handleEvent(RuntimeServiceEvent event) {
167                if (event.id != RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP) {
168                    return;
169                }
170                Framework.removeListener(this);
171                applicationStopped(context);
172            }
173
174        });
175    }
176
177    protected void applicationStopped(ComponentContext context) {
178        Framework.getService(ResourcePublisher.class).unregisterResource("connect-report", "connect-report");
179    }
180
181    @Override
182    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
183        if (contribution instanceof Contribution) {
184            configuration.addContribution((Contribution) contribution);
185        } else {
186            throw new IllegalArgumentException(String.format("unknown contribution of type %s in %s", contribution.getClass(), contributor));
187        }
188    }
189
190    @Override
191    public <T> T getAdapter(Class<T> adapter) {
192        if (adapter.isAssignableFrom(Service.class)) {
193            return adapter.cast(service);
194        }
195        return super.getAdapter(adapter);
196    }
197
198}