Am So., 24. Feb. 2019 um 19:40 Uhr schrieb Daniel Dekany <[email protected]
>:

> Sunday, February 24, 2019, 12:45:48 AM, Christoph Rüger wrote:
>
> > Thanks you very much. This looks great.
> > Our testsuite passes all freemarker related tests.
> >
> > Also some simple manual tests were successful e.g.
> > <#list row.cols?map(col -> col.title)?filter(ct -> ct?contains("name"))
> as
> coltitle >>${coltitle!}<#sep>,</#sep></#list>
> > <#list description?split(" ")?filter(word -> word == "test") as
> filteredWord>>${filteredWord}</#list>
> >
> > Regarding other use-cases for lazy-evaluation:
> >
> > Do you think a ?count (as terminal operation) without building up a list
> in
> > memory?
> > E.g.
> > Number of products < 1000: ${products?filter(it -> it.price <
> 1000)?count}
> >
> > (I tested ${products?filter(it -> it.price < 1000)?size} but I think this
> > builds up a list in memory which can be large)
>
> Good point! We don't need to introduce ?count for this though, simply
> ?size has to be made smarter.
>

Right, that's better. Let's use ?size.


>
> > Or what about other aggregations for numerical values like sum, avg, min,
> > max e.g.:
> > Cheapest price of products < 1000: ${products?filter(it -> it.price <
> > 1000)?map(it -> it.price)?min}
>
> We have ?min and ?max, they also need to be made smarter.


Ok. If it can be done efficiently then good. As ?min ?max are already there
I guess people would expect to use them...as I did.


> As of ?sum
> and ?acg... they are doable. But aggregates are mostly useful combined
> with "group by". Technically, we can do all that, but I'm not 100%
> sure if it's wise to do.

The more such operations the template
> language supports, the more people will tend to move such calculations
> from the Java code that builds the data-model to the template. But the
> good practice was always the opposite of that: only do presentation
> logic in templates, not "business" calculations. But then again, there
> are the situations when the template authors have little control over
> the data model (typically, when you make a report/mail template for
> some business application that was developed by another party), and
> then they are happy that they can get the work done in the template.
> So, it's not easy to decide which compromise is the better.
>

Yeah difficult decision.... We are such an application you mention where
the template is the only tool available.

Maybe groupBy and aggregates like ?sum, ?avg should not be part of it for
now. I think Grouping requires keeping data in memory... that's pretty
powerful but performance critical. Or have a way to enable/disable it with
configuration.

Speaking of configuration / customization: We have built something like
groupBy ourselves outside the template language (for a special table
object). We added some "safety" stuff to abort processing if the data grows
too large over a defined threshold. It would be good to be able to
customize this too.



> BTW, the kind of "group by" operation that doesn't aggregate anything,
> just splits the collection into smaller collections for each distinct
> "group by" value, like Collectors.groupBy in Java 8, would be often
> useful for presentation logic. The use case is that you get a List of
> rows, and let's say you have a Year column in there, and the List is
> ordered by that. Then you should render it so that you don't show the
> same year again and again. Like if it's a HTML table then you are
> supposed to use td.@rowspan in the year column. It's quite awkward to
> do such thing currently (try it and you will see...), despite that
> it's a common requirement and is clearly a presentation decision, not
> really "business logic". So, we could introduce a built-in for that:
> collection?splitBy(lambdaOrFunctionOrMethod). I would avoid calling it
> groupBy, since that has a different meaning in SQL (the aggregating
> groupBy veriant). So now rowspan by year would be like:
>
>   <table>
>     <tr>
>       <th>Year</th>
>       <th>Department</th>
>       <th>Income</th>
>     </tr>
>     <#list rows?groupBy(row -> row.year) as sameYearRows>
>       <#list sameYearRows as row>
>         <tr>
>           <#if row?isFirst>
>             <td rowspan="${sameYearRows?size}">${row.year}</td>
>           </#if>
>           <td>${row.department}</td>
>           <td>${row.income}</td>
>         </tr>
>       </#list>
>     <#list>
>   </table>
>
> Or if you have to print a new H2 and table for each year:
>
>   <#list rows?groupBy(row -> row.year) as sameYearRows>
>     <h2>${sameYearRows[0].year}<h2>
>     <table>
>       <tr>
>         <th>Department</th>
>         <th>Income</th>
>       </tr>
>       <#list sameYearRows as row>
>         <tr>
>           <td>${row.department}</td>
>           <td>${row.income}</td>
>         </tr>
>       </#list>
>     </table>
>   <#list>
>
>
Yes that can be handy. But the implementation should be careful to avoid
large in-memory structures for the "groups"
-> Does ?groupBy(row -> row.year) create a new List for each group? Or is
it kind of a "Views" on the original list?


> Lambdas happen to be handy with this as well. Actually, some long
> existing built-ins, like ?sortBy, ?contains, etc. could use them as
> well (a bit tricky for them technically due to backward compatibility
> requirements, but I belive I can work that around.)
>

Overall those lambdas allow some great new use cases with pretty concise
syntax. That is good. The ?filter and ?map built-ins are very cool already.
If some existing built-ins will be made "smarter" (as you said), then it
will be a great enhancement.

Speaking for us, performance and memory usage is always a concern. So it
would be good to keep an eye on avoiding large new in-memory structures.

My thoughts so far.
Thanks
Christoph


>
> > Didn't think this through, but just had the idea while testing.
> >
> > Anyway, good work!
> >
> >
> >
> >
> >
> >
> > Am Sa., 23. Feb. 2019 um 18:19 Uhr schrieb Daniel Dekany <
> [email protected]
> >>:
> >
> >> I have pushed an implementation of the restricted lambdas we were
> >> talking about. Guys, please review/test it.
> >>
> >> I call these "local lambdas" in the source code (but I'm open for
> >> suggestions), as the function they define can only be called in the
> >> same variable scope where the lambda was (as we have no closures in
> >> the template language, nor final variables). Also, they can only be
> >> used as the parameters of the built-ins that explicitly support them.
> >> As the subject shows, the main goal was just support filtering for
> >> #list, without a nested #if, as that breaks #sep, #else, it?hasNext,
> >> it?index, etc. (I will try to make more universal lambdas in FM3... I
> >> guess it would be way too tricky in FM2.)
> >>
> >> For now, I have only added two built-ins that support lambdas: ?filter
> >> and ?map. Any ideas what other such built-ins would be often useful in
> >> templates (with use case, if possible)?
> >>
> >> Examples of using ?filter and ?map:
> >>
> >>   <#list products?filter(it -> it.price < 1000) as product>
> >>     ${product.name}
> >>   </#list>
> >>
> >>   <#list products?map(it -> it.name) as name>
> >>     ${name}
> >>   </#list>
> >>
> >> Of course these built-ins aren't specific to #list, they can be used
> >> anywhere. Naturally, they can be chained as well:
> >>
> >>   <#assign chepProdNames = products
> >>      ?filter(it -> it.price < 1000)
> >>      ?map(it -> it.name)
> >>   >
> >>
> >> As a side note, ?filter and ?map also accepts FTL function-s and Java
> >> methods as its parameter, not only lambdas.
> >>
> >> A tricky aspect of this feature is lazy evaluation, in similar sense
> >> as Java 8 Stream intermediate operations are lazy. On most places,
> >> ?filer and ?map are eager, that is, they return a completed sequence
> >> of items. That's because our restricted lambdas only work correctly
> >> "locally". However, there are very common situations where it's clear
> >> that we can use lazy ("streaming") evaluation, as we know where the
> >> resulting stream of elements will be consumed:
> >>
> >> - One such case is when these kind of built-ins are chained. Like in
> >>   the last example, ?filter doesn't construct a List in memory,
> >>   instead the elements just flow through it (some is dropped, as it's
> >>   filtering), into ?map. Only the ?map at the end of the chain will
> >>   built a List eagerly. Some other built-ins also allow the left-hand
> >>   built-in to stream, like in ?filter(...)?map(...)?join(", ") no List
> >>   is built anywhere, instead the elements flow through both ?filter
> >>   and ?map, and ?join just appends them to the StringBuilder where it
> >>   creates its results.
> >>
> >> - #list also enables lazy evaluation to its 1st parameter. So in the
> >>   #list examples above, yet again no List is built in memory.
> >>
> >> - There are other such cases, which I didn't implement yet. For
> >>   example the sequence slice operator, like, xs?filter(f)[10..20],
> >>   should allow lazy evaluation.
> >>
> >> Feedback is welcome!
> >>
> >>
> >> Monday, December 17, 2018, 11:39:36 AM, Christoph Rüger wrote:
> >>
> >> > Hey Daniel,
> >> > I'm very sorry, but I didn't make any progress with this. I think I
> was a
> >> > bit over-motivated, but unfortunately I cannot spend more time on this
> >> for
> >> > various reasons.
> >> > You can take this over. I'm glad to help out with testing and
> feedback.
> >> >
> >> > Thanks
> >> > Christoph
> >> >
> >> >
> >> >
> >> >
> >> > Am Mo., 17. Dez. 2018 um 11:04 Uhr schrieb Daniel Dekany <
> >> [email protected]
> >> >>:
> >> >
> >> >> Any progress in this? I think I will give it a try in the coming days
> >> >> otherwise.
> >> >>
> >> >>
> >> >> Sunday, November 18, 2018, 10:31:29 PM, Daniel Dekany wrote:
> >> >>
> >> >> > See my answers inline...
> >> >> >
> >> >> > Sunday, November 18, 2018, 8:44:40 PM, Christoph Rüger wrote:
> >> >> >
> >> >> >> Thanks Daniel for your feedback. See my answers below
> >> >> >>
> >> >> >> Am So., 11. Nov. 2018 um 19:14 Uhr schrieb Daniel Dekany <
> >> >> [email protected]
> >> >> >>>:
> >> >> >>
> >> >> >>> Sunday, November 11, 2018, 11:40:50 AM, Christoph Rüger wrote:
> >> >> >>>
> >> >> >>> > Am So., 11. Nov. 2018 um 09:25 Uhr schrieb Daniel Dekany <
> >> >> >>> [email protected]
> >> >> >>> >>:
> >> >> >>> >
> >> >> >>> >> Saturday, November 10, 2018, 3:08:14 PM, Denis Bredelet wrote:
> >> >> >>> >>
> >> >> >>> >> > Hi,
> >> >> >>> >> >
> >> >> >>> >> > Le 9 novembre 2018 à 22:36, Christoph Rüger <
> >> [email protected]>
> >> >> a
> >> >> >>> >> écrit :
> >> >> >>> >> >
> >> >> >>> >> > Am Fr., 9. Nov. 2018 um 22:55 Uhr schrieb Daniel Dekany <
> >> >> >>> >> [email protected]
> >> >> >>> >> > :
> >> >> >>> >> >
> >> >> >>> >> > It's certainly tricky, but as far as I see possible (but
> then,
> >> who
> >> >> >>> >> >
> >> >> >>> >> > knows what will one find when actually working on it). It's
> >> also a
> >> >> >>> >> > feature missing a lot. It's especially missing for #list (I
> >> know
> >> >> that
> >> >> >>> >> > you need it for something else), because if you filter the
> >> items
> >> >> >>> >> > inside #list with #if-s, then #sep, ?hasNext, etc. will not
> be
> >> >> usable.
> >> >> >>> >> >
> >> >> >>> >> > Let me say that I disagree here.
> >> >> >>> >> >
> >> >> >>> >> > I do not think that closures are required for FreeMarker,
> nor
> >> that
> >> >> >>> they
> >> >> >>> >> are a good idea.
> >> >> >>> >> >
> >> >> >>> >> > If we add new features to the FreeMarker *tempate engine* I
> >> would
> >> >> >>> >> > rather we focus on multi-part macro body rather than an
> >> advanced
> >> >> >>> >> language feature like closures.
> >> >> >>> >> >
> >> >> >>> >> > You can add ?filter and ?map if you want, a simple
> expression
> >> as
> >> >> >>> >> parameter should be enough.
> >> >> >>> >>
> >> >> >>> >> Yes, as I said, we certainly start with only allowing lambdas
> in
> >> >> >>> >> ?filter/?map, also certainly in ?contains.
> >> >> >>> >>
> >> >> >>> > Would be enough in my opinion and very useful.
> >> >> >>> >
> >> >> >>> > Is it possiblefor you to give some pointers to the code on how
> >> this
> >> >> could
> >> >> >>> > be implemented? I would maybe like to wrap my head around this
> a
> >> >> little
> >> >> >>> bit.
> >> >> >>>
> >> >> >>> Please feel yourself encouraged! (:
> >> >> >>>
> >> >> >>> > I started looking at seq_containsBI (
> >> >> >>> >
> >> >> >>>
> >> >>
> >>
> https://github.com/apache/freemarker/blob/a03a1473b65d9819674b285a0538fed824f37478/src/main/java/freemarker/core/BuiltInsForSequences.java#L291
> >> >> >>> )
> >> >> >>> > and
> >> >> >>> > and reverseBI (
> >> >> >>> >
> >> >> >>>
> >> >>
> >>
> https://github.com/apache/freemarker/blob/a03a1473b65d9819674b285a0538fed824f37478/src/main/java/freemarker/core/BuiltInsForSequences.java#L264
> >> >> >>> )
> >> >> >>> > just to find something related (seq_containsBI checks
> something)
> >> and
> >> >> >>> > reverseBI returns a new sequence.
> >> >> >>> > What I haven't found is a function which takes an Expression
> as a
> >> >> >>> > parameter.
> >> >> >>> > Is there something similar already or would that be a new
> thing?
> >> >> >>>
> >> >> >>> It's a new thing in that it will be part of the expression syntax
> >> >> >>> (even if for now we will only allow lambdas as the parameters of
> a
> >> few
> >> >> >>> built-ins, so that we can get away without closures). So it's a
> new
> >> >> >>> Expression subclass, and has to be part of the parser (ftl.jj) as
> >> >> >>> well.
> >> >> >>
> >> >> >> Hmm, that parser stuff is new for me, it'll take me some time to
> get
> >> >> into
> >> >> >> it.
> >> >> >
> >> >> > So it's a JavaCC lexer+parser. With a few twists... sorry, but this
> >> >> > code has history... :)
> >> >> >
> >> >> >>> As of lazy evaluation of parameters expressions, that's already
> >> >> >>> done in the built-ins in BuiltInsWithParseTimeParameters, and you
> >> will
> >> >> >>> see it's trivial to do, but the situation there is much simpler.
> >> >> >>>
> >> >> >>
> >> >> >>
> >> >> >>>
> >> >> >>> In principle, a LambdaExpression should evaluate to a
> >> >> >>> TemplateMethodModelEx, and then you pass that
> TemplateMethodModelEx
> >> to
> >> >> >>> the called built-in or whatever it is. But with the approach of
> >> >> >>> BuiltInsWithParseTimeParameters we can certainly even skip that,
> and
> >> >> >>> just bind to the LambdaExpression directly, add a LocalContext
> that
> >> >> >>> contains the lambda arguments, end evaluate the LambdaExpression
> >> right
> >> >> >>> there, in the built-in implementation. Or at least at a very
> quick
> >> >> >>> glance I think so.
> >> >> >>>
> >> >> >> Not sure I can follow completely but that hint with*
> >> >> >> BuiltInsWithParseTimeParameters* got me started, but at the moment
> >> I'm
> >> >> >> stuck as I need to get more familiar with the internals of
> >> Freemarker.
> >> >> I am
> >> >> >> also not sure I am on the same page regarding the syntax we are
> >> aiming
> >> >> for
> >> >> >> and why I would need to extend the parser when there is something
> >> like
> >> >> >> BuiltInsWithParseTimeParameters....
> >> >> >
> >> >> > I assumed that the syntax will be similar to the Java lambda syntax
> >> >> > (see below why), and that's of course needs lexer/parser changes.
> >> >> >
> >> >> >> Here is an example what I have in mind:
> >> >> >> I started with the ?filter() builtin. I had a syntax like this in
> >> mind:
> >> >> >>
> >> >> >> *Example 1: ["a","b","c"]?filter(element, element == "c")*
> >> >> >> *Example 2: ["a","b","c"]?filter(element, element ==
> >> someOtherVariable,
> >> >> >> someOtherVariable="c")*
> >> >> >
> >> >> > Looking at the above one believe that the value of `element` is
> passed
> >> >> > in as first argument, and the the value of `element == "c"` as the
> >> >> > second, but that's not the case. It's much better if it's visible
> that
> >> >> > you got some kind of anonymous function definition there without
> >> >> > knowing about the "filter" built-in.
> >> >> >
> >> >> > Java already has a syntax for expressing this kind of thing, so it
> >> >> > would be better to use that familiar syntax. As far as I know it
> >> >> > doesn't conflict with ours (it kind of it does, but not fatally).
> >> >> >
> >> >> > Also, even if for now we only allow this in said built-ins, we
> >> >> > shouldn't exclude the possibility of making this kind of expression
> >> >> > accessible elsewhere as well. Then, on many places the parsed won't
> >> >> > know what kind of value is expected (consider passing a lambda to
> an
> >> >> > user defined directive for example), so a syntax like above won't
> >> >> > work.
> >> >> >
> >> >> >> Not sure if that's what you have in mind too, but to me it made
> sense
> >> >> with
> >> >> >> regards to BuiltInsWithParseTimeParameters and I could start
> without
> >> >> >> touching parser stuff.
> >> >> >
> >> >> > BuiltInsWithParseTimeParameters doesn't affect the syntax (only a
> >> >> > bit...), as a call to a such built-in looks like a call to any
> other
> >> >> > built-in.
> >> >> >
> >> >> >> *1st argument 'element'* would just be the iterator variable
> similar
> >> to
> >> >> >> <#list ["a","b","c"] as *element*>
> >> >> >> 2nd argument is the filter lambda expression... aka our filter
> >> condition
> >> >> >> 3rd+n argument are optional parameters in case used in the lambda
> >> >> expression
> >> >> >
> >> >> > #list is special, similarly as `for ... in ...` is in most
> languages.
> >> >> > It's not lambda-ish either; you can't write `as element/2` or such.
> >> >> >
> >> >> >> So at first I was looking how <#list> works and found
> IteratorBlock,
> >> and
> >> >> >> though I could reuse it somehow.
> >> >> >>
> >> >> >> Here is some simple pseudo code I played around for the for
> Example
> >> 1:
> >> >> >>
> >> >> >> static class filter_BI extends BuiltInWithParseTimeParameters {
> >> >> >>
> >> >> >>
> >> >> >>
> >> >> >>         TemplateModel _eval(Environment env) throws
> >> TemplateException {
> >> >> >>
> >> >> >>             // sequence
> >> >> >>
> >> >> >>         TemplateModel targetValue = target.evalToNonMissing(env);
> >> >> >>
> >> >> >>
> >> >> >>
> >> >> >>             List parameters = this.parameters;
> >> >> >>
> >> >> >>
> >> >> >>
> >> >> >>             Expression iteratorAlias = (Expression)
> >> parameters.get(0);
> >> >> >>
> >> >> >>             Expression conditionExpression = (Expression)
> >> >> parameters.get(1);
> >> >> >>
> >> >> >>
> >> >> >>
> >> >> >>             TemplateSequenceModel seq =  (TemplateSequenceModel)
> >> >> target.eval
> >> >> >> (env);
> >> >> >>
> >> >> >>             for (int i = 0; i < seq.size(); i++) {
> >> >> >>
> >> >> >>                TemplateModel cur = seq.get(i);
> >> >> >>
> >> >> >>
> >> >> >>                // this is where I am stuck at the moment
> >> >> >>
> >> >> >>                // I basically want to evaluate conditionExpression
> >> >> >> where iteratorAlias
> >> >> >> is basically what I passed as 'element'
> >> >> >>
> >> >> >>                // I am not sure if or how LocalContext could come
> >> into
> >> >> play
> >> >> >> here
> >> >> >>
> >> >> >>                // basically for each iteration I would assign the
> >> >> current
> >> >> >> loop element to a context variable with the name 'element'
> >> >> >>
> >> >> >>                // and then evaluate conditionExpression with that
> >> >> context.
> >> >> >>
> >> >> >>                // if conditionExpression is "true" then I would
> >> populate
> >> >> >> add the current sequence element 'cur'
> >> >> >>
> >> >> >>                // to a new result-List.... and return that....
> >> something
> >> >> >>
> >> >> >>                // I wanted to reuse IteratorBlock here somehow,
> but
> >> >> didn't
> >> >> >> get it to work yet.
> >> >> >>
> >> >> >>                // maybe this is a stupid idea, or we just need
> >> something
> >> >> >> similar
> >> >> >>
> >> >> >>
> >> >> >>
> >> >> >>             }
> >> >> >>
> >> >> >> }
> >> >> >>
> >> >> >>
> >> >> >>
> >> >> >> Ok so far for my pseudo code....  Maybe you could give some more
> >> >> pointers
> >> >> >> based on that... in case this makes any sense ...
> >> >> >
> >> >> > For LocalContext, Environment has a pushLocalContext method. See
> the
> >> >> > calls to it, like in visit(TemplateElement[],
> TemplateDirectiveModel,
> >> >> > Map, List).
> >> >> >
> >> >> > To avoid running into more new things at once than necessary,
> perhaps
> >> >> > you should start with seq?seq_contains(lambda). The end result
> should
> >> >> > be like:
> >> >> >
> >> >> >   users?contains(u -> u.paying)
> >> >> >
> >> >> >>> Another similarity to BuiltInsWithParseTimeParameters is that we
> >> won't
> >> >> >>> allow separating the `?someBI` and `(arg)`. Like, with the
> example
> >> of
> >> >> >>> `cond?then(1, 2)`, you aren't allowed to do this:
> >> >> >>>
> >> >> >>>   <#assign t=cond?then>
> >> >> >>>   ${t(1, 2)}
> >> >> >>>
> >> >> >>> That maybe looks natural, but most other built-ins allow that. Of
> >> >> >>> course we can't allow that for ?filter etc., because then we need
> >> >> >>> closures again.
> >> >> >>>
> >> >> >>> >> Multi-part macro body is also planned. Means, I know it
> >> definitely
> >> >> >>> >> should be added, but who knows when that's done... I mean,
> it's
> >> like
> >> >> >>> >> that for what, a decade? (: It's not even decided what it
> exactly
> >> >> >>> >> does, as there are many ways of approaching this. (I have my
> own
> >> >> idea
> >> >> >>> >> about what the right compromise would be, but others has other
> >> >> >>> >> ideas...)
> >> >> >>> >>
> >> >> >>> >> Filtering lists bothers me because the template language
> should
> >> be
> >> >> >>> >> (and somewhat indeed is) specialized on listing things on
> fancy
> >> ways
> >> >> >>> >> that used to come up when generating document-like output.
> (If it
> >> >> >>> >> doesn't do things like that, you might as well use a general
> >> purpose
> >> >> >>> >> language.) Thus, that filter is unsolved (filtering with #if
> is
> >> >> >>> >> verbose and spoils #sep etc.) bothers me a lot.
> >> >> >>> >>
> >> >> >>> >> BTW, ?filter and ?map is also especially handy in our case as
> >> >> >>> >> FreeMarker doesn't support building new sequences (sequences
> are
> >> >> >>> >> immutable). Although it has sequence concatenation with `+`,
> it's
> >> >> not
> >> >> >>> >> good for building a sequence one by one, unless the sequence
> >> will be
> >> >> >>> >> quite short.
> >> >> >>> >>
> >> >> >>> > Good point.
> >> >> >>> >
> >> >> >>> >
> >> >> >>> >>
> >> >> >>> >> > Cheers,
> >> >> >>> >> > -- Denis.
> >> >> >>> >>
> >> >> >>> >> --
> >> >> >>> >> Thanks,
> >> >> >>> >>  Daniel Dekany
> >> >> >>> >>
> >> >> >>> >>
> >> >> >>> >
> >> >> >>>
> >> >> >>> --
> >> >> >>> Thanks,
> >> >> >>>  Daniel Dekany
> >> >> >>>
> >> >> >>>
> >> >> >> Thanks
> >> >> >> Christoph
> >> >> >>
> >> >> >
> >> >>
> >> >> --
> >> >> Thanks,
> >> >>  Daniel Dekany
> >> >>
> >> >>
> >> >
> >> > --
> >> > Christoph Rüger, Geschäftsführer
> >> > Synesty <https://synesty.com/> - Anbinden und Automatisieren ohne
> >> > Programmieren - Automatisierung, Schnittstellen, Datenfeeds
> >> >
> >> > Xing: https://www.xing.com/profile/Christoph_Rueger2
> >> > LinkedIn: http://www.linkedin.com/pub/christoph-rueger/a/685/198
> >> >
> >>
> >> --
> >> Thanks,
> >>  Daniel Dekany
> >>
> >>
> >
>
> --
> Thanks,
>  Daniel Dekany
>
>

-- 
Synesty GmbH
Moritz-von-Rohr-Str. 1a
07745 Jena
Tel.: +49 3641 
5596493Internet: https://synesty.com <https://synesty.com>
Informationen 
zum Datenschutz: https://synesty.com/datenschutz 
<https://synesty.com/datenschutz>

Geschäftsführer: Christoph Rüger

Unternehmenssitz: Jena
Handelsregister B beim Amtsgericht: Jena

Handelsregister-Nummer: HRB 508766
Ust-IdNr.: DE287564982

Reply via email to