source: FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSEndpointTester.java @ 7196

Last change on this file since 7196 was 7196, checked in by Oliver Schonefeld, 6 years ago
  • rewrite to use SRUClient instead of SRUSimpleClient
  • add Tests for Endpoint Description (FCS 1.0 and FCS 2.0)
  • Property svn:eol-style set to native
File size: 17.5 KB
Line 
1/**
2 * This software is copyright (c) 2013 by
3 *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
4 * This is free software. You can redistribute it
5 * and/or modify it under the terms described in
6 * the GNU General Public License v3 of which you
7 * should have received a copy. Otherwise you can download
8 * it from
9 *
10 *   http://www.gnu.org/licenses/gpl-3.0.txt
11 *
12 * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
13 *
14 * @license http://www.gnu.org/licenses/gpl-3.0.txt
15 *  GNU General Public License v3
16 */
17package eu.clarin.fcs.tester;
18
19import java.io.IOException;
20import java.util.ArrayList;
21import java.util.Arrays;
22import java.util.Collections;
23import java.util.Comparator;
24import java.util.LinkedList;
25import java.util.List;
26import java.util.Set;
27import java.util.concurrent.ExecutorService;
28import java.util.concurrent.Executors;
29import java.util.concurrent.RejectedExecutionException;
30import java.util.concurrent.TimeUnit;
31import java.util.logging.Handler;
32import java.util.logging.Level;
33import java.util.logging.LogRecord;
34
35import javax.servlet.ServletContextEvent;
36import javax.servlet.ServletContextListener;
37
38import org.apache.http.HttpResponse;
39import org.apache.http.HttpStatus;
40import org.apache.http.StatusLine;
41import org.apache.http.client.ClientProtocolException;
42import org.apache.http.client.config.CookieSpecs;
43import org.apache.http.client.config.RequestConfig;
44import org.apache.http.client.methods.HttpHead;
45import org.apache.http.client.protocol.HttpClientContext;
46import org.apache.http.client.utils.HttpClientUtils;
47import org.apache.http.config.SocketConfig;
48import org.apache.http.impl.NoConnectionReuseStrategy;
49import org.apache.http.impl.client.CloseableHttpClient;
50import org.apache.http.impl.client.HttpClients;
51import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
52import org.reflections.Reflections;
53import org.slf4j.Logger;
54import org.slf4j.LoggerFactory;
55
56import eu.clarin.sru.client.SRUClient;
57import eu.clarin.sru.client.SRUClientConfig;
58import eu.clarin.sru.client.SRUClientException;
59import eu.clarin.sru.client.SRUExplainRequest;
60import eu.clarin.sru.client.SRUExplainResponse;
61import eu.clarin.sru.client.SRUVersion;
62import eu.clarin.sru.client.fcs.ClarinFCSClientBuilder;
63import eu.clarin.sru.client.fcs.ClarinFCSConstants;
64import eu.clarin.sru.client.fcs.ClarinFCSEndpointDescription;
65import eu.clarin.sru.client.fcs.ClarinFCSEndpointDescriptionParser;
66
67
68public class FCSEndpointTester implements ServletContextListener {
69    public interface ProgressListener {
70        public void updateProgress(String message);
71
72        public void updateMaximum(int maximum);
73
74        public void onDone(FCSTestContext context, List<FCSTestResult> results);
75
76        public void onError(String message, Throwable e);
77    }
78    private static final String TESTCASE_PACKAGE =
79            "eu.clarin.fcs.tester.tests";
80    private static final String USER_AGENT = "FCSEndpointTester/1.0.0";
81    private static final int DEFAULT_CONNECT_TIMEOUT = -1;
82    private static final int DEFAULT_SOCKET_TIMEOUT  = -1;
83    private static final List<FCSTest> TESTS;
84    private static final Logger logger =
85            LoggerFactory.getLogger(FCSEndpointTester.class);
86    private final FCSLoggingHandler logcapturehandler =
87            new FCSLoggingHandler();
88    private final ExecutorService executor = Executors.newCachedThreadPool();
89    private static final FCSEndpointTester INSTANCE = new FCSEndpointTester();
90
91
92    public static FCSEndpointTester getInstance() {
93        return INSTANCE;
94    }
95
96
97    private void destroy() {
98        executor.shutdownNow();
99        try {
100            executor.awaitTermination(5000, TimeUnit.MILLISECONDS);
101            logger.debug("thread pool terminated");
102        } catch (InterruptedException e) {
103            /* IGNORE */
104        }
105    }
106
107
108    private static CloseableHttpClient createHttpClient(int connectTimeout,
109            int socketTimeout) {
110        final PoolingHttpClientConnectionManager manager =
111                new PoolingHttpClientConnectionManager();
112        manager.setDefaultMaxPerRoute(8);
113        manager.setMaxTotal(128);
114
115        final SocketConfig socketConfig = SocketConfig.custom()
116                .setSoReuseAddress(true)
117                .setSoLinger(0)
118                .build();
119
120        final RequestConfig requestConfig = RequestConfig.custom()
121                .setAuthenticationEnabled(false)
122                .setRedirectsEnabled(true)
123                .setMaxRedirects(4)
124                .setCircularRedirectsAllowed(false)
125                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
126                .setConnectTimeout(connectTimeout)
127                .setSocketTimeout(socketTimeout)
128                .setConnectionRequestTimeout(0) /* infinite */
129                .build();
130
131        return HttpClients.custom()
132                .setUserAgent(USER_AGENT)
133                .setConnectionManager(manager)
134                .setDefaultSocketConfig(socketConfig)
135                .setDefaultRequestConfig(requestConfig)
136                .setConnectionReuseStrategy(new NoConnectionReuseStrategy())
137                .build();
138    }
139
140
141    public void performTests(final ProgressListener listener,
142            final FCSTestProfile profile,
143            final String endpointURI,
144            final String searchTerm,
145            final boolean strictMode,
146            final boolean performProbeQuest,
147            final int connectTimeout,
148            final int socketTimeout) {
149        if (listener == null) {
150            throw new NullPointerException("listener == null");
151        }
152        try {
153            executor.submit(new Runnable() {
154                @Override
155                public void run() {
156                    try {
157                        FCSTestContext context = null;
158                        if (performProbeQuest) {
159                            doPerformProbeRequest(listener, endpointURI);
160                        }
161                        // auto-detect FCS version?
162                        if (profile == null) {
163                            context = doPerformAutodetect(listener,
164                                    endpointURI,
165                                    searchTerm,
166                                    strictMode,
167                                    connectTimeout,
168                                    socketTimeout);
169                        } else {
170                            context = new FCSTestContext(profile,
171                                    endpointURI,
172                                    searchTerm,
173                                    strictMode,
174                                    connectTimeout,
175                                    socketTimeout);
176                        }
177                        context.init();
178                        doPerformTests(listener, context);
179                    } catch (IOException e) {
180                        listener.onError("An error occurred", e);
181                    } catch (SRUClientException e) {
182                        listener.onError(e.getMessage(), e.getCause());
183                    } catch (Throwable t) {
184                        logger.error("Internal error!", t);
185                        listener.onError("Internal error!", t);
186                    }
187                }
188            });
189        } catch (RejectedExecutionException e) {
190            listener.onError("Error starting tests", null);
191        }
192    }
193
194
195    private FCSTestContext doPerformAutodetect(final ProgressListener listener,
196            final String endpointURI,
197            final String searchTerm,
198            final boolean strictMode,
199            final int connectTimeout,
200            final int socketTimeout) throws SRUClientException {
201        listener.updateProgress("Detecting CLARIN-FCS profile ...");
202
203        FCSTestProfile profile = null;
204
205        SRUClient client = new ClarinFCSClientBuilder()
206                .addDefaultDataViewParsers()
207                .setDefaultSRUVersion(SRUVersion.VERSION_2_0)
208                .unknownDataViewAsString()
209                .enableLegacySupport()
210                .registerExtraResponseDataParser(
211                        new ClarinFCSEndpointDescriptionParser())
212                .buildClient();
213
214        try {
215            SRUExplainRequest request = new SRUExplainRequest(endpointURI);
216            request.setStrictMode(false);
217            request.setVersion(SRUVersion.VERSION_1_2);
218            request.setExtraRequestData(ClarinFCSConstants.X_FCS_ENDPOINT_DESCRIPTION,
219                    ClarinFCSConstants.TRUE);
220            request.setParseRecordDataEnabled(true);
221            SRUExplainResponse response = client.explain(request);
222
223            ClarinFCSEndpointDescription ed =
224                    response.getFirstExtraResponseData(ClarinFCSEndpointDescription.class);
225            if (ed != null) {
226                if (ed.getVersion() == 1) {
227                    profile = FCSTestProfile.CLARIN_FCS_1_0;
228                }
229            } else {
230                logger.debug("assume legacy");
231                profile = FCSTestProfile.CLARIN_FCS_LEGACY;
232            }
233
234            if (profile == null) {
235                request = new SRUExplainRequest(endpointURI);
236                request.setStrictMode(false);
237                request.setVersion(SRUVersion.VERSION_2_0);
238                request.setExtraRequestData(
239                        ClarinFCSConstants.X_FCS_ENDPOINT_DESCRIPTION,
240                        ClarinFCSConstants.TRUE);
241                request.setParseRecordDataEnabled(true);
242                try {
243                    response = client.explain(request);
244
245                    ed = response.getFirstExtraResponseData(
246                            ClarinFCSEndpointDescription.class);
247                    if (ed != null) {
248                        if (ed.getVersion() == 2) {
249                            profile = FCSTestProfile.CLARIN_FCS_2_0;
250                        }
251                    }
252                } catch (SRUClientException e) {
253                    if ((e.getMessage() != null) && (e.getMessage()
254                            .contains("responded with different version"))) {
255                        throw new SRUClientException(
256                                "Seriously broken Endpoint: when trying to " +
257                                "detect FCS 2.0 the Endpoint illegally " +
258                                "responded with a SRU 1.2 reponse to a " +
259                                "SRU 2.0 request!");
260                    } else {
261                        throw e;
262                    }
263                }
264            }
265            if (profile != null) {
266                final FCSTestContext context =
267                        new FCSTestContext(profile,
268                                endpointURI,
269                                searchTerm,
270                                strictMode,
271                                connectTimeout,
272                                socketTimeout);
273                return context;
274            }
275        } catch (SRUClientException e) {
276            logger.error("error", e);
277            throw new SRUClientException("An error occured while " +
278                    "auto-detecting CLARIN-FCS version", e);
279        }
280        throw new SRUClientException("Unable to auto-detect CLARIN-FCS version!");
281    }
282
283
284    private void doPerformTests(final ProgressListener listener,
285            final FCSTestContext context) {
286        /* make sure that we'll capture the logging records */
287        java.util.logging.Logger l =
288                java.util.logging.Logger.getLogger("eu.clarin.sru");
289        l.setLevel(Level.FINEST);
290        boolean found = false;
291        Handler[] handlers = l.getHandlers();
292        if (handlers != null) {
293            for (Handler handler : handlers) {
294                if (handler == logcapturehandler) {
295                    found = true;
296                    break;
297                }
298            }
299        }
300        if (!found) {
301            l.addHandler(logcapturehandler);
302        }
303
304
305        List<FCSTest> tests = new ArrayList<FCSTest>();
306        for (FCSTest test : TESTS) {
307            final FCSTestCase tc =
308                    test.getClass().getAnnotation(FCSTestCase.class);
309            if (Arrays.binarySearch(tc.profiles(), context.getProfile()) >= 0) {
310                tests.add(test);
311            }
312        }
313
314        List<FCSTestResult> results = null;
315
316        final SRUClient client = context.getClient();
317        final int totalCount = tests.size();
318        int num = 1;
319        listener.updateMaximum(totalCount);
320        for (FCSTest test : tests) {
321            if (results == null) {
322                results = new LinkedList<FCSTestResult>();
323            }
324            logger.debug("running test {}:{}", num, test.getName());
325            final String message = String.format(
326                    "Performing test \"%s:%s\" (%d/%d) ...",
327                    context.getProfile().toDisplayString(),
328                    test.getName(), num, totalCount);
329            listener.updateProgress(message);
330            num++;
331
332            FCSTestResult result = null;
333            try {
334                logcapturehandler.publish(new LogRecord(Level.FINE,
335                        "running test class " + test.getClass().getName()));
336                result = test.perform(context, client);
337                result.setLogRecords(logcapturehandler.getLogRecords());
338            } catch (SRUClientException e) {
339                String msg = e.getMessage();
340                if (msg == null) {
341                    msg = "unclassified exception from SRUClient";
342                }
343                final LogRecord record = new LogRecord(Level.SEVERE, msg);
344                record.setThrown(e);
345                logcapturehandler.publish(record);
346                result = new FCSTestResult(test,
347                        FCSTestResult.Code.ERROR,
348                        msg,
349                        logcapturehandler.getLogRecords());
350            } catch (Throwable t) {
351                final LogRecord record = new LogRecord(Level.SEVERE,
352                        "The endpoint tester as triggered an internal error! " +
353                        "Please report to developers.");
354                logcapturehandler.publish(record);
355                result = new FCSTestResult(test,
356                        FCSTestResult.Code.ERROR,
357                        record.getMessage(),
358                        logcapturehandler.getLogRecords());
359                logger.error("an internal error occured", t);
360            }
361            results.add(result);
362        } // for
363        listener.onDone(context, results);
364    }
365
366
367    private void doPerformProbeRequest(final ProgressListener listener,
368            final String baseURI) throws IOException {
369        try {
370            logger.debug("performing initial probe request to {}",
371                    baseURI);
372            listener.updateProgress(
373                    "Performing HTTP HEAD probe request ...");
374            final CloseableHttpClient client =
375                    createHttpClient(DEFAULT_CONNECT_TIMEOUT,
376                            DEFAULT_SOCKET_TIMEOUT);
377            final HttpHead request = new HttpHead(baseURI);
378            HttpResponse response = null;
379            try {
380                response = client.execute(request);
381                StatusLine status = response.getStatusLine();
382                if (status.getStatusCode() != HttpStatus.SC_OK) {
383                    throw new IOException("Probe request to endpoint " +
384                            "returned unexpected HTTP status " +
385                            status.getStatusCode());
386                }
387            } finally {
388                HttpClientUtils.closeQuietly(response);
389
390                HttpClientUtils.closeQuietly(client);
391            }
392        } catch (ClientProtocolException e) {
393            throw new IOException(e);
394        }
395    }
396
397
398    @Override
399    public void contextInitialized(ServletContextEvent event) {
400        logger.info("initialized");
401    }
402
403
404    @Override
405    public void contextDestroyed(ServletContextEvent event) {
406        logger.info("shutting down ...");
407        INSTANCE.destroy();
408    }
409
410
411    static {
412        List<FCSTest> tests = null;
413        try {
414            Reflections reflections = new Reflections(TESTCASE_PACKAGE);
415            Set<Class<?>> annotations =
416                    reflections.getTypesAnnotatedWith(FCSTestCase.class);
417            if ((annotations != null) && !annotations.isEmpty()) {
418                List<Class<?>> classes = new ArrayList<Class<?>>(
419                        annotations.size());
420                for (Class<?> clazz : annotations) {
421                    FCSTestCase tc = clazz.getAnnotation(FCSTestCase.class);
422                    if ((tc != null) && (tc.priority() >= 0)) {
423                        classes.add(clazz);
424                    }
425                }
426                Collections.sort(classes, new Comparator<Class<?>>() {
427                    @Override
428                    public int compare(Class<?> o1, Class<?> o2) {
429                        FCSTestCase a1 = o1.getAnnotation(FCSTestCase.class);
430                        FCSTestCase a2 = o2.getAnnotation(FCSTestCase.class);
431                        return a1.priority() - a2.priority();
432                    }
433                });
434                for (Class<?> clazz : classes) {
435                    if (tests == null) {
436                        tests = new ArrayList<FCSTest>(classes.size());
437                    }
438                    tests.add((FCSTest) clazz.newInstance());
439                }
440            }
441        } catch (InstantiationException e) {
442            e.printStackTrace();
443        } catch (IllegalAccessException e) {
444            e.printStackTrace();
445        }
446
447        if (tests != null) {
448            TESTS = Collections.unmodifiableList(tests);
449        } else {
450            TESTS = Collections.emptyList();
451        }
452    }
453
454} // class SRUEndpointTester
Note: See TracBrowser for help on using the repository browser.