001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util;
018
019import java.net.URI;
020import java.net.URISyntaxException;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Iterator;
024import java.util.LinkedHashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.concurrent.atomic.AtomicLong;
028import java.util.regex.Matcher;
029import java.util.regex.Pattern;
030import java.util.regex.PatternSyntaxException;
031
032import org.apache.camel.CamelContext;
033import org.apache.camel.DelegateEndpoint;
034import org.apache.camel.Endpoint;
035import org.apache.camel.Exchange;
036import org.apache.camel.ExchangePattern;
037import org.apache.camel.Message;
038import org.apache.camel.PollingConsumer;
039import org.apache.camel.Processor;
040import org.apache.camel.ResolveEndpointFailedException;
041import org.apache.camel.Route;
042import org.apache.camel.spi.BrowsableEndpoint;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046import static org.apache.camel.util.JsonSchemaHelper.getPropertyDefaultValue;
047import static org.apache.camel.util.JsonSchemaHelper.getPropertyPrefix;
048import static org.apache.camel.util.JsonSchemaHelper.isPropertyMultiValue;
049import static org.apache.camel.util.JsonSchemaHelper.isPropertyRequired;
050import static org.apache.camel.util.ObjectHelper.after;
051
052/**
053 * Some helper methods for working with {@link Endpoint} instances
054 */
055public final class EndpointHelper {
056
057    private static final Logger LOG = LoggerFactory.getLogger(EndpointHelper.class);
058    private static final AtomicLong ENDPOINT_COUNTER = new AtomicLong(0);
059    private static final Pattern SYNTAX_PATTERN = Pattern.compile("(\\w+)");
060
061    private EndpointHelper() {
062        //Utility Class
063    }
064
065    /**
066     * Creates a {@link PollingConsumer} and polls all pending messages on the endpoint
067     * and invokes the given {@link Processor} to process each {@link Exchange} and then closes
068     * down the consumer and throws any exceptions thrown.
069     */
070    public static void pollEndpoint(Endpoint endpoint, Processor processor, long timeout) throws Exception {
071        PollingConsumer consumer = endpoint.createPollingConsumer();
072        try {
073            ServiceHelper.startService(consumer);
074
075            while (true) {
076                Exchange exchange = consumer.receive(timeout);
077                if (exchange == null) {
078                    break;
079                } else {
080                    processor.process(exchange);
081                }
082            }
083        } finally {
084            try {
085                ServiceHelper.stopAndShutdownService(consumer);
086            } catch (Exception e) {
087                LOG.warn("Failed to stop PollingConsumer: " + consumer + ". This example is ignored.", e);
088            }
089        }
090    }
091
092    /**
093     * Creates a {@link PollingConsumer} and polls all pending messages on the
094     * endpoint and invokes the given {@link Processor} to process each
095     * {@link Exchange} and then closes down the consumer and throws any
096     * exceptions thrown.
097     */
098    public static void pollEndpoint(Endpoint endpoint, Processor processor) throws Exception {
099        pollEndpoint(endpoint, processor, 1000L);
100    }
101
102    /**
103     * Matches the endpoint with the given pattern.
104     * <p/>
105     * The endpoint will first resolve property placeholders using {@link CamelContext#resolvePropertyPlaceholders(String)}.
106     * <p/>
107     * The match rules are applied in this order:
108     * <ul>
109     * <li>exact match, returns true</li>
110     * <li>wildcard match (pattern ends with a * and the uri starts with the pattern), returns true</li>
111     * <li>regular expression match, returns true</li>
112     * <li>otherwise returns false</li>
113     * </ul>
114     *
115     * @param context the Camel context, if <tt>null</tt> then property placeholder resolution is skipped.
116     * @param uri     the endpoint uri
117     * @param pattern a pattern to match
118     * @return <tt>true</tt> if match, <tt>false</tt> otherwise.
119     */
120    public static boolean matchEndpoint(CamelContext context, String uri, String pattern) {
121        if (context != null) {
122            try {
123                uri = context.resolvePropertyPlaceholders(uri);
124            } catch (Exception e) {
125                throw new ResolveEndpointFailedException(uri, e);
126            }
127        }
128
129        // normalize uri so we can do endpoint hits with minor mistakes and parameters is not in the same order
130        try {
131            uri = URISupport.normalizeUri(uri);
132        } catch (Exception e) {
133            throw new ResolveEndpointFailedException(uri, e);
134        }
135
136        // we need to test with and without scheme separators (//)
137        if (uri.contains("://")) {
138            // try without :// also
139            String scheme = ObjectHelper.before(uri, "://");
140            String path = after(uri, "://");
141            if (matchPattern(scheme + ":" + path, pattern)) {
142                return true;
143            }
144        } else {
145            // try with :// also
146            String scheme = ObjectHelper.before(uri, ":");
147            String path = after(uri, ":");
148            if (matchPattern(scheme + "://" + path, pattern)) {
149                return true;
150            }
151        }
152
153        // and fallback to test with the uri as is
154        return matchPattern(uri, pattern);
155    }
156
157    /**
158     * Matches the endpoint with the given pattern.
159     *
160     * @see #matchEndpoint(org.apache.camel.CamelContext, String, String)
161     * @deprecated use {@link #matchEndpoint(org.apache.camel.CamelContext, String, String)} instead.
162     */
163    @Deprecated
164    public static boolean matchEndpoint(String uri, String pattern) {
165        return matchEndpoint(null, uri, pattern);
166    }
167
168    /**
169     * Matches the name with the given pattern.
170     * <p/>
171     * The match rules are applied in this order:
172     * <ul>
173     * <li>exact match, returns true</li>
174     * <li>wildcard match (pattern ends with a * and the name starts with the pattern), returns true</li>
175     * <li>regular expression match, returns true</li>
176     * <li>otherwise returns false</li>
177     * </ul>
178     *
179     * @param name    the name
180     * @param pattern a pattern to match
181     * @return <tt>true</tt> if match, <tt>false</tt> otherwise.
182     */
183    public static boolean matchPattern(String name, String pattern) {
184        if (name == null || pattern == null) {
185            return false;
186        }
187
188        if (name.equals(pattern)) {
189            // exact match
190            return true;
191        }
192
193        if (matchWildcard(name, pattern)) {
194            return true;
195        }
196
197        if (matchRegex(name, pattern)) {
198            return true;
199        }
200
201        // no match
202        return false;
203    }
204
205    /**
206     * Matches the name with the given pattern.
207     * <p/>
208     * The match rules are applied in this order:
209     * <ul>
210     * <li>wildcard match (pattern ends with a * and the name starts with the pattern), returns true</li>
211     * <li>otherwise returns false</li>
212     * </ul>
213     *
214     * @param name    the name
215     * @param pattern a pattern to match
216     * @return <tt>true</tt> if match, <tt>false</tt> otherwise.
217     */
218    private static boolean matchWildcard(String name, String pattern) {
219        // we have wildcard support in that hence you can match with: file* to match any file endpoints
220        if (pattern.endsWith("*") && name.startsWith(pattern.substring(0, pattern.length() - 1))) {
221            return true;
222        }
223        return false;
224    }
225
226    /**
227     * Matches the name with the given pattern.
228     * <p/>
229     * The match rules are applied in this order:
230     * <ul>
231     * <li>regular expression match, returns true</li>
232     * <li>otherwise returns false</li>
233     * </ul>
234     *
235     * @param name    the name
236     * @param pattern a pattern to match
237     * @return <tt>true</tt> if match, <tt>false</tt> otherwise.
238     */
239    private static boolean matchRegex(String name, String pattern) {
240        // match by regular expression
241        try {
242            if (name.matches(pattern)) {
243                return true;
244            }
245        } catch (PatternSyntaxException e) {
246            // ignore
247        }
248        return false;
249    }
250
251    /**
252     * Sets the regular properties on the given bean
253     *
254     * @param context    the camel context
255     * @param bean       the bean
256     * @param parameters parameters
257     * @throws Exception is thrown if setting property fails
258     */
259    public static void setProperties(CamelContext context, Object bean, Map<String, Object> parameters) throws Exception {
260        IntrospectionSupport.setProperties(context.getTypeConverter(), bean, parameters);
261    }
262
263    /**
264     * Sets the reference properties on the given bean
265     * <p/>
266     * This is convention over configuration, setting all reference parameters (using {@link #isReferenceParameter(String)}
267     * by looking it up in registry and setting it on the bean if possible.
268     *
269     * @param context    the camel context
270     * @param bean       the bean
271     * @param parameters parameters
272     * @throws Exception is thrown if setting property fails
273     */
274    public static void setReferenceProperties(CamelContext context, Object bean, Map<String, Object> parameters) throws Exception {
275        Iterator<Map.Entry<String, Object>> it = parameters.entrySet().iterator();
276        while (it.hasNext()) {
277            Map.Entry<String, Object> entry = it.next();
278            String name = entry.getKey();
279            Object v = entry.getValue();
280            String value = v != null ? v.toString() : null;
281            if (value != null && isReferenceParameter(value)) {
282                boolean hit = IntrospectionSupport.setProperty(context, context.getTypeConverter(), bean, name, null, value, true);
283                if (hit) {
284                    // must remove as its a valid option and we could configure it
285                    it.remove();
286                }
287            }
288        }
289    }
290
291    /**
292     * Is the given parameter a reference parameter (starting with a # char)
293     *
294     * @param parameter the parameter
295     * @return <tt>true</tt> if its a reference parameter
296     */
297    public static boolean isReferenceParameter(String parameter) {
298        return parameter != null && parameter.trim().startsWith("#");
299    }
300
301    /**
302     * Resolves a reference parameter by making a lookup in the registry.
303     *
304     * @param <T>     type of object to lookup.
305     * @param context Camel context to use for lookup.
306     * @param value   reference parameter value.
307     * @param type    type of object to lookup.
308     * @return lookup result.
309     * @throws IllegalArgumentException if referenced object was not found in registry.
310     */
311    public static <T> T resolveReferenceParameter(CamelContext context, String value, Class<T> type) {
312        return resolveReferenceParameter(context, value, type, true);
313    }
314
315    /**
316     * Resolves a reference parameter by making a lookup in the registry.
317     *
318     * @param <T>     type of object to lookup.
319     * @param context Camel context to use for lookup.
320     * @param value   reference parameter value.
321     * @param type    type of object to lookup.
322     * @return lookup result (or <code>null</code> only if
323     * <code>mandatory</code> is <code>false</code>).
324     * @throws IllegalArgumentException if object was not found in registry and
325     *                                  <code>mandatory</code> is <code>true</code>.
326     */
327    public static <T> T resolveReferenceParameter(CamelContext context, String value, Class<T> type, boolean mandatory) {
328        String valueNoHash = StringHelper.replaceAll(value, "#", "");
329        if (mandatory) {
330            return CamelContextHelper.mandatoryLookup(context, valueNoHash, type);
331        } else {
332            return CamelContextHelper.lookup(context, valueNoHash, type);
333        }
334    }
335
336    /**
337     * Resolves a reference list parameter by making lookups in the registry.
338     * The parameter value must be one of the following:
339     * <ul>
340     * <li>a comma-separated list of references to beans of type T</li>
341     * <li>a single reference to a bean type T</li>
342     * <li>a single reference to a bean of type java.util.List</li>
343     * </ul>
344     *
345     * @param context     Camel context to use for lookup.
346     * @param value       reference parameter value.
347     * @param elementType result list element type.
348     * @return list of lookup results, will always return a list.
349     * @throws IllegalArgumentException if any referenced object was not found in registry.
350     */
351    @SuppressWarnings({"unchecked", "rawtypes"})
352    public static <T> List<T> resolveReferenceListParameter(CamelContext context, String value, Class<T> elementType) {
353        if (value == null) {
354            return new ArrayList<T>();
355        }
356        List<String> elements = Arrays.asList(value.split(","));
357        if (elements.size() == 1) {
358            Object bean = resolveReferenceParameter(context, elements.get(0).trim(), Object.class);
359            if (bean instanceof List) {
360                // The bean is a list
361                return (List) bean;
362            } else {
363                // The bean is a list element
364                return Arrays.asList(elementType.cast(bean));
365            }
366        } else { // more than one list element
367            List<T> result = new ArrayList<T>(elements.size());
368            for (String element : elements) {
369                result.add(resolveReferenceParameter(context, element.trim(), elementType));
370            }
371            return result;
372        }
373    }
374
375    /**
376     * Resolves a parameter, by doing a reference lookup if the parameter is a reference, and converting
377     * the parameter to the given type.
378     *
379     * @param <T>     type of object to convert the parameter value as.
380     * @param context Camel context to use for lookup.
381     * @param value   parameter or reference parameter value.
382     * @param type    type of object to lookup.
383     * @return lookup result if it was a reference parameter, or the value converted to the given type
384     * @throws IllegalArgumentException if referenced object was not found in registry.
385     */
386    public static <T> T resolveParameter(CamelContext context, String value, Class<T> type) {
387        T result;
388        if (EndpointHelper.isReferenceParameter(value)) {
389            result = EndpointHelper.resolveReferenceParameter(context, value, type);
390        } else {
391            result = context.getTypeConverter().convertTo(type, value);
392        }
393        return result;
394    }
395
396    /**
397     * @deprecated use {@link #resolveParameter(org.apache.camel.CamelContext, String, Class)}
398     */
399    @Deprecated
400    public static <T> T resloveStringParameter(CamelContext context, String value, Class<T> type) {
401        return resolveParameter(context, value, type);
402    }
403
404    /**
405     * Gets the route id for the given endpoint in which there is a consumer listening.
406     *
407     * @param endpoint the endpoint
408     * @return the route id, or <tt>null</tt> if none found
409     */
410    public static String getRouteIdFromEndpoint(Endpoint endpoint) {
411        if (endpoint == null || endpoint.getCamelContext() == null) {
412            return null;
413        }
414
415        List<Route> routes = endpoint.getCamelContext().getRoutes();
416        for (Route route : routes) {
417            if (route.getEndpoint().equals(endpoint)
418                    || route.getEndpoint().getEndpointKey().equals(endpoint.getEndpointKey())) {
419                return route.getId();
420            }
421        }
422        return null;
423    }
424
425    /**
426     * A helper method for Endpoint implementations to create new Ids for Endpoints which also implement
427     * {@link org.apache.camel.spi.HasId}
428     */
429    public static String createEndpointId() {
430        return "endpoint" + ENDPOINT_COUNTER.incrementAndGet();
431    }
432
433    /**
434     * Lookup the id the given endpoint has been enlisted with in the {@link org.apache.camel.spi.Registry}.
435     *
436     * @param endpoint the endpoint
437     * @return the endpoint id, or <tt>null</tt> if not found
438     */
439    public static String lookupEndpointRegistryId(Endpoint endpoint) {
440        if (endpoint == null || endpoint.getCamelContext() == null) {
441            return null;
442        }
443
444        // it may be a delegate endpoint, which we need to match as well
445        Endpoint delegate = null;
446        if (endpoint instanceof DelegateEndpoint) {
447            delegate = ((DelegateEndpoint) endpoint).getEndpoint();
448        }
449
450        Map<String, Endpoint> map = endpoint.getCamelContext().getRegistry().findByTypeWithName(Endpoint.class);
451        for (Map.Entry<String, Endpoint> entry : map.entrySet()) {
452            if (entry.getValue().equals(endpoint) || entry.getValue().equals(delegate)) {
453                return entry.getKey();
454            }
455        }
456
457        // not found
458        return null;
459    }
460
461    /**
462     * Browses the {@link BrowsableEndpoint} within the given range, and returns the messages as a XML payload.
463     *
464     * @param endpoint    the browsable endpoint
465     * @param fromIndex   from range
466     * @param toIndex     to range
467     * @param includeBody whether to include the message body in the XML payload
468     * @return XML payload with the messages
469     * @throws IllegalArgumentException if the from and to range is invalid
470     * @see MessageHelper#dumpAsXml(org.apache.camel.Message)
471     */
472    public static String browseRangeMessagesAsXml(BrowsableEndpoint endpoint, Integer fromIndex, Integer toIndex, Boolean includeBody) {
473        if (fromIndex == null) {
474            fromIndex = 0;
475        }
476        if (toIndex == null) {
477            toIndex = Integer.MAX_VALUE;
478        }
479        if (fromIndex > toIndex) {
480            throw new IllegalArgumentException("From index cannot be larger than to index, was: " + fromIndex + " > " + toIndex);
481        }
482
483        List<Exchange> exchanges = endpoint.getExchanges();
484        if (exchanges.size() == 0) {
485            return null;
486        }
487
488        StringBuilder sb = new StringBuilder();
489        sb.append("<messages>");
490        for (int i = fromIndex; i < exchanges.size() && i <= toIndex; i++) {
491            Exchange exchange = exchanges.get(i);
492            Message msg = exchange.hasOut() ? exchange.getOut() : exchange.getIn();
493            String xml = MessageHelper.dumpAsXml(msg, includeBody);
494            sb.append("\n").append(xml);
495        }
496        sb.append("\n</messages>");
497        return sb.toString();
498    }
499
500    /**
501     * Attempts to resolve if the url has an <tt>exchangePattern</tt> option configured
502     *
503     * @param url the url
504     * @return the exchange pattern, or <tt>null</tt> if the url has no <tt>exchangePattern</tt> configured.
505     * @throws URISyntaxException is thrown if uri is invalid
506     */
507    public static ExchangePattern resolveExchangePatternFromUrl(String url) throws URISyntaxException {
508        int idx = url.indexOf("?");
509        if (idx > 0) {
510            url = url.substring(idx + 1);
511        }
512        Map<String, Object> parameters = URISupport.parseQuery(url, true);
513        String pattern = (String) parameters.get("exchangePattern");
514        if (pattern != null) {
515            return ExchangePattern.asEnum(pattern);
516        }
517        return null;
518    }
519
520    /**
521     * Parses the endpoint uri and builds a map of documentation information for each option which is extracted
522     * from the component json documentation
523     *
524     * @param camelContext the Camel context
525     * @param uri          the endpoint uri
526     * @return a map for each option in the uri with the corresponding information from the json
527     * @throws Exception is thrown in case of error
528     */
529    // CHECKSTYLE:OFF
530    public static Map<String, Object> endpointProperties(CamelContext camelContext, String uri) throws Exception {
531        // NOTICE: This logic is similar to org.apache.camel.util.EndpointHelper#endpointProperties
532        // as the catalog also offers similar functionality (without having camel-core on classpath)
533
534        // need to normalize uri first
535
536        // parse the uri
537        URI u = normalizeUri(uri);
538        String scheme = u.getScheme();
539
540        String json = camelContext.getComponentParameterJsonSchema(u.getScheme());
541        if (json == null) {
542            throw new IllegalArgumentException("Cannot find endpoint with scheme " + scheme);
543        }
544
545        // grab the syntax
546        String syntax = null;
547        String alternativeSyntax = null;
548        List<Map<String, String>> rows = JsonSchemaHelper.parseJsonSchema("component", json, false);
549        for (Map<String, String> row : rows) {
550            if (row.containsKey("syntax")) {
551                syntax = row.get("syntax");
552            }
553            if (row.containsKey("alternativeSyntax")) {
554                alternativeSyntax = row.get("alternativeSyntax");
555            }
556        }
557        if (syntax == null) {
558            throw new IllegalArgumentException("Endpoint with scheme " + scheme + " has no syntax defined in the json schema");
559        }
560
561        // only if we support alternative syntax, and the uri contains the username and password in the authority
562        // part of the uri, then we would need some special logic to capture that information and strip those
563        // details from the uri, so we can continue parsing the uri using the normal syntax
564        Map<String, String> userInfoOptions = new LinkedHashMap<String, String>();
565        if (alternativeSyntax != null && alternativeSyntax.contains("@")) {
566            // clip the scheme from the syntax
567            alternativeSyntax = after(alternativeSyntax, ":");
568            // trim so only userinfo
569            int idx = alternativeSyntax.indexOf("@");
570            String fields = alternativeSyntax.substring(0, idx);
571            String[] names = fields.split(":");
572
573            // grab authority part and grab username and/or password
574            String authority = u.getAuthority();
575            if (authority != null && authority.contains("@")) {
576                String username = null;
577                String password = null;
578
579                // grab unserinfo part before @
580                String userInfo = authority.substring(0, authority.indexOf("@"));
581                String[] parts = userInfo.split(":");
582                if (parts.length == 2) {
583                    username = parts[0];
584                    password = parts[1];
585                } else {
586                    // only username
587                    username = userInfo;
588                }
589
590                // remember the username and/or password which we add later to the options
591                if (names.length == 2) {
592                    userInfoOptions.put(names[0], username);
593                    if (password != null) {
594                        // password is optional
595                        userInfoOptions.put(names[1], password);
596                    }
597                }
598            }
599        }
600
601        // clip the scheme from the syntax
602        syntax = after(syntax, ":");
603        // clip the scheme from the uri
604        uri = after(uri, ":");
605        String uriPath = stripQuery(uri);
606
607        // strip user info from uri path
608        if (!userInfoOptions.isEmpty()) {
609            int idx = uriPath.indexOf('@');
610            if (idx > -1) {
611                uriPath = uriPath.substring(idx + 1);
612            }
613        }
614
615        // strip double slash in the start
616        if (uriPath != null && uriPath.startsWith("//")) {
617            uriPath = uriPath.substring(2);
618        }
619
620        // parse the syntax and find the names of each option
621        Matcher matcher = SYNTAX_PATTERN.matcher(syntax);
622        List<String> word = new ArrayList<String>();
623        while (matcher.find()) {
624            String s = matcher.group(1);
625            if (!scheme.equals(s)) {
626                word.add(s);
627            }
628        }
629        // parse the syntax and find each token between each option
630        String[] tokens = SYNTAX_PATTERN.split(syntax);
631
632        // find the position where each option start/end
633        List<String> word2 = new ArrayList<String>();
634        int prev = 0;
635        int prevPath = 0;
636
637        // special for activemq/jms where the enum for destinationType causes a token issue as it includes a colon
638        // for 'temp:queue' and 'temp:topic' values
639        if ("activemq".equals(scheme) || "jms".equals("scheme")) {
640            if (uriPath.startsWith("temp:")) {
641                prevPath = 5;
642            }
643        }
644
645        for (String token : tokens) {
646            if (token.isEmpty()) {
647                continue;
648            }
649
650            // special for some tokens where :// can be used also, eg http://foo
651            int idx = -1;
652            int len = 0;
653            if (":".equals(token)) {
654                idx = uriPath.indexOf("://", prevPath);
655                len = 3;
656            }
657            if (idx == -1) {
658                idx = uriPath.indexOf(token, prevPath);
659                len = token.length();
660            }
661
662            if (idx > 0) {
663                String option = uriPath.substring(prev, idx);
664                word2.add(option);
665                prev = idx + len;
666                prevPath = prev;
667            }
668        }
669        // special for last or if we did not add anyone
670        if (prev > 0 || word2.isEmpty()) {
671            String option = uriPath.substring(prev);
672            word2.add(option);
673        }
674
675        rows = JsonSchemaHelper.parseJsonSchema("properties", json, true);
676
677        boolean defaultValueAdded = false;
678
679        // now parse the uri to know which part isw what
680        Map<String, String> options = new LinkedHashMap<String, String>();
681
682        // include the username and password from the userinfo section
683        if (!userInfoOptions.isEmpty()) {
684            options.putAll(userInfoOptions);
685        }
686
687        // word contains the syntax path elements
688        Iterator<String> it = word2.iterator();
689        for (int i = 0; i < word.size(); i++) {
690            String key = word.get(i);
691
692            boolean allOptions = word.size() == word2.size();
693            boolean required = isPropertyRequired(rows, key);
694            String defaultValue = getPropertyDefaultValue(rows, key);
695
696            // we have all options so no problem
697            if (allOptions) {
698                String value = it.next();
699                options.put(key, value);
700            } else {
701                // we have a little problem as we do not not have all options
702                if (!required) {
703                    String value = null;
704
705                    boolean last = i == word.size() - 1;
706                    if (last) {
707                        // if its the last value then use it instead of the default value
708                        value = it.hasNext() ? it.next() : null;
709                        if (value != null) {
710                            options.put(key, value);
711                        } else {
712                            value = defaultValue;
713                        }
714                    }
715                    if (value != null) {
716                        options.put(key, value);
717                        defaultValueAdded = true;
718                    }
719                } else {
720                    String value = it.hasNext() ? it.next() : null;
721                    if (value != null) {
722                        options.put(key, value);
723                    }
724                }
725            }
726        }
727
728        Map<String, Object> answer = new LinkedHashMap<String, Object>();
729
730        // remove all options which are using default values and are not required
731        for (Map.Entry<String, String> entry : options.entrySet()) {
732            String key = entry.getKey();
733            String value = entry.getValue();
734
735            if (defaultValueAdded) {
736                boolean required = isPropertyRequired(rows, key);
737                String defaultValue = getPropertyDefaultValue(rows, key);
738
739                if (!required && defaultValue != null) {
740                    if (defaultValue.equals(value)) {
741                        continue;
742                    }
743                }
744            }
745
746            // we should keep this in the answer
747            answer.put(key, value);
748        }
749
750        // now parse the uri parameters
751        Map<String, Object> parameters = URISupport.parseParameters(u);
752
753        // and covert the values to String so its JMX friendly
754        while (!parameters.isEmpty()) {
755            Map.Entry<String, Object> entry = parameters.entrySet().iterator().next();
756            String key = entry.getKey();
757            String value = entry.getValue() != null ? entry.getValue().toString() : "";
758
759            boolean multiValued = isPropertyMultiValue(rows, key);
760            if (multiValued) {
761                String prefix = getPropertyPrefix(rows, key);
762                // extra all the multi valued options
763                Map<String, Object> values = URISupport.extractProperties(parameters, prefix);
764                // build a string with the extra multi valued options with the prefix and & as separator
765                CollectionStringBuffer csb = new CollectionStringBuffer("&");
766                for (Map.Entry<String, Object> multi : values.entrySet()) {
767                    String line = prefix + multi.getKey() + "=" + (multi.getValue() != null ? multi.getValue().toString() : "");
768                    csb.append(line);
769                }
770                // append the extra multi-values to the existing (which contains the first multi value)
771                if (!csb.isEmpty()) {
772                    value = value + "&" + csb.toString();
773                }
774            }
775
776            answer.put(key, value);
777            // remove the parameter as we run in a while loop until no more parameters
778            parameters.remove(key);
779        }
780
781        return answer;
782    }
783    // CHECKSTYLE:ON
784
785    /**
786     * Normalizes the URI so unsafe characters is encoded
787     *
788     * @param uri the input uri
789     * @return as URI instance
790     * @throws URISyntaxException is thrown if syntax error in the input uri
791     */
792    private static URI normalizeUri(String uri) throws URISyntaxException {
793        return new URI(UnsafeUriCharactersEncoder.encode(uri, true));
794    }
795
796    /**
797     * Strips the query parameters from the uri
798     *
799     * @param uri the uri
800     * @return the uri without the query parameter
801     */
802    private static String stripQuery(String uri) {
803        int idx = uri.indexOf('?');
804        if (idx > -1) {
805            uri = uri.substring(0, idx);
806        }
807        return uri;
808    }
809
810}