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     */
017    package org.apache.camel.builder;
018    
019    import java.text.SimpleDateFormat;
020    import java.util.Collection;
021    import java.util.Collections;
022    import java.util.Comparator;
023    import java.util.Date;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Scanner;
027    import java.util.regex.Pattern;
028    
029    import org.apache.camel.Endpoint;
030    import org.apache.camel.Exchange;
031    import org.apache.camel.Expression;
032    import org.apache.camel.Message;
033    import org.apache.camel.NoSuchEndpointException;
034    import org.apache.camel.Producer;
035    import org.apache.camel.impl.ExpressionAdapter;
036    import org.apache.camel.language.bean.BeanLanguage;
037    import org.apache.camel.spi.Language;
038    import org.apache.camel.util.ExchangeHelper;
039    import org.apache.camel.util.ObjectHelper;
040    
041    /**
042     * A helper class for working with <a href="http://camel.apache.org/expression.html">expressions</a>.
043     *
044     * @version $Revision: 780937 $
045     */
046    public final class ExpressionBuilder {
047    
048        /**
049         * Utility classes should not have a public constructor.
050         */
051        private ExpressionBuilder() {
052        }
053    
054        /**
055         * Returns an expression for the header value with the given name
056         *
057         * @param headerName the name of the header the expression will return
058         * @return an expression object which will return the header value
059         */
060        public static Expression headerExpression(final String headerName) {
061            return new ExpressionAdapter() {
062                public Object evaluate(Exchange exchange) {
063                    Object header = exchange.getIn().getHeader(headerName);
064                    if (header == null) {
065                        // fall back on a property
066                        header = exchange.getProperty(headerName);
067                    }
068                    return header;
069                }
070    
071                @Override
072                public String toString() {
073                    return "header(" + headerName + ")";
074                }
075            };
076        }
077    
078        /**
079         * Returns an expression for the inbound message headers
080         *
081         * @return an expression object which will return the inbound headers
082         */
083        public static Expression headersExpression() {
084            return new ExpressionAdapter() {
085                public Object evaluate(Exchange exchange) {
086                    return exchange.getIn().getHeaders();
087                }
088    
089                @Override
090                public String toString() {
091                    return "headers";
092                }
093            };
094        }
095    
096        /**
097         * Returns an expression for the out header value with the given name
098         *
099         * @param headerName the name of the header the expression will return
100         * @return an expression object which will return the header value
101         */
102        public static Expression outHeaderExpression(final String headerName) {
103            return new ExpressionAdapter() {
104                public Object evaluate(Exchange exchange) {
105                    if (!exchange.hasOut()) {
106                        return null;
107                    }
108    
109                    Message out = exchange.getOut();
110                    Object header = out.getHeader(headerName);
111                    if (header == null) {
112                        // lets try the exchange header
113                        header = exchange.getProperty(headerName);
114                    }
115                    return header;
116                }
117    
118                @Override
119                public String toString() {
120                    return "outHeader(" + headerName + ")";
121                }
122            };
123        }
124    
125        /**
126         * Returns an expression for the outbound message headers
127         *
128         * @return an expression object which will return the headers
129         */
130        public static Expression outHeadersExpression() {
131            return new ExpressionAdapter() {
132                public Object evaluate(Exchange exchange) {
133                    return exchange.getOut().getHeaders();
134                }
135    
136                @Override
137                public String toString() {
138                    return "outHeaders";
139                }
140            };
141        }
142    
143        /**
144         * Returns an expression for an exception set on the exchange
145         *
146         * @see Exchange#getException()
147         * @return an expression object which will return the exception set on the exchange
148         */
149        public static Expression exchangeExceptionExpression() {
150            return new ExpressionAdapter() {
151                public Object evaluate(Exchange exchange) {
152                    Exception exception = exchange.getException();
153                    if (exception == null) {
154                        exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
155                    }
156                    return exception;
157                }
158    
159                @Override
160                public String toString() {
161                    return "exchangeException";
162                }
163            };
164        }   
165        
166        /**
167         * Returns an expression for an exception set on the exchange
168         * <p/>
169         * Is used to get the caused exception that typically have been wrapped in some sort
170         * of Camel wrapper exception
171         * @param type the exception type
172         * @see Exchange#getException(Class)
173         * @return an expression object which will return the exception set on the exchange
174         */
175        public static Expression exchangeExceptionExpression(final Class<Exception> type) {
176            return new ExpressionAdapter() {
177                public Object evaluate(Exchange exchange) {
178                    Exception exception = exchange.getException(type);
179                    if (exception == null) {
180                        exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
181                        // must use exception iterator to walk it and find the type we are looking for
182                        Iterator<Throwable> it = ObjectHelper.createExceptionIterator(exception);
183                        while (it.hasNext()) {
184                            Throwable e = it.next();
185                            if (type.isInstance(e)) {
186                                return type.cast(e);
187                            }
188                        }
189                        // not found
190                        return null;
191    
192                    }
193                    return exception;
194                }
195    
196                @Override
197                public String toString() {
198                    return "exchangeException[" + type + "]";
199                }
200            };
201        }
202    
203        /**
204         * Returns an expression for the type converter
205         *
206         * @return an expression object which will return the type converter
207         */
208        public static Expression typeConverterExpression() {
209            return new ExpressionAdapter() {
210                public Object evaluate(Exchange exchange) {
211                    return exchange.getContext().getTypeConverter();
212                }
213    
214                @Override
215                public String toString() {
216                    return "typeConverter";
217                }
218            };
219        }
220    
221        /**
222         * Returns an expression for an exception message set on the exchange
223         *
224         * @see <tt>Exchange.getException().getMessage()</tt>
225         * @return an expression object which will return the exception message set on the exchange
226         */
227        public static Expression exchangeExceptionMessageExpression() {
228            return new ExpressionAdapter() {
229                public Object evaluate(Exchange exchange) {
230                    Exception exception = exchange.getException();
231                    if (exception == null) {
232                        exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
233                    }
234                    return exception != null ? exception.getMessage() : null;
235                }
236    
237                @Override
238                public String toString() {
239                    return "exchangeExceptionMessage";
240                }
241            };
242        }
243    
244        /**
245         * Returns an expression for the property value of exchange with the given name
246         *
247         * @param propertyName the name of the property the expression will return
248         * @return an expression object which will return the property value
249         */
250        public static Expression propertyExpression(final String propertyName) {
251            return new ExpressionAdapter() {
252                public Object evaluate(Exchange exchange) {
253                    return exchange.getProperty(propertyName);
254                }
255    
256                @Override
257                public String toString() {
258                    return "property(" + propertyName + ")";
259                }
260            };
261        }
262    
263        /**
264         * Returns an expression for the properties of exchange
265         *
266         * @return an expression object which will return the properties
267         */
268        public static Expression propertiesExpression() {
269            return new ExpressionAdapter() {
270                public Object evaluate(Exchange exchange) {
271                    return exchange.getProperties();
272                }
273    
274                @Override
275                public String toString() {
276                    return "properties";
277                }
278            };
279        }
280        
281        /**
282         * Returns an expression for the properties of the camel context
283         *
284         * @return an expression object which will return the properties
285         */
286        public static Expression camelContextPropertiesExpression() {
287            return new ExpressionAdapter() {
288                public Object evaluate(Exchange exchange) {
289                    return exchange.getContext().getProperties();
290                }
291    
292                @Override
293                public String toString() {
294                    return "camelContextProperties";
295                }
296            };
297        }
298        
299        /**
300         * Returns an expression for the property value of the camel context with the given name
301         *
302         * @param propertyName the name of the property the expression will return
303         * @return an expression object which will return the property value
304         */
305        public static Expression camelContextPropertyExpression(final String propertyName) {
306            return new ExpressionAdapter() {
307                public Object evaluate(Exchange exchange) {
308                    return exchange.getContext().getProperties().get(propertyName);
309                }
310    
311                @Override
312                public String toString() {
313                    return "camelContextProperty(" + propertyName + ")";
314                }
315            };
316        }
317    
318        /**
319         * Returns an expression for a system property value with the given name
320         *
321         * @param propertyName the name of the system property the expression will return
322         * @return an expression object which will return the system property value
323         */
324        public static Expression systemPropertyExpression(final String propertyName) {
325            return systemPropertyExpression(propertyName, null);
326        }
327    
328        /**
329         * Returns an expression for a system property value with the given name
330         *
331         * @param propertyName the name of the system property the expression will return
332         * @param defaultValue default value to return if no system property exists
333         * @return an expression object which will return the system property value
334         */
335        public static Expression systemPropertyExpression(final String propertyName,
336                                                          final String defaultValue) {
337            return new ExpressionAdapter() {
338                public Object evaluate(Exchange exchange) {
339                    return System.getProperty(propertyName, defaultValue);
340                }
341    
342                @Override
343                public String toString() {
344                    return "systemProperty(" + propertyName + ")";
345                }
346            };
347        }
348    
349        /**
350         * Returns an expression for the constant value
351         *
352         * @param value the value the expression will return
353         * @return an expression object which will return the constant value
354         */
355        public static Expression constantExpression(final Object value) {
356            return new ExpressionAdapter() {
357                public Object evaluate(Exchange exchange) {
358                    return value;
359                }
360    
361                @Override
362                public String toString() {
363                    return "" + value;
364                }
365            };
366        }
367    
368        /**
369         * Returns the expression for the exchanges inbound message body
370         */
371        public static Expression bodyExpression() {
372            return new ExpressionAdapter() {
373                public Object evaluate(Exchange exchange) {
374                    return exchange.getIn().getBody();
375                }
376    
377                @Override
378                public String toString() {
379                    return "body";
380                }
381            };
382        }
383    
384        /**
385         * Returns the expression for the exchanges inbound message body converted
386         * to the given type
387         */
388        public static <T> Expression bodyExpression(final Class<T> type) {
389            return new ExpressionAdapter() {
390                public Object evaluate(Exchange exchange) {
391                    return exchange.getIn().getBody(type);
392                }
393    
394                @Override
395                public String toString() {
396                    return "bodyAs[" + type.getName() + "]";
397                }
398            };
399        }
400    
401        /**
402         * Returns the expression for the exchanges inbound message body type
403         */
404        public static Expression bodyTypeExpression() {
405            return new ExpressionAdapter() {
406                public Object evaluate(Exchange exchange) {
407                    return exchange.getIn().getBody().getClass();
408                }
409    
410                @Override
411                public String toString() {
412                    return "bodyType";
413                }
414            };
415        }
416    
417        /**
418         * Returns the expression for the out messages body
419         */
420        public static Expression outBodyExpression() {
421            return new ExpressionAdapter() {
422                public Object evaluate(Exchange exchange) {
423                    if (exchange.hasOut()) {
424                        return exchange.getOut().getBody();
425                    } else {
426                        return null;
427                    }
428                }
429    
430                @Override
431                public String toString() {
432                    return "outBody";
433                }
434            };
435        }
436    
437        /**
438         * Returns the expression for the exchanges outbound message body converted
439         * to the given type
440         */
441        public static <T> Expression outBodyExpression(final Class<T> type) {
442            return new ExpressionAdapter() {
443                public Object evaluate(Exchange exchange) {
444                    if (exchange.hasOut()) {
445                        return exchange.getOut().getBody(type);
446                    } else {
447                        return null;
448                    }
449                }
450    
451                @Override
452                public String toString() {
453                    return "outBodyAs[" + type.getName() + "]";
454                }
455            };
456        }
457    
458        /**
459         * Returns the expression for the fault messages body
460         */
461        public static Expression faultBodyExpression() {
462            return new ExpressionAdapter() {
463                public Object evaluate(Exchange exchange) {
464                    return exchange.getFault().getBody();
465                }
466    
467                @Override
468                public String toString() {
469                    return "faultBody";
470                }
471            };
472        }
473    
474        /**
475         * Returns the expression for the exchanges fault message body converted
476         * to the given type
477         */
478        public static <T> Expression faultBodyExpression(final Class<T> type) {
479            return new ExpressionAdapter() {
480                public Object evaluate(Exchange exchange) {
481                    return exchange.getFault().getBody(type);
482                }
483    
484                @Override
485                public String toString() {
486                    return "faultBodyAs[" + type.getName() + "]";
487                }
488            };
489        }
490    
491        /**
492         * Returns the expression for the exchange
493         */
494        public static Expression exchangeExpression() {
495            return new ExpressionAdapter() {
496                public Object evaluate(Exchange exchange) {
497                    return exchange;
498                }
499    
500                @Override
501                public String toString() {
502                    return "exchange";
503                }
504            };
505        }
506    
507        /**
508         * Returns the expression for the IN message
509         */
510        public static Expression inMessageExpression() {
511            return new ExpressionAdapter() {
512                public Object evaluate(Exchange exchange) {
513                    return exchange.getIn();
514                }
515    
516                @Override
517                public String toString() {
518                    return "inMessage";
519                }
520            };
521        }
522    
523        /**
524         * Returns the expression for the OUT message
525         */
526        public static Expression outMessageExpression() {
527            return new ExpressionAdapter() {
528                public Object evaluate(Exchange exchange) {
529                    return exchange.getOut();
530                }
531    
532                @Override
533                public String toString() {
534                    return "outMessage";
535                }
536            };
537        }
538    
539        /**
540         * Returns an expression which converts the given expression to the given type
541         */
542        @SuppressWarnings("unchecked")
543        public static Expression convertToExpression(final Expression expression, final Class type) {
544            return new ExpressionAdapter() {
545                public Object evaluate(Exchange exchange) {
546                    return expression.evaluate(exchange, type);
547                }
548    
549                @Override
550                public String toString() {
551                    return "" + expression + ".convertTo(" + type.getCanonicalName() + ".class)";
552                }
553            };
554        }
555    
556        /**
557         * Returns an expression which converts the given expression to the given type the type
558         * expression is evaluted to
559         */
560        public static Expression convertToExpression(final Expression expression, final Expression type) {
561            return new ExpressionAdapter() {
562                public Object evaluate(Exchange exchange) {
563                    return expression.evaluate(exchange, type.evaluate(exchange, Object.class).getClass());
564                }
565    
566                @Override
567                public String toString() {
568                    return "" + expression + ".convertToEvaluatedType(" + type + ")";
569                }
570            };
571        }
572    
573        /**
574         * Returns a tokenize expression which will tokenize the string with the
575         * given token
576         */
577        public static Expression tokenizeExpression(final Expression expression,
578                                                    final String token) {
579            return new ExpressionAdapter() {
580                public Object evaluate(Exchange exchange) {
581                    Object value = expression.evaluate(exchange, Object.class);
582                    Scanner scanner = ObjectHelper.getScanner(exchange, value);
583                    scanner.useDelimiter(token);
584                    return scanner;
585                }
586    
587                @Override
588                public String toString() {
589                    return "tokenize(" + expression + ", " + token + ")";
590                }
591            };
592        }
593    
594        /**
595         * Returns a tokenize expression which will tokenize the string with the
596         * given regex
597         */
598        public static Expression regexTokenizeExpression(final Expression expression,
599                                                         final String regexTokenizer) {
600            final Pattern pattern = Pattern.compile(regexTokenizer);
601            return new ExpressionAdapter() {
602                public Object evaluate(Exchange exchange) {
603                    Object value = expression.evaluate(exchange, Object.class);
604                    Scanner scanner = ObjectHelper.getScanner(exchange, value);
605                    scanner.useDelimiter(regexTokenizer);
606                    return scanner;
607                }
608    
609                @Override
610                public String toString() {
611                    return "regexTokenize(" + expression + ", " + pattern.pattern() + ")";
612                }
613            };
614        }
615    
616        /**
617         * Returns a sort expression which will sort the expression with the given comparator.
618         * <p/>
619         * The expression is evaluted as a {@link List} object to allow sorting.
620         */
621        @SuppressWarnings("unchecked")
622        public static Expression sortExpression(final Expression expression, final Comparator comparator) {
623            return new ExpressionAdapter() {
624                public Object evaluate(Exchange exchange) {
625                    List list = expression.evaluate(exchange, List.class);
626                    Collections.sort(list, comparator);
627                    return list;
628                }
629    
630                @Override
631                public String toString() {
632                    return "sort(" + expression + " by: " + comparator + ")";
633                }
634            };
635        }
636    
637        /**
638         * Transforms the expression into a String then performs the regex
639         * replaceAll to transform the String and return the result
640         */
641        public static Expression regexReplaceAll(final Expression expression,
642                                                 final String regex, final String replacement) {
643            final Pattern pattern = Pattern.compile(regex);
644            return new ExpressionAdapter() {
645                public Object evaluate(Exchange exchange) {
646                    String text = expression.evaluate(exchange, String.class);
647                    if (text == null) {
648                        return null;
649                    }
650                    return pattern.matcher(text).replaceAll(replacement);
651                }
652    
653                @Override
654                public String toString() {
655                    return "regexReplaceAll(" + expression + ", " + pattern.pattern() + ")";
656                }
657            };
658        }
659    
660        /**
661         * Transforms the expression into a String then performs the regex
662         * replaceAll to transform the String and return the result
663         */
664        public static Expression regexReplaceAll(final Expression expression,
665                                                 final String regex, final Expression replacementExpression) {
666    
667            final Pattern pattern = Pattern.compile(regex);
668            return new ExpressionAdapter() {
669                public Object evaluate(Exchange exchange) {
670                    String text = expression.evaluate(exchange, String.class);
671                    String replacement = replacementExpression.evaluate(exchange, String.class);
672                    if (text == null || replacement == null) {
673                        return null;
674                    }
675                    return pattern.matcher(text).replaceAll(replacement);
676                }
677    
678                @Override
679                public String toString() {
680                    return "regexReplaceAll(" + expression + ", " + pattern.pattern() + ")";
681                }
682            };
683        }
684    
685        /**
686         * Appends the String evaluations of the two expressions together
687         */
688        public static Expression append(final Expression left, final Expression right) {
689            return new ExpressionAdapter() {
690                public Object evaluate(Exchange exchange) {
691                    return left.evaluate(exchange, String.class) + right.evaluate(exchange, String.class);
692                }
693    
694                @Override
695                public String toString() {
696                    return "append(" + left + ", " + right + ")";
697                }
698            };
699        }
700    
701        /**
702         * Prepends the String evaluations of the two expressions together
703         */
704        public static Expression prepend(final Expression left, final Expression right) {
705            return new ExpressionAdapter() {
706                public Object evaluate(Exchange exchange) {
707                    return right.evaluate(exchange, String.class) + left.evaluate(exchange, String.class);
708                }
709    
710                @Override
711                public String toString() {
712                    return "prepend(" + left + ", " + right + ")";
713                }
714            };
715        }
716    
717        /**
718         * Returns an expression which returns the string concatenation value of the various
719         * expressions
720         *
721         * @param expressions the expression to be concatenated dynamically
722         * @return an expression which when evaluated will return the concatenated values
723         */
724        public static Expression concatExpression(final Collection<Expression> expressions) {
725            return concatExpression(expressions, null);
726        }
727    
728        /**
729         * Returns an expression which returns the string concatenation value of the various
730         * expressions
731         *
732         * @param expressions the expression to be concatenated dynamically
733         * @param expression the text description of the expression
734         * @return an expression which when evaluated will return the concatenated values
735         */
736        public static Expression concatExpression(final Collection<Expression> expressions, final String expression) {
737            return new ExpressionAdapter() {
738                public Object evaluate(Exchange exchange) {
739                    StringBuffer buffer = new StringBuffer();
740                    for (Expression expression : expressions) {
741                        String text = expression.evaluate(exchange, String.class);
742                        if (text != null) {
743                            buffer.append(text);
744                        }
745                    }
746                    return buffer.toString();
747                }
748    
749                @Override
750                public String toString() {
751                    if (expression != null) {
752                        return expression;
753                    } else {
754                        return "concat" + expressions;
755                    }
756                }
757            };
758        }
759    
760        /**
761         * Returns an Expression for the inbound message id
762         */
763        public static Expression messageIdExpression() {
764            return new ExpressionAdapter() {
765                public Object evaluate(Exchange exchange) {
766                    return exchange.getIn().getMessageId();
767                }
768    
769                @Override
770                public String toString() {
771                    return "messageId";
772                }
773            };
774        }
775    
776        public static Expression dateExpression(final String command, final String pattern) {
777            return new ExpressionAdapter() {
778                public Object evaluate(Exchange exchange) {
779                    Date date;
780                    if ("now".equals(command)) {
781                        date = new Date();
782                    } else if (command.startsWith("header.") || command.startsWith("in.header.")) {
783                        String key = command.substring(command.lastIndexOf('.') + 1);
784                        date = exchange.getIn().getHeader(key, Date.class);
785                        if (date == null) {
786                            throw new IllegalArgumentException("Cannot find java.util.Date object at " + command);
787                        }
788                    } else if (command.startsWith("out.header.")) {
789                        String key = command.substring(command.lastIndexOf('.') + 1);
790                        date = exchange.getOut().getHeader(key, Date.class);
791                        if (date == null) {
792                            throw new IllegalArgumentException("Cannot find java.util.Date object at " + command);
793                        }
794                    } else {
795                        throw new IllegalArgumentException("Command not supported for dateExpression: " + command);
796                    }
797    
798                    SimpleDateFormat df = new SimpleDateFormat(pattern);
799                    return df.format(date);
800                }
801    
802                @Override
803                public String toString() {
804                    return "date(" + command + ":" + pattern + ")";
805                }
806            };
807        }
808    
809        public static Expression simpleExpression(final String expression) {
810            return new ExpressionAdapter() {
811                public Object evaluate(Exchange exchange) {
812                    // resolve language using context to have a clear separation of packages
813                    // must call evalute to return the nested langauge evaluate when evaluating
814                    // stacked expressions
815                    Language language = exchange.getContext().resolveLanguage("simple");
816                    return language.createExpression(expression).evaluate(exchange, Object.class);
817                }
818    
819                @Override
820                public String toString() {
821                    return "simple(" + expression + ")";
822                }
823            };
824        }
825    
826        public static Expression beanExpression(final String expression) {
827            return new ExpressionAdapter() {
828                public Object evaluate(Exchange exchange) {
829                    // resolve language using context to have a clear separation of packages
830                    // must call evaluate to return the nested language evaluate when evaluating
831                    // stacked expressions
832                    Language language = exchange.getContext().resolveLanguage("bean");
833                    return language.createExpression(expression).evaluate(exchange, Object.class);
834                }
835    
836                @Override
837                public String toString() {
838                    return "bean(" + expression + ")";
839                }
840            };
841        }
842        
843        public static Expression beanExpression(final Class beanType, final String methodName) {
844            return BeanLanguage.bean(beanType, methodName);        
845        }
846    
847        public static Expression beanExpression(final String beanRef, final String methodName) {
848            String expression = methodName != null ? beanRef + "." + methodName : beanRef;
849            return beanExpression(expression);
850        }
851    
852        /**
853         * Returns an expression processing the exchange to the given endpoint uri
854         *
855         * @param uri endpoint uri to send the exchange to
856         * @return an expression object which will return the OUT body
857         */
858        public static Expression toExpression(final String uri) {
859            return new ExpressionAdapter() {
860                public Object evaluate(Exchange exchange) {
861                    Endpoint endpoint = exchange.getContext().getEndpoint(uri);
862                    if (endpoint == null) {
863                        throw new NoSuchEndpointException(uri);
864                    }
865    
866                    Producer producer;
867                    try {
868                        producer = endpoint.createProducer();
869                        producer.start();
870                        producer.process(exchange);
871                        producer.stop();
872                    } catch (Exception e) {
873                        throw ObjectHelper.wrapRuntimeCamelException(e);
874                    }
875    
876                    // return the OUT body, but check for exchange pattern
877                    if (ExchangeHelper.isOutCapable(exchange)) {
878                        return exchange.getOut().getBody();
879                    } else {
880                        return exchange.getIn().getBody();
881                    }
882                }
883    
884                @Override
885                public String toString() {
886                    return "to(" + uri + ")";
887                }
888            };
889        }
890    
891    
892    }