001package org.apache.turbine.services.urlmapper;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import static org.junit.jupiter.api.Assertions.assertEquals;
023import static org.junit.jupiter.api.Assertions.assertNotNull;
024import static org.junit.jupiter.api.Assertions.assertTrue;
025import static org.junit.jupiter.api.Assertions.fail;
026
027import java.util.ArrayList;
028import java.util.List;
029import java.util.Spliterator;
030import java.util.SplittableRandom;
031import java.util.concurrent.atomic.AtomicInteger;
032import java.util.concurrent.atomic.AtomicLong;
033import java.util.function.IntConsumer;
034import java.util.stream.IntStream;
035
036import javax.servlet.http.HttpServletRequest;
037import javax.servlet.http.HttpServletResponse;
038
039import org.apache.fulcrum.parser.ParameterParser;
040import org.apache.turbine.Turbine;
041import org.apache.turbine.pipeline.PipelineData;
042import org.apache.turbine.services.TurbineServices;
043import org.apache.turbine.test.BaseTestCase;
044import org.apache.turbine.util.RunData;
045import org.apache.turbine.util.TurbineConfig;
046import org.apache.turbine.util.uri.TemplateURI;
047import org.junit.jupiter.api.AfterEach;
048import org.junit.jupiter.api.BeforeEach;
049import org.junit.jupiter.api.Tag;
050import org.junit.jupiter.api.Test;
051import org.mockito.Mockito;
052
053public class TurbineURLMapperServiceTest extends BaseTestCase
054{
055    private TurbineConfig tc = null;
056
057    private URLMapperService urlMapper = null;
058
059    @BeforeEach
060    public void setUp() throws Exception
061    {
062        tc =
063                new TurbineConfig(
064                        ".",
065                        "/conf/test/TurbineURLMapperServiceTest.properties");
066        tc.initialize();
067
068        urlMapper = (URLMapperService) TurbineServices.getInstance().getService(URLMapperService.SERVICE_NAME);
069    }
070
071    @AfterEach
072    public void tearDown() throws Exception
073    {
074        if (tc != null)
075        {
076            tc.dispose();
077        }
078    }
079
080    /**
081     * Tests
082     *
083     * <code>scheme://bob/wow/damn2/bookId/123</code>
084     * <code>scheme://bob/wow/book/123</code>
085     * <p>
086     * and
087     *
088     * <code>scheme://bob/wow/damn2/bookId/123/template/Book.vm?detail=1&amp;detail=2&amp;view=collapsed</code>
089     * <code>scheme://bob/wow/book/123/1?view=collapsed</code>
090     *
091     * @throws Exception
092     */
093    @Test
094    public void testMapToURL() throws Exception
095    {
096        assertNotNull(urlMapper);
097        HttpServletRequest request = getMockRequest();
098        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
099
100        PipelineData pipelineData = getPipelineData(request, response, tc.getTurbine().getServletConfig());
101        assertNotNull(pipelineData);
102
103        TemplateURI uri = new TemplateURI(pipelineData.getRunData());
104        uri.clearResponse(); // avoid encoding on mocked HTTPServletResponse
105        uri.addPathInfo("bookId", 123);
106        uri.setTemplate("Book.vm");
107        uri.addQueryData("detail", 0);
108
109        urlMapper.mapToURL(uri);
110        assertEquals("/wow/book/123", uri.getRelativeLink());
111        assertTrue(uri.getPathInfo().isEmpty());
112        assertTrue(uri.getQueryData().isEmpty());
113
114        uri = new TemplateURI(pipelineData.getRunData());
115        uri.clearResponse(); // avoid encoding on mocked HTTPServletResponse
116        uri.addPathInfo("bookId", 123);
117        uri.setTemplate("Book.vm");
118        uri.addQueryData("detail", 1);
119        uri.addQueryData("detail", 2);
120        uri.addQueryData("view", "collapsed");
121
122        urlMapper.mapToURL(uri);
123        assertEquals("/wow/book/123/1?view=collapsed", uri.getRelativeLink());
124        assertTrue(uri.getPathInfo().isEmpty());
125        assertEquals(1, uri.getQueryData().size());
126    }
127
128    /**
129     * Tests
130     *
131     * <code>scheme:///app/book/123/4</code>
132     * <code>scheme:///wow/damn2/detail/4/bookId/123</code>
133     *
134     * @throws Exception
135     */
136    @Test
137    public void testMapFromURL() throws Exception
138    {
139        assertNotNull(urlMapper);
140        HttpServletRequest request = getMockRequest();
141        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
142
143        PipelineData pipelineData = getPipelineData(request, response, tc.getTurbine().getServletConfig());
144        assertNotNull(pipelineData);
145        ParameterParser pp = pipelineData.get(Turbine.class, ParameterParser.class);
146        assertNotNull(pp);
147        assertTrue(pp.keySet().isEmpty());
148
149        urlMapper.mapFromURL("/app/book/123/4", pp);
150
151        assertEquals(3, pp.keySet().size());
152        assertEquals(123, pp.getInt("bookId"));
153        assertEquals("Book.vm", pp.getString("template"));
154        assertEquals(4, pp.getInt("detail"));
155
156        // double check
157        TemplateURI uri = new TemplateURI(pipelineData.getRunData());
158        uri.clearResponse();
159        uri.addPathInfo(pp);
160        assertEquals("/wow/damn2/detail/4/bookId/123", uri.getRelativeLink());
161        urlMapper.mapToURL(uri);
162        assertEquals("/wow/book/123/4", uri.getRelativeLink());
163    }
164
165
166    @Tag("performance")
167    @Test
168    public void testPerformance() throws Exception
169    {
170        assertNotNull(urlMapper);
171        int templateURIs = 5;
172        List<AtomicLong> counterSum = new ArrayList<>();
173        List<AtomicInteger> counters = new ArrayList<>();
174        for (int i = 0; i < templateURIs; i++)
175        {
176            counters.add(i, new AtomicInteger(0));
177            counterSum.add(i, new AtomicLong(0L));
178        }
179        int calls = 10_000; // above 1024, set max total of parser pool2 in fulcrum component configuration   ..
180        boolean parallel = false;
181        IntStream range = IntStream.range(0, calls);
182        if (parallel)
183        {
184            range = range.parallel();
185        }
186
187        SplittableRandom sr = new SplittableRandom();
188
189//        range
190//        .peek(e -> System.out.println("current value: " + e))
191//        .forEach( actionInt -> {
192//              runCheck(templateURIs, counterSum, counters, parallel, sr);
193//        });
194
195        Spliterator.OfInt spliterator1 = range.spliterator();
196        Spliterator.OfInt spliterator2 = spliterator1.trySplit();
197
198        System.out.println("s1 estimateSize: " + spliterator1.estimateSize());
199        spliterator1.forEachRemaining((IntConsumer) i ->
200        {
201            runCheck(templateURIs, counterSum, counters, parallel, sr);
202        });
203        System.out.println("s2 estimateSize: " + spliterator2.estimateSize());
204        spliterator2.forEachRemaining((IntConsumer) i ->
205        {
206            runCheck(templateURIs, counterSum, counters, parallel, sr);
207        });
208
209        for (int i = 0; i < counters.size() - 1; i++)
210        {
211            long time = counterSum.get(i).longValue() / 1_000_000;
212            int count = counters.get(i).get();
213            TemplateURI turi = getURI(i);
214            String relativeLink = turi.getRelativeLink();
215            callMapToUrl(turi);
216            System.out.printf("time = %dms (%d calls),average time = %5.3fmics, uri=%s, map=%s%n", time, count,
217                    count > 0 ? ((double) time * 1000 / count) : 0,
218                    relativeLink, turi.getRelativeLink());
219        }
220        System.out.printf("total time = %dms (%d total calls) parallel:%s%n",
221                counterSum.stream().mapToInt(AtomicLong::intValue).sum() / 1_000_000,
222                counters.stream().mapToInt(AtomicInteger::intValue).sum(),
223                parallel
224        );
225    }
226
227    private void runCheck(int templateURIs, List<AtomicLong> counterSum, List<AtomicInteger> counters, boolean parallel,
228                          SplittableRandom sr)
229    {
230        int randomNum = sr.nextInt(templateURIs);
231        TemplateURI turi = getURI(randomNum);
232        long time = System.nanoTime();
233        try
234        {
235            callMapToUrl(turi);
236        }
237        finally
238        {
239            time = System.nanoTime() - time;
240            counterSum.get(randomNum).addAndGet(time);
241            counters.get(randomNum).incrementAndGet();
242        }
243    }
244
245    /**
246     * to get a fresh URI
247     *
248     * @param tnr
249     * @return
250     */
251    private TemplateURI getURI(int tnr)
252    {
253        TemplateURI turi = null;
254        switch (tnr)
255        {
256            case 0:
257                turi = getURI1();
258                break;
259            case 1:
260                turi = getURI2();
261                break;
262            case 2:
263                turi = getURI3();
264                break;
265            case 3:
266                turi = getURI4();
267                break;
268            case 4:
269                turi = getURI5();
270                break;
271            default:
272                break;
273        }
274        return turi;
275    }
276
277    private TemplateURI getURI1()
278    {
279        TemplateURI uri = new TemplateURI(getRunData());
280        uri.clearResponse(); // avoid encoding on mocked HTTPServletResponse
281        uri.addPathInfo("bookId", 123);
282        uri.setTemplate("Book.vm");
283        uri.addQueryData("detail", 0);
284        return uri;
285    }
286
287    private TemplateURI getURI2()
288    {
289        TemplateURI uri2 = new TemplateURI(getRunData());
290        uri2.clearResponse();
291        uri2.addPathInfo("bookId", 123);
292        uri2.setTemplate("Book.vm");
293        uri2.addQueryData("detail", 1);
294        uri2.addQueryData("detail", 2);
295        uri2.addQueryData("view", "collapsed");
296        return uri2;
297    }
298
299    private TemplateURI getURI3()
300    {
301        TemplateURI uri3 = new TemplateURI(getRunData());
302        uri3.clearResponse();
303        uri3.addPathInfo("id", 1234);
304        uri3.addPathInfo("role", "guest");
305        uri3.addPathInfo("language", "de");
306        return uri3;
307    }
308
309    private TemplateURI getURI4()
310    {
311        TemplateURI uri4 = new TemplateURI(getRunData());
312        uri4.clearResponse();
313        uri4.addPathInfo("js_pane", "random-id-123-abc");
314        uri4.addPathInfo("role", "anon");
315        uri4.addPathInfo("media-type", "html");
316        uri4.setTemplate("Registerone.vm");
317        return uri4;
318    }
319
320    private TemplateURI getURI5()
321    {
322        TemplateURI uri5 = new TemplateURI(getRunData());
323        uri5.clearResponse();
324        uri5.addPathInfo("js_pane", "another-random-id-876-dfg");
325        uri5.addPathInfo("role", "anon");
326        uri5.addPathInfo("media-type", "html");
327        uri5.addPathInfo("page", "Contact");
328        return uri5;
329    }
330
331    private RunData getRunData()
332    {
333        HttpServletRequest request = getMockRequest();
334        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
335        try
336        {
337            PipelineData pipelineData = getPipelineData(request, response, tc.getTurbine().getServletConfig());
338            assertNotNull(pipelineData);
339            return pipelineData.getRunData();
340        }
341        catch (Exception e)
342        {
343            fail();
344        }
345        return null;
346    }
347
348    private void callMapToUrl(TemplateURI uri)
349    {
350        urlMapper.mapToURL(uri);
351        assertTrue(uri.getPathInfo().isEmpty(), "path is not empty:" + uri.getPathInfo());
352    }
353}