[topia] branch site created (now 8650f17)
This is an automated email from the git hooks/post-receive script. New change to branch site in repository topia. See http://git.nuiton.org/topia.git at 8650f17 Create Jekyll site structure This branch includes the following new commits: new 8650f17 Create Jekyll site structure The 1 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Detailed log of new commits: commit 8650f17f458f6d31e8568fbe0f5a9c055f30a720 Author: Arnaud Thimel <thimel@codelutin.com> Date: Mon Sep 29 17:12:24 2014 +0200 Create Jekyll site structure -- To stop receiving notification emails like this one, please contact nuiton.org SCM administrator <admin+scm@nuiton.org>.
This is an automated email from the git hooks/post-receive script. New commit to branch site in repository topia. See http://git.nuiton.org/topia.git commit 8650f17f458f6d31e8568fbe0f5a9c055f30a720 Author: Arnaud Thimel <thimel@codelutin.com> Date: Mon Sep 29 17:12:24 2014 +0200 Create Jekyll site structure --- jekyll/.gitignore | 2 + jekyll/_config.yml | 20 ++ jekyll/_includes/footer.html | 61 ++++ jekyll/_includes/head.html | 20 ++ jekyll/_includes/header.html | 47 +++ jekyll/_layouts/default.html | 20 ++ jekyll/_layouts/page.html | 14 + jekyll/_layouts/post.html | 15 + jekyll/_posts/2014-09-12-topia_3.0-beta-13.md | 67 +++++ jekyll/_sass/_base.scss | 204 +++++++++++++ jekyll/_sass/_layout.scss | 236 +++++++++++++++ jekyll/_sass/_syntax-highlighting.scss | 67 +++++ jekyll/all_posts.html | 24 ++ jekyll/assets/AppTest.java | 94 ++++++ jekyll/assets/employeeAndCompany.png | Bin 0 -> 2125 bytes jekyll/assets/info.png | Bin 0 -> 819 bytes jekyll/assets/library-model.zargo | Bin 0 -> 3853 bytes jekyll/assets/library.png | Bin 0 -> 36833 bytes jekyll/assets/mandatory.png | Bin 0 -> 1987 bytes jekyll/assets/recommended.png | Bin 0 -> 1659 bytes jekyll/assets/topia_60_20.png | Bin 0 -> 2940 bytes jekyll/css/main.scss | 172 +++++++++++ jekyll/docs/model_generation.md | 75 +++++ jekyll/index.md | 112 +++++++ jekyll/tutos/from_scratch.md | 350 ++++++++++++++++++++++ jekyll/tutos/migrate_to_topia_3.md | 408 ++++++++++++++++++++++++++ 26 files changed, 2008 insertions(+) diff --git a/jekyll/.gitignore b/jekyll/.gitignore new file mode 100644 index 0000000..badbc02 --- /dev/null +++ b/jekyll/.gitignore @@ -0,0 +1,2 @@ +_site +.sass-cache diff --git a/jekyll/_config.yml b/jekyll/_config.yml new file mode 100644 index 0000000..f97b013 --- /dev/null +++ b/jekyll/_config.yml @@ -0,0 +1,20 @@ +# Site settings +title: “Tools for Portable and Independant Architecture” +email: topia-users at list.nuiton.org +description: > # this means to ignore newlines until "baseurl:" + ToPIA stands for 'Tools for Portable and Independant Architecture' +baseurl: "" # the subpath of your site, e.g. /blog/ +url: "doc.nuiton.org/topia" # the base hostname & protocol for your site + +topiaVersion: "3.0-alpha-beta-charlie" +eugeneVersion: "2.13" +junitVersion: "4.11" +h2Version: "1.4.181" + +headerCategories: ["tutos", "docs"] + +apidocs: "http://doc.nuiton.org/topia/master/topia-persistence/apidocs/index.html" + +# Build settings +markdown: kramdown + diff --git a/jekyll/_includes/footer.html b/jekyll/_includes/footer.html new file mode 100644 index 0000000..ebdee83 --- /dev/null +++ b/jekyll/_includes/footer.html @@ -0,0 +1,61 @@ +<footer class="site-footer"> + + <div class="wrapper"> + + <h2 class="footer-heading">{{ site.title }}</h2> + + <div class="footer-col-wrapper"> + <div class="footer-col footer-col-1"> + <ul class="contact-list"> + <li>{{ site.title }}</li> + <li><a href="mailto:{{ site.email }}">{{ site.email }}</a></li> + </ul> + </div> + + <div class="footer-col footer-col-2"> + <ul class="social-media-list"> + {% if site.github_username %} + <li> + <a href="https://github.com/{{ site.github_username }}"> + <span class="icon icon--github"> + <svg viewBox="0 0 16 16"> + <path fill="#828282" d="M7.999,0.431c-4.285,0-7.76,3.474-7.76,7.761 c0,3.428,2.223,6.337,5.307,7.363c0.388,0.071,0.53-0.168,0.53-0.374c0-0.184-0.007-0.672-0.01-1.32 c-2.159,0.469-2.614-1.04-2.614-1.04c-0.353-0.896-0.862-1.135-0.862-1.135c-0.705-0.481,0.053-0.472,0.053-0.472 c0.779,0.055,1.189,0.8,1.189,0.8c0.692,1.186,1.816,0.843,2.258,0.645c0.071-0.502,0.271-0.843,0.493-1.037 C4.86,11.425,3.049,10.76,3.049,7.786c0-0.847,0.302-1.54,0.799-2.082C3.768,5.507,3.501,4.718,3. [...] + </svg> + </span> + + <span class="username">{{ site.github_username }}</span> + </a> + </li> + {% endif %} + + {% if site.twitter_username %} + <li> + <a href="https://twitter.com/{{ site.twitter_username }}"> + <span class="icon icon--twitter"> + <svg viewBox="0 0 16 16"> + <path fill="#828282" d="M15.969,3.058c-0.586,0.26-1.217,0.436-1.878,0.515c0.675-0.405,1.194-1.045,1.438-1.809 + c-0.632,0.375-1.332,0.647-2.076,0.793c-0.596-0.636-1.446-1.033-2.387-1.033c-1.806,0-3.27,1.464-3.27,3.27 c0,0.256,0.029,0.506,0.085,0.745C5.163,5.404,2.753,4.102,1.14,2.124C0.859,2.607,0.698,3.168,0.698,3.767 c0,1.134,0.577,2.135,1.455,2.722C1.616,6.472,1.112,6.325,0.671,6.08c0,0.014,0,0.027,0,0.041c0,1.584,1.127,2.906,2.623,3.206 C3.02,9.402,2.731,9.442,2.433,9.442c-0.211,0-0.416-0.021-0.615-0.059c0.416,1.299,1.624,2.245,3.055,2.271 c-1.119,0.877-2.529,1.4-4.061,1.4c-0 [...] + </svg> + </span> + + <span class="username">{{ site.twitter_username }}</span> + </a> + </li> + {% endif %} + </ul> + </div> + + <div class="footer-col footer-col-3"> + <p class="text">{{ site.description }}</p> + </div> + </div> + + </div> + +<script> +$(function () { + $('.dropdown-toggle').dropdown(); +}); +</script> + +</footer> diff --git a/jekyll/_includes/head.html b/jekyll/_includes/head.html new file mode 100644 index 0000000..09eb346 --- /dev/null +++ b/jekyll/_includes/head.html @@ -0,0 +1,20 @@ +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width initial-scale=1" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + + <title>{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}</title> + <meta name="description" content="{{ site.description }}"> + + <link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.baseurl }}"> + <link rel="canonical" href="{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}"> + + <!-- Latest compiled and minified CSS --> + <!--link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"--> + <!--link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"--> + + <!-- Latest compiled and minified JavaScript --> + <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.2.0/js/bootstrap.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.2.0/js/dropdown.min.js"></script> +</head> diff --git a/jekyll/_includes/header.html b/jekyll/_includes/header.html new file mode 100644 index 0000000..8f3e654 --- /dev/null +++ b/jekyll/_includes/header.html @@ -0,0 +1,47 @@ +<header class="site-header"> + + <div class="wrapper"> + + <a class="site-title" href="{{ site.baseurl }}/">{{ site.title }}</a> + + <nav class="site-nav"> + <a href="#" class="menu-icon"> + <svg viewBox="0 0 18 15"> + <path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/> + <path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/> + <path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/> + </svg> + </a> + + <ul class="nav"> + {% for categ in site.headerCategories %} + <li class="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ categ }}<b class="caret"></b></a> + <ul class="dropdown-menu"> + {% for page in site.pages %} + {% if page.title %} + {% if page.category == categ %} + <li><a class="page-link" href="{{ page.url | prepend: site.baseurl }}">{{ page.title }}</a></li> + {% endif %} + {% endif %} + {% endfor %} + </ul> + </li> + {% endfor %} + + <li class="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown">Annonces<b class="caret"></b></a> + <ul class="dropdown-menu"> + {% for post in site.posts %} + <li><a class="page-link" href="{{ post.url | prepend: site.baseurl }}">{{ post.title }} ({{ post.date | date: "%-d %b %Y" }})</a></li> + {% endfor %} + </ul> + </li> + </ul> + + + </nav> + + </div> + +</header> diff --git a/jekyll/_layouts/default.html b/jekyll/_layouts/default.html new file mode 100644 index 0000000..d784082 --- /dev/null +++ b/jekyll/_layouts/default.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> + + {% include head.html %} + + <body class="topbar-enabled"> + + {% include header.html %} + + <div class="page-content"> + <div class="wrapper"> + {{ content }} + </div> + </div> + + {% include footer.html %} + + </body> + +</html> diff --git a/jekyll/_layouts/page.html b/jekyll/_layouts/page.html new file mode 100644 index 0000000..74c1a11 --- /dev/null +++ b/jekyll/_layouts/page.html @@ -0,0 +1,14 @@ +--- +layout: default +--- +<div class="post"> + + <header class="post-header"> + <h1 class="post-title">{{ page.title }}</h1> + </header> + + <article class="post-content"> + {{ content }} + </article> + +</div> diff --git a/jekyll/_layouts/post.html b/jekyll/_layouts/post.html new file mode 100644 index 0000000..a2b4e52 --- /dev/null +++ b/jekyll/_layouts/post.html @@ -0,0 +1,15 @@ +--- +layout: default +--- +<div class="post"> + + <header class="post-header"> + <h1 class="post-title">{{ page.title }}</h1> + <p class="post-meta">{{ page.date | date: "%b %-d, %Y" }}{% if page.author %} • {{ page.author }}{% endif %}{% if page.meta %} • {{ page.meta }}{% endif %}</p> + </header> + + <article class="post-content"> + {{ content }} + </article> + +</div> diff --git a/jekyll/_posts/2014-09-12-topia_3.0-beta-13.md b/jekyll/_posts/2014-09-12-topia_3.0-beta-13.md new file mode 100644 index 0000000..5afddd4 --- /dev/null +++ b/jekyll/_posts/2014-09-12-topia_3.0-beta-13.md @@ -0,0 +1,67 @@ +--- +layout: post +title: "ToPIA 3.0-beta-13 released" +tags: releases +--- + +The ToPIA team is pleased to announce the topia-3.0-beta-13 release! + +Tools for Portable and Independent Architecture : + Framework de persistance et de distribution d'application. + +Documentation of the project can be found here: +<a href="https://doc.nuiton.org/topia">https://doc.nuiton.org/topia</a> + +Changes +------- + +Changes in this version include: + +New features: + + * Introduce TopiaListenableEntity contract to move out event related methods from TopiaEntity [Issue: 3484]. Thanks to Arnaud Thimel. Resolved by athimel. + * Introduce a short TopiaIdFactory [Issue: 3495]. Thanks to Arnaud Thimel. Resolved by athimel. + * Update TopiaMetaTransformer with most commonly used values [Issue: 3283]. Thanks to Arnaud Thimel. Resolved by athimel. + +Fixed Bugs: + + * Default value for "useEnumerationName" tag value should be "true" [Issue: 3475]. Thanks to Arnaud Thimel. Resolved by athimel. + * Prevent using 'object' as an HQL parameter [Issue: 3193]. Thanks to Éric Chatellier. Resolved by bleny. + * Add warning about 'user' as a reserved sql keyword [Issue: 3194]. Thanks to Éric Chatellier. Resolved by bleny. + * Do not generate DDL INDEX when target entity is an abstract class [Issue: 3494]. Thanks to Arnaud Thimel. Resolved by athimel. + * Non compilable code generated when using custom type with generics [Issue: 3453]. Thanks to Éric Chatellier. Resolved by echatellier. + +Changes: + + * Update to commons-collections4 [Issue: 2942]. Thanks to Éric Chatellier. Resolved by athimel. + + +Downloads +--------- + +No release file deployed. (all files are deployed in the maven repository) + +Maven artifacts +--------------- + +Artifacts are deployed in Maven Central Repository +http://repo1.maven.org/maven2 + +Find us at + + <a href="http://search.maven.org/#artifactdetails|org.nuiton|topia|3.0-beta-13|jar">http://search.maven.org/#artifactdetails|org.nuiton|topia|3.0-beta-13|jar</a> + +Have fun!<br/> +-ToPIA team + +[Issue: 3484]: http://nuiton.org/issues/3484 +[Issue: 3495]: http://nuiton.org/issues/3495 +[Issue: 3283]: http://nuiton.org/issues/3283 +[Issue: 3475]: http://nuiton.org/issues/3475 +[Issue: 3193]: http://nuiton.org/issues/3193 +[Issue: 3194]: http://nuiton.org/issues/3194 +[Issue: 3494]: http://nuiton.org/issues/3494 +[Issue: 3453]: http://nuiton.org/issues/3453 +[Issue: 2942]: http://nuiton.org/issues/2942 + + diff --git a/jekyll/_sass/_base.scss b/jekyll/_sass/_base.scss new file mode 100644 index 0000000..518bf2b --- /dev/null +++ b/jekyll/_sass/_base.scss @@ -0,0 +1,204 @@ +/** + * Reset some basic elements + */ +body, h1, h2, h3, h4, h5, h6, +p, blockquote, pre, hr, +dl, dd, ol, ul, figure { + margin: 0; + padding: 0; +} + + + +/** + * Basic styling + */ +body { + font-family: $base-font-family; + font-size: $base-font-size; + line-height: $base-line-height; + font-weight: 300; + color: $text-color; + background-color: $background-color; + -webkit-text-size-adjust: 100%; +} + + + +/** + * Set `margin-bottom` to maintain vertical rhythm + */ +h1, h2, h3, h4, h5, h6, +p, blockquote, pre, +ul, ol, dl, figure, +%vertical-rhythm { + margin-bottom: $spacing-unit / 2; +} + + + +/** + * Images + */ +img { + max-width: 100%; + vertical-align: middle; +} + + + +/** + * Figures + */ +figure > img { + display: block; +} + +figcaption { + font-size: $small-font-size; +} + + + +/** + * Lists + */ +ul, ol { + margin-left: $spacing-unit; +} + +li { + > ul, + > ol { + margin-bottom: 0; + } +} + + + +/** + * Headings + */ +h1, h2, h3, h4, h5, h6 { + font-weight: 300; +} + + + +/** + * Links + */ +a { + color: $brand-color; + text-decoration: none; + + &:visited { + color: darken($brand-color, 15%); + } + + &:hover { + color: $text-color; + text-decoration: underline; + } +} + + + +/** + * Blockquotes + */ +blockquote { + color: $grey-color; + border-left: 4px solid $grey-color-light; + padding-left: $spacing-unit / 2; + font-size: 18px; + letter-spacing: -1px; + font-style: italic; + + > :last-child { + margin-bottom: 0; + } +} + + + +/** + * Code formatting + */ +pre, +code { + font-size: 15px; + border: 1px solid $grey-color-light; + border-radius: 3px; + background-color: #eef; +} + +code { + padding: 1px 5px; +} + +pre { + padding: 8px 12px; + overflow-x: scroll; + + > code { + border: 0; + padding-right: 0; + padding-left: 0; + } +} + + + +/** + * Wrapper + */ +.wrapper { + max-width: -webkit-calc(800px - (#{$spacing-unit} * 2)); + max-width: calc(800px - (#{$spacing-unit} * 2)); + margin-right: auto; + margin-left: auto; + padding-right: $spacing-unit; + padding-left: $spacing-unit; + @extend %clearfix; + + @include media-query($on-laptop) { + max-width: -webkit-calc(800px - (#{$spacing-unit})); + max-width: calc(800px - (#{$spacing-unit})); + padding-right: $spacing-unit / 2; + padding-left: $spacing-unit / 2; + } +} + + + +/** + * Clearfix + */ +%clearfix { + + &:after { + content: ""; + display: table; + clear: both; + } +} + + + +/** + * Icons + */ +.icon { + + > svg { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + + path { + fill: $grey-color; + } + } +} diff --git a/jekyll/_sass/_layout.scss b/jekyll/_sass/_layout.scss new file mode 100644 index 0000000..def56f8 --- /dev/null +++ b/jekyll/_sass/_layout.scss @@ -0,0 +1,236 @@ +/** + * Site header + */ +.site-header { + border-top: 5px solid $grey-color-dark; + border-bottom: 1px solid $grey-color-light; + min-height: 56px; + + // Positioning context for the mobile navigation icon + position: relative; +} + +.site-title { + font-size: 26px; + line-height: 56px; + letter-spacing: -1px; + margin-bottom: 0; + float: left; + + &, + &:visited { + color: $grey-color-dark; + } +} + +.site-nav { + float: right; + line-height: 56px; + + .menu-icon { + display: none; + } + + .page-link { + color: $text-color; + line-height: $base-line-height; + + // Gaps between nav items, but not on the first one + &:not(:first-child) { + margin-left: 20px; + } + } + + @include media-query($on-palm) { + position: absolute; + top: 9px; + right: 30px; + background-color: $background-color; + border: 1px solid $grey-color-light; + border-radius: 5px; + text-align: right; + + .menu-icon { + display: block; + float: right; + width: 36px; + height: 26px; + line-height: 0; + padding-top: 10px; + text-align: center; + + > svg { + width: 18px; + height: 15px; + + path { + fill: $grey-color-dark; + } + } + } + + .trigger { + clear: both; + display: none; + } + + &:hover .trigger { + display: block; + padding-bottom: 5px; + } + + .page-link { + display: block; + padding: 5px 10px; + } + } +} + + + +/** + * Site footer + */ +.site-footer { + border-top: 1px solid $grey-color-light; + padding: $spacing-unit 0; +} + +.footer-heading { + font-size: 18px; + margin-bottom: $spacing-unit / 2; +} + +.contact-list, +.social-media-list { + list-style: none; + margin-left: 0; +} + +.footer-col-wrapper { + font-size: 15px; + color: $grey-color; + margin-left: -$spacing-unit / 2; + @extend %clearfix; +} + +.footer-col { + float: left; + margin-bottom: $spacing-unit / 2; + padding-left: $spacing-unit / 2; +} + +.footer-col-1 { + width: -webkit-calc(35% - (#{$spacing-unit} / 2)); + width: calc(35% - (#{$spacing-unit} / 2)); +} + +.footer-col-2 { + width: -webkit-calc(20% - (#{$spacing-unit} / 2)); + width: calc(20% - (#{$spacing-unit} / 2)); +} + +.footer-col-3 { + width: -webkit-calc(45% - (#{$spacing-unit} / 2)); + width: calc(45% - (#{$spacing-unit} / 2)); +} + +@include media-query($on-laptop) { + .footer-col-1, + .footer-col-2 { + width: -webkit-calc(50% - (#{$spacing-unit} / 2)); + width: calc(50% - (#{$spacing-unit} / 2)); + } + + .footer-col-3 { + width: -webkit-calc(100% - (#{$spacing-unit} / 2)); + width: calc(100% - (#{$spacing-unit} / 2)); + } +} + +@include media-query($on-palm) { + .footer-col { + float: none; + width: -webkit-calc(100% - (#{$spacing-unit} / 2)); + width: calc(100% - (#{$spacing-unit} / 2)); + } +} + + + +/** + * Page content + */ +.page-content { + padding: $spacing-unit 0; +} + +.page-heading { + font-size: 20px; +} + +.post-list { + margin-left: 0; + list-style: none; + + > li { + margin-bottom: $spacing-unit; + } +} + +.post-meta { + font-size: $small-font-size; + color: $grey-color; +} + +.post-link { + display: block; + font-size: 24px; +} + + + +/** + * Posts + */ +.post-header { + margin-bottom: $spacing-unit; +} + +.post-title { + font-size: 42px; + letter-spacing: -1px; + line-height: 1; + + @include media-query($on-laptop) { + font-size: 36px; + } +} + +.post-content { + margin-bottom: $spacing-unit; + + h2 { + font-size: 32px; + + @include media-query($on-laptop) { + font-size: 28px; + } + } + + h3 { + font-size: 26px; + + @include media-query($on-laptop) { + font-size: 22px; + } + } + + h4 { + font-size: 20px; + + @include media-query($on-laptop) { + font-size: 18px; + } + } +} diff --git a/jekyll/_sass/_syntax-highlighting.scss b/jekyll/_sass/_syntax-highlighting.scss new file mode 100644 index 0000000..e36627d --- /dev/null +++ b/jekyll/_sass/_syntax-highlighting.scss @@ -0,0 +1,67 @@ +/** + * Syntax highlighting styles + */ +.highlight { + background: #fff; + @extend %vertical-rhythm; + + .c { color: #998; font-style: italic } // Comment + .err { color: #a61717; background-color: #e3d2d2 } // Error + .k { font-weight: bold } // Keyword + .o { font-weight: bold } // Operator + .cm { color: #998; font-style: italic } // Comment.Multiline + .cp { color: #999; font-weight: bold } // Comment.Preproc + .c1 { color: #998; font-style: italic } // Comment.Single + .cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special + .gd { color: #000; background-color: #fdd } // Generic.Deleted + .gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific + .ge { font-style: italic } // Generic.Emph + .gr { color: #a00 } // Generic.Error + .gh { color: #999 } // Generic.Heading + .gi { color: #000; background-color: #dfd } // Generic.Inserted + .gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific + .go { color: #888 } // Generic.Output + .gp { color: #555 } // Generic.Prompt + .gs { font-weight: bold } // Generic.Strong + .gu { color: #aaa } // Generic.Subheading + .gt { color: #a00 } // Generic.Traceback + .kc { font-weight: bold } // Keyword.Constant + .kd { font-weight: bold } // Keyword.Declaration + .kp { font-weight: bold } // Keyword.Pseudo + .kr { font-weight: bold } // Keyword.Reserved + .kt { color: #458; font-weight: bold } // Keyword.Type + .m { color: #099 } // Literal.Number + .s { color: #d14 } // Literal.String + .na { color: #008080 } // Name.Attribute + .nb { color: #0086B3 } // Name.Builtin + .nc { color: #458; font-weight: bold } // Name.Class + .no { color: #008080 } // Name.Constant + .ni { color: #800080 } // Name.Entity + .ne { color: #900; font-weight: bold } // Name.Exception + .nf { color: #900; font-weight: bold } // Name.Function + .nn { color: #555 } // Name.Namespace + .nt { color: #000080 } // Name.Tag + .nv { color: #008080 } // Name.Variable + .ow { font-weight: bold } // Operator.Word + .w { color: #bbb } // Text.Whitespace + .mf { color: #099 } // Literal.Number.Float + .mh { color: #099 } // Literal.Number.Hex + .mi { color: #099 } // Literal.Number.Integer + .mo { color: #099 } // Literal.Number.Oct + .sb { color: #d14 } // Literal.String.Backtick + .sc { color: #d14 } // Literal.String.Char + .sd { color: #d14 } // Literal.String.Doc + .s2 { color: #d14 } // Literal.String.Double + .se { color: #d14 } // Literal.String.Escape + .sh { color: #d14 } // Literal.String.Heredoc + .si { color: #d14 } // Literal.String.Interpol + .sx { color: #d14 } // Literal.String.Other + .sr { color: #009926 } // Literal.String.Regex + .s1 { color: #d14 } // Literal.String.Single + .ss { color: #990073 } // Literal.String.Symbol + .bp { color: #999 } // Name.Builtin.Pseudo + .vc { color: #008080 } // Name.Variable.Class + .vg { color: #008080 } // Name.Variable.Global + .vi { color: #008080 } // Name.Variable.Instance + .il { color: #099 } // Literal.Number.Integer.Long +} diff --git a/jekyll/all_posts.html b/jekyll/all_posts.html new file mode 100644 index 0000000..0e10f3e --- /dev/null +++ b/jekyll/all_posts.html @@ -0,0 +1,24 @@ +--- +layout: default +--- + +<div class="home"> + + + <ul class="post-list"> + {% for post in site.posts %} + <li> + <span class="post-meta">{{ post.date | date: "%b %-d, %Y" }}</span> + + <h2> + <a href="{{ post.url | prepend: site.baseurl }}">{{ post.title }}</a> + </h2> + </li> + {% endfor %} + </ul> + + <p class="rss-subscribe">subscribe <a href="{{ "/feed.xml" | prepend: site.baseurl }}">via RSS</a></p> + + +</div> + diff --git a/jekyll/assets/AppTest.java b/jekyll/assets/AppTest.java new file mode 100644 index 0000000..a58bbed --- /dev/null +++ b/jekyll/assets/AppTest.java @@ -0,0 +1,94 @@ +package com.company; + +/* + * #%L + * ToPIA + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2004 - 2014 CodeLutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; +import org.nuiton.topia.persistence.TopiaConfigurationConstants; + +import com.company.app.MyLibraryTopiaApplicationContext; +import com.company.app.MyLibraryTopiaPersistenceContext; +import com.company.app.entities.Author; +import com.company.app.entities.AuthorTopiaDao; +import com.company.app.entities.Book; +import com.company.app.entities.BookTopiaDao; + +public class AppTest { + + @Test + public void testApp() { + // Declaration de la configuration + Map<String, String> config = new HashMap<String, String>(); + config.put(TopiaConfigurationConstants.CONFIG_DRIVER, "org.h2.Driver"); + config.put(TopiaConfigurationConstants.CONFIG_DIALECT, "org.hibernate.dialect.H2Dialect"); + config.put(TopiaConfigurationConstants.CONFIG_USER, "sa"); + config.put(TopiaConfigurationConstants.CONFIG_PASS, ""); + config.put(TopiaConfigurationConstants.CONFIG_URL, "jdbc:h2:file:/tmp/test-" + System.nanoTime()); + config.put(TopiaConfigurationConstants.CONFIG_PERSISTENCE_INIT_SCHEMA, "true"); + + // Creation de l'ApplicationContext (va initialiser la base et les services de ToPIA) + MyLibraryTopiaApplicationContext applicationContext = + new MyLibraryTopiaApplicationContext(config); + + // Démarre une transaction représentée par le PersistenceContext + MyLibraryTopiaPersistenceContext persistenceContext = + applicationContext.newPersistenceContext(); + + // On demande au PersistenceContext les instances des Dao + AuthorTopiaDao authorDao = persistenceContext.getAuthorDao(); + BookTopiaDao bookDao = persistenceContext.getBookDao(); + + Assert.assertEquals(0, authorDao.count()); + Assert.assertEquals(0, bookDao.count()); + + { // On créé un author et on demande au Dao de le persister + Author author = authorDao.newInstance(); + author.setName("Antoine de Saint-Exupéry"); + authorDao.create(author); + } + + Assert.assertEquals(1, authorDao.count()); + + { // On créé un livre en listant les propriétés qui le composent + Author author = authorDao + .forNameEquals("Antoine de Saint-Exupéry") + .findUnique(); + bookDao.create(Book.PROPERTY_NAME, "Le petit prince", + Book.PROPERTY_AUTHOR, author); + } + + Assert.assertEquals(1, bookDao.count()); + + // On commite la transaction + persistenceContext.commit(); + + // Fermeture des contextes + persistenceContext.close(); + applicationContext.close(); + } +} diff --git a/jekyll/assets/employeeAndCompany.png b/jekyll/assets/employeeAndCompany.png new file mode 100644 index 0000000..9263a45 Binary files /dev/null and b/jekyll/assets/employeeAndCompany.png differ diff --git a/jekyll/assets/info.png b/jekyll/assets/info.png new file mode 100644 index 0000000..05dbc53 Binary files /dev/null and b/jekyll/assets/info.png differ diff --git a/jekyll/assets/library-model.zargo b/jekyll/assets/library-model.zargo new file mode 100644 index 0000000..daae97c Binary files /dev/null and b/jekyll/assets/library-model.zargo differ diff --git a/jekyll/assets/library.png b/jekyll/assets/library.png new file mode 100644 index 0000000..8418a68 Binary files /dev/null and b/jekyll/assets/library.png differ diff --git a/jekyll/assets/mandatory.png b/jekyll/assets/mandatory.png new file mode 100644 index 0000000..c45f7ac Binary files /dev/null and b/jekyll/assets/mandatory.png differ diff --git a/jekyll/assets/recommended.png b/jekyll/assets/recommended.png new file mode 100644 index 0000000..986f1d8 Binary files /dev/null and b/jekyll/assets/recommended.png differ diff --git a/jekyll/assets/topia_60_20.png b/jekyll/assets/topia_60_20.png new file mode 100644 index 0000000..b80fa75 Binary files /dev/null and b/jekyll/assets/topia_60_20.png differ diff --git a/jekyll/css/main.scss b/jekyll/css/main.scss new file mode 100755 index 0000000..1eb3719 --- /dev/null +++ b/jekyll/css/main.scss @@ -0,0 +1,172 @@ +--- +# Only the main Sass file needs front matter (the dashes are enough) +--- +@charset "utf-8"; + + + +// Our variables +$base-font-family: Helvetica, Arial, sans-serif; +$base-font-size: 16px; +$small-font-size: $base-font-size * 0.875; +$base-line-height: 1.5; + +$spacing-unit: 30px; + +$text-color: #111; +$background-color: #fdfdfd; +$brand-color: #2a7ae2; + +$grey-color: #828282; +$grey-color-light: lighten($grey-color, 40%); +$grey-color-dark: darken($grey-color, 25%); + +$on-palm: 600px; +$on-laptop: 800px; + + + +// Using media queries with like this: +// @include media-query($palm) { +// .wrapper { +// padding-right: $spacing-unit / 2; +// padding-left: $spacing-unit / 2; +// } +// } +@mixin media-query($device) { + @media screen and (max-width: $device) { + @content; + } +} + + + +// Import partials from `sass_dir` (defaults to `_sass`) +@import + "base", + "layout", + "syntax-highlighting" +; + + +.site-title { + background: url('/assets/topia_60_20.png') no-repeat scroll 0px 20px transparent; + padding-left: 65px; + font-size: 20px; +} +body { + font-size: 14px; + color: #333 +} + +body.topbar-enabled { + padding-top: 65px; +} + +.site-header { + position: fixed; + top: 0px; + left: 0px; + width: 100%; + background: #eee; + height: 60px; +} + +.wrapper { + max-width: calc(960px - 30px * 2); +} + +.site-header .wrapper { + height: 60px; +} + +.site-header .site-title, +.site-header nav.site-nav { + height: 60px; +} + +.caret { + display: inline-block; + width: 0px; + height: 0px; + vertical-align: top; + border-top: 4px solid #000; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + content: ""; + margin-left: 4px; +} +.site-nav .nav { + position: relative; + left: 0px; + display: block; + float: left; + margin: 0px 10px 0px 0px; + height: 60px; +} +.site-nav .nav > li { + float: left; + height: 60px; +} +.dropdown { + position: relative; +} +.nav > li > a { + display: block; +} +.dropdown-menu li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 20px; + color: #333; + white-space: nowrap; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0px; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0px; + margin: 2px 0px 0px; + list-style: none outside none; + background-color: #FFF; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.2); + background-clip: padding-box; +} +.nav { + list-style: none outside none; +} +.site-nav .nav li.dropdown > a:hover { + color: #111; +} +.site-nav .nav li.dropdown > a:hover .caret { + border-top-color: #555; + border-bottom-color: #555; +} +.site-nav .nav .dropdown-toggle .caret { + margin-top: 28px; +} +.site-nav .nav > li > a { + float: none; + padding: 0px 10px; + color: #777; + text-decoration: none; + text-shadow: 0px 1px 0px #FFF; +} +.open > .dropdown-menu { + display: block; +} +.highlight pre { + line-height: 1.2; +} +code { + font-size: 14px; +} + diff --git a/jekyll/docs/model_generation.md b/jekyll/docs/model_generation.md new file mode 100644 index 0000000..460bf4c --- /dev/null +++ b/jekyll/docs/model_generation.md @@ -0,0 +1,75 @@ +--- +layout: page +title: "Génération des modèles" +category: docs +--- + + +Le module `topia-persistence` de ToPIA est capable de générer la persistence à partir d'un modèle UML. + +Nous allons détailler ici comme inclure la génération dans le cycle de compilation maven. + + +Architecture maven +------------------ + + * `src/main/xmi/mymodel.zargo` (exemple de modele UML avec argoUML) + * `src/main/xmi/mymodel.properties` (fichier de propriétés attaché au modele) + +Configuration du pom.xml +------------------------ + +Pour générer les sources dans la phase `generate-sources` de maven, et utiliser +les sources générées, et faut configurer le pom. + +### Plugin Eugene + + +{% highlight xml %} + <plugin> + <groupId>org.nuiton.eugene</groupId> + <artifactId>eugene-maven-plugin</artifactId> + <version>{{site.eugeneVersion}}</version> + <executions> + <execution> + <id>Generator</id> + <phase>generate-sources</phase> + <configuration> + <inputs>zargo</inputs> + <fullPackagePath>org.company.package</fullPackagePath> + <extractedPackages>org.company.package</extractedPackages> + <templates>org.nuiton.topia.generator.TopiaMetaGenerator</templates> + <defaultPackage>org.company.package</defaultPackage> + </configuration> + <goals> + <goal>generate</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.nuiton.topia</groupId> + <artifactId>topia-persistence</artifactId> + <version>{{site.topiaVersion}}</version> + </dependency> + </dependencies> + </plugin> +{% endhighlight %} + +Pour plus d'information à propos d'Eugene, merci de consulter le site : +http://doc.nuiton.org/eugene/ + +### Dépendances du projet + + +Il faut enfin ajouter "topia-persistence" en dépendance du projet : + + +{% highlight xml %} + <dependency> + <groupId>org.nuiton.topia</groupId> + <artifactId>topia-persistence</artifactId> + <version>{{site.topiaVersion}}</version> + </dependency> +{% endhighlight %} + diff --git a/jekyll/index.md b/jekyll/index.md new file mode 100644 index 0000000..1fa4dc0 --- /dev/null +++ b/jekyll/index.md @@ -0,0 +1,112 @@ +--- +layout: page +title: "ToPIA" +--- + +ToPIA est un framework d'abstraction des plateformes techniques. À l'heure actuelle ToPIA se focalise sur la persistence +des données. + +Lorsque vous utilisez ToPIA, vous définissez votre modèle UML, puis vous codez votre code métier sans vous +soucier de la persistence. C'est ToPIA qui va s'occuper de ce qui se passe entre ces 2 étapes. + + +Comment ça marche ? +------------------- + +Avant tout, vous devez créer un modèle UML qui représente vos objets métier. + +Ici, nous avons un modèle très simple dans lequel un employé est caractérisé par son nom, son genre et sa ville de +résidence. Une société est constituée d'un nom, d'un SIRET et d'une liste d'employés. + + + +En amont de la compilation, ToPIA va générer (grâce à [Eugene]) les fichiers nécessaires à son bon fonctionnement. Ces +fichiers générés ajoutés au code que vous avez écrit constituent les sources de l'application. Cela signifie que vous +bénéficiez de toute la robustesse d'une compilation Java, alliée à une sérieuse économie de lignes de code. Lorsque vous +packagez votre application, vous packagez donc des sources déjà générées, rien n'est généré ultérieurement. + +Les fichiers ainsi générés vous permettent de manipuler vos objets, de les charger depuis la base, les persister, +les supprimer ... Pour cela, vous disposez de plusieurs API de base qui donnent accès aux opérations les plus courantes, +mais également des classes générées qui vous évitent de devoir écrire 80% du code nécessaire à la manipulation de vos +objets. + +Par exemple, admettons que vous souhaitiez rechercher tous les emplyés qui habitent Nantes. Vous pouvez le faire en une +seule ligne sans vous soucier de comment charger des objets depuis la base : + +{% highlight java %} +List<Employee> employees = personDao.forCityEquals("Nantes").findAll(); +{% endhighlight %} + +À l'éxécution, ToPIA va traduire ces appels de méthodes en requêtes compréhensibles par le support de persistence. À +l'heure actuelle, ToPIa se base sur [Hibernate], mais le framework est pensé de manière à masquer autant que possible +ce choix technique afin de faciliter une éventuelle migration vers un autre framework de mapping objet-relationnel. + +<a href="#">Plus de détails sur les cycles en Y</a>. + + +Les services +------------ + +Autour de la persistence, ToPIA propose un certain nombre de services qui peuvent être activés ou non et qui vous +permettent de bénéficier d'autres fonctionnalités ou encore de rajouter des comportements auxquels nous n'aurions pas +pensé. + + - <a href="#">Service de gestion des mises à jour</a> : permet de gérer les migrations de base lorsque vous déployez une nouvelle + version de votre application ; + - <a href="#">Service de gestion des mises à jour (Flyway)</a> : permet également de gérer les migrations de base en se basant sur + [Flyway] ; + - <a href="#">Service de gestion des mises à jour (Liquibase)</a> : permet également de gérer les migrations de base en se basant sur + [Liquibase] ; + - <a href="#">Service de réplication</a> : permet de répliquer des données de base à base ; + - <a href="#">Service CSV</a> : service gérant les imports et expots au format CSV. + + +Avantages +--------- + + + - Principales méthodes générées pour le recherche/chargement des objets ; + - Pas de mapping ORM à écrire ; + - Génération de code intelligente (basé sur l'héritage Java, compilation, ...) ; + - En dépit de la génération de code, il est toujours possible de remplacer une classe générée ; + - Déjà utilisé dans des projets à forte volumétrie ; + - Quasi-indépendance du code métier par rapport à la solution de persistence ; + - Migrations facilitées lors des livraisons de nouvelles versions ; + - Intégration transparente à l'outil de construction du projet (Maven, ...) ; + - ... + + +Par où commencer ? +------------------ + +ToPIA est disponible sur Maven Central + +{% highlight xml %} +<dependency> + <groupId>org.nuiton</groupId> + <artifactId>topia-perssitence</artifactId> + <version>{{site.topiaVersion}}</version> +</dependency> +{% endhighlight %} + +Consultez les tutos pour créer votre projet à base de ToPIA en fonction de votre besoin : + + - [Créer un projet ToPIA en partant de rien] ; + - [Migrer de ToPIA 2.x vers ToPIA 3][migrate_to_topia_3] ; + - ... + + +Version 3.0 +----------- + + La version 3.0 de ToPIA est en cours de développement. Si vous utilisiez déjà la version 2, [consultez le guide de migration][migrate_to_topia_3]. + + +[Hibernate]: http://hibernate.org/ +[Eugene]: http://doc.nuiton.org/eugene/ +[Flyway]: http://flywaydb.org/ +[Liquibase]: http://www.liquibase.org/ + +[Créer un projet ToPIA en partant de rien]: {{site.baseurl}}/tutos/from_scratch.html +[migrate_to_topia_3]: {{site.baseurl}}/tutos/migrate_to_topia_3.html + diff --git a/jekyll/tutos/from_scratch.md b/jekyll/tutos/from_scratch.md new file mode 100644 index 0000000..a8259cf --- /dev/null +++ b/jekyll/tutos/from_scratch.md @@ -0,0 +1,350 @@ +--- +layout: page +title: "ToPIA from scratch" +category: tutos +--- + + +Ce tutoriel explique comment créer un projet from scratch. Il se base sur [Maven] mais si vous êtes plus à l'aise avec +un autre outil de construction de projet, ça ne devrait pas poser de problème. + + +Créer le projet +--------------- + +Pour commencer, il faut créer un projet Maven en respectant les conventions habituelles. Partons d'un projet Maven vide : + + +{% highlight bash %} +$ mvn archetype:generate -DgroupId=com.company -DartifactId=my-topia-app -Dversion=0.1-SNAPSHOT -DarchetypeArtifactId=maven-archetype-quickstart +{% endhighlight %} + +L'arborescence du projet ressemble alors à : + + +{% highlight bash %} + my-topia-app + ├── pom.xml + └── src + ├── main + │ └── java + │ └── com + │ └── company + │ └── App.java + └── test + └── java + └── com + └── company + └── AppTest.java +{% endhighlight %} + + +Ajoutez un dossier `src/main/xmi`, c'est ici que nous allons placer le modèle. + +{% highlight bash %} +$ cd my-topia-app +$ mkdir src/main/xmi +{% endhighlight %} + +Dans le pom, on ajoute ToPIA en dépendance, et on configure la génération : + +{% highlight xml %} +<properties> + <topiaVersion>{{site.topiaVersion}}</topiaVersion> + <eugeneVersion>{{site.eugeneVersion}}</eugeneVersion> +</properties> + +<dependencies> + <dependency> + <groupId>org.nuiton.topia</groupId> + <artifactId>topia-persistence</artifactId> + <version>${topiaVersion}</version> + </dependency> +</dependencies> + +<build> + <plugins> + <plugin> + <groupId>org.nuiton.eugene</groupId> + <artifactId>eugene-maven-plugin</artifactId> + <version>${eugeneVersion}</version> + <configuration> + <inputs>zargo</inputs> + <resolver>org.nuiton.util.FasterCachedResourceResolver</resolver> + </configuration> + <executions> + <execution> + <id>generate-entities</id> + <phase>generate-sources</phase> + <configuration> + <!-- Corresponding to extracted package from zargo file --> + <fullPackagePath>com.company.app.entities</fullPackagePath> + <!-- DefaultPackage used for Model classes generation (XxxEntityEnum, XxxTopiaXyz classes, ...) --> + <defaultPackage>com.company.app</defaultPackage> + <templates> + org.nuiton.eugene.java.JavaInterfaceTransformer, + org.nuiton.eugene.java.JavaBeanTransformer, + org.nuiton.eugene.java.JavaEnumerationTransformer, + org.nuiton.topia.templates.TopiaMetaTransformer + </templates> + </configuration> + <goals> + <goal>smart-generate</goal> + </goals> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.nuiton.topia</groupId> + <artifactId>topia-persistence</artifactId> + <version>${topiaVersion}</version> + </dependency> + <dependency> + <groupId>org.nuiton.topia</groupId> + <artifactId>topia-templates</artifactId> + <version>${topiaVersion}</version> + </dependency> + </dependencies> + </plugin> + </plugins> +</build> +{% endhighlight %} + + +Créer le modèle avec ArgoUML +---------------------------- + +Avec [ArgoUML], créez un nouveau modèle. Nous allons créer un modèle simplifié pour une application de gestion de +bibliothèque. + +Nous avons modélisé les entités `Book` et `Author` avec les champs appropriés. Faites attention à bien placer les +entités dans le bon package (`com.company.app.entities`), sinon elles ne seront pas générées. Pour que ToPIA sache que +ces classes décrivent des entités qui ont vocation à être sauvées en base, il faut leur ajouter le stéréotype `entity`. + +N'oubliez pas de donner un nom et une version au modèle. + +Enregistrez votre modèle dans `src/main/xmi` et nommez le `library-model.zargo`. + + + + +Note : le modèle utilisé pour ce tutoriel est [disponible ici][library-model.zargo]. + +C'est prêt ! +------------ + +Il suffit de construire l'application avec Maven. + +{% highlight bash %} +$ mvn clean install +{% endhighlight %} + +La compilation va provoquer la phase de génération. Si on examine le contenu du projet, on voit apparaître un dossier +`target/generated-sources/java` qui va regrouper le code généré : + + +{% highlight bash %} + my-topia-app + ├── pom.xml + ├── src + │ ├── main + │ │ ├── java + │ │ │ └── com + │ │ │ └── company + │ │ │ └── App.java + │ │ └── xmi + │ │ └── library-model.zargo + │ └── test + │ └── java + │ └── com + │ └── company + │ └── AppTest.java + └── target + ├── classes + │ └── ... + ├── generated-sources + │ ├── java + │ │ └── com + │ │ └── company + │ │ └── app + │ │ ├── AbstractMyLibraryTopiaApplicationContext.java + │ │ ├── AbstractMyLibraryTopiaDao.java + │ │ ├── AbstractMyLibraryTopiaPersistenceContext.java + │ │ ├── entities + │ │ │ ├── AbstractAuthorTopiaDao.java + │ │ │ ├── AbstractBookTopiaDao.java + │ │ │ ├── AuthorAbstract.java + │ │ │ ├── AuthorImpl.hbm.xml + │ │ │ ├── AuthorImpl.java + │ │ │ ├── Author.java + │ │ │ ├── AuthorTopiaDao.java + │ │ │ ├── BookAbstract.java + │ │ │ ├── BookImpl.hbm.xml + │ │ │ ├── BookImpl.java + │ │ │ ├── Book.java + │ │ │ ├── BookTopiaDao.java + │ │ │ ├── GeneratedAuthorTopiaDao.java + │ │ │ └── GeneratedBookTopiaDao.java + │ │ ├── MyLibraryEntityEnum.java + │ │ ├── MyLibraryTopiaApplicationContext.java + │ │ ├── MyLibraryTopiaDaoSupplier.java + │ │ └── MyLibraryTopiaPersistenceContext.java + │ ├── models + │ │ └── library-model.objectmodel + │ └── xmi + │ └── library-model.xmi + [...] + └── my-topia-app-0.1-SNAPSHOT.jar +{% endhighlight %} + + + +ToPIA a donc généré plusieurs fichiers, dont : + + * `MyLibraryTopiaApplicationContext.java` : représente le point d'entrée dans l'application. (cf [TopiaApplicationContext]) ; + * `MyLibraryTopiaPersistenceContext.java` : représente un transaction (cf [TopiaPersistenceContext]) ; + * `MyLibraryTopiaDaoSupplier.java` : permet d'obtenir une instance de n'importe quel Dao géré par ToPIA ; + * `MyLibraryEntityEnum.java` : liste les contrats et classes d'implémentation du modèle ; + * des classes abstraites pour le `MyLibraryTopiaApplicationContext` et `MyLibraryTopiaPersistenceContext`. + +Dans le package `com.company.app.entities`, on retrouve nos deux entités `Book` et `Author`. Chaque entité est représentée par plusieurs fichiers : + + * `Book.java` : une interface qui expose les informations publiques de l'entité Book ; + * `BookAbstract.java` : l'implémentation générée par ToPIA. Cette classe est abstraite ; + * `BookImpl.java` : la classe concrète implémentant la classe abstraite ; + * `BookImpl.hbm.xml` : le fichier de mapping Hibernate ; + * `GeneratedBookTopiaDao.java` : contient le code généré relatif aux entité Book : `forNameEquals(...)`, `forIsbnIn(...)`, `forAuthorEquals(...)` ; + * `AbstractBookTopiaDao.java` : implémentation du `GeneratedBookTopiaDao` retournant des entités héritant de Book ; + * `BookTopiaDao.java` : implémentation du `GeneratedBookTopiaDao` retournant des entités Book. + + +Parmi toutes ces classes relatives aux entités, celles que vous allez principalement utiliser sont `Book.java` et `BookTopiaDao.java`. + + +On teste ? +---------- + +L'archetype Maven a créé un projet compatible JUnit, ça tombe bien on va s'en servir ! Par contre, la version proposée +par défaut est un peu vieille, on va la mettre à jour dans le `pom.xml`. Il nous faut aussi une base pour faire les +tests, ajoutez `H2` : + + +{% highlight xml %} +<dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>{{site.junitVersion}}</version> + <scope>test</scope> +</dependency> +<dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <version>{{site.h2Version}}</version> + <scope>test</scope> +</dependency> +{% endhighlight %} + + + +Voilà un test qui initialise ToPIA et créé des entités : + +{% highlight java %} +package com.company; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; +import org.nuiton.topia.persistence.TopiaConfigurationConstants; + +import com.company.app.MyLibraryTopiaApplicationContext; +import com.company.app.MyLibraryTopiaPersistenceContext; +import com.company.app.entities.Author; +import com.company.app.entities.AuthorTopiaDao; +import com.company.app.entities.Book; +import com.company.app.entities.BookTopiaDao; + +public class AppTest { + + @Test + public void testApp() { + // Declaration de la configuration + Map<String, String> config = new HashMap<String, String>(); + config.put(TopiaConfigurationConstants.CONFIG_DRIVER, "org.h2.Driver"); + config.put(TopiaConfigurationConstants.CONFIG_DIALECT, "org.hibernate.dialect.H2Dialect"); + config.put(TopiaConfigurationConstants.CONFIG_USER, "sa"); + config.put(TopiaConfigurationConstants.CONFIG_PASS, ""); + config.put(TopiaConfigurationConstants.CONFIG_URL, "jdbc:h2:file:/tmp/test-" + System.nanoTime()); + config.put(TopiaConfigurationConstants.CONFIG_PERSISTENCE_INIT_SCHEMA, "true"); + + // Creation de l'ApplicationContext (va initialiser la base et les services de ToPIA) + MyLibraryTopiaApplicationContext applicationContext = + new MyLibraryTopiaApplicationContext(config); + + // Démarre une transaction représentée par le PersistenceContext + MyLibraryTopiaPersistenceContext persistenceContext = + applicationContext.newPersistenceContext(); + + // On demande au PersistenceContext les instances des Dao + AuthorTopiaDao authorDao = persistenceContext.getAuthorDao(); + BookTopiaDao bookDao = persistenceContext.getBookDao(); + + Assert.assertEquals(0, authorDao.count()); + Assert.assertEquals(0, bookDao.count()); + + { // On créé un author et on demande au Dao de le persister + Author author = authorDao.newInstance(); + author.setName("Antoine de Saint-Exupéry"); + authorDao.create(author); + } + + Assert.assertEquals(1, authorDao.count()); + + { // On créé un livre en listant les propriétés qui le composent + Author author = authorDao + .forNameEquals("Antoine de Saint-Exupéry") + .findUnique(); + bookDao.create(Book.PROPERTY_NAME, "Le petit prince", + Book.PROPERTY_AUTHOR, author); + } + + Assert.assertEquals(1, bookDao.count()); + + // On commite la transaction + persistenceContext.commit(); + + // Fermeture des contextes + persistenceContext.close(); + applicationContext.close(); + } +} +{% endhighlight %} + +Cette classe de test est [télécargeable ici][AppTest.java]. + + +Aller plus loin +--------------- + + * `Comment personnaliser la génération de code ?`_ + * `Comment intégrer ToPIA dans votre application ?`_ + * `Comment gérer les évolutions de schéma avec ToPIA ?`_ + * `Détails de l'intégration avec Maven`_ + + + +[Maven]: http://maven.apache.org/ +[ArgoUML]: http://argouml.tigris.org/ + +[AppTest.java]: {{site.baseurl}}/assets/AppTest.java +[library-model.zargo]: {{site.baseurl}}/assets/library-model.zargo + +[TopiaApplicationContext]: {{site.apidocs}}?org/nuiton/topia/persistence/TopiaApplicationContext.html +[TopiaPersistenceContext]: {{site.apidocs}}?org/nuiton/topia/persistence/TopiaPersistenceContext.html + +.. _Comment personnaliser la génération de code ?: ./howto_customize_generation.html +.. _Comment intégrer ToPIA dans votre application ?: ./howto_architecture_with_topia.html +.. _Comment gérer les évolutions de schéma avec ToPIA ?: ./howto_migration_service.html +.. _Détails de l'intégration avec Maven: ../docs/maven_integration.html + diff --git a/jekyll/tutos/migrate_to_topia_3.md b/jekyll/tutos/migrate_to_topia_3.md new file mode 100644 index 0000000..606200d --- /dev/null +++ b/jekyll/tutos/migrate_to_topia_3.md @@ -0,0 +1,408 @@ +--- +layout: page +title: "Migrate from ToPIA 2.x to ToPIA 3" +category: tutos +--- + + + +Gestion des dépendances +----------------------- + + + +ToPIA nécessite des versions minimum de Eugene et Hibernate. Vous devez utiliser les version suivantes : + + * Eugene >= 2.13 + * Hibernate >= 4.3.6.Final + + +Configuration du plugin Maven +----------------------------- + + + +Les templates sont contenus dans un modules à part, il faut désormais utiliser +cette dépendance dans le plugin eugene. + +{% highlight xml %} +<dependency> + <groupId>org.nuiton.topia</groupId> + <artifactId>topia-templates</artifactId> + <version>{{site.topiaVersion}}</version> +</dependency> +{% endhighlight %} + +De plus les templates ont changé de package, le nouveau package est le suivant : + + org.nuiton.topia.templates.* + + + +Génération des PropertyChangeSupport +------------------------------------ + + + +Nous nous sommes aperçus que l'impact des PropertyChangeSupport sur les performances peut ne pas être négligeable. C'est +pourquoi nous avons décidé de désactiver par défaut leur génération. Si vous souhaitez que les PropertyChangeSupport +soient générés, il faut ajouter à votre fichier de properties la tag-value suivante : + +{% highlight properties %} +model.tagValue.generatePropertyChangeSupport=true +{% endhighlight %} + +Au même titre, les méthodes permettant d'ajouter/supprimer des listener sur les PropertyChangeSupport ne sont plus sur +le contrat TopiaEntity, mais sur le contrat ListenableTopiaEntity. Si vous utilisiez ces méthodes sur votre entité +métier : pas de changement. En revanche si vous utilisiez ces méthodes sur TopiaEntity, vous devez maintenant caster +l'entité ou utiliser directement le bon contrat. + +{% highlight java %} +// Topia 2.x +TopiaEntity anEntity = ...; +anEntity.addPropertyChangeListener(listener); + +// Topia 3 (cast) +TopiaEntity anEntity = ...; +if (anEntity instanceof ListenableTopiaEntity) { + ((ListenableTopiaEntity)anEntity).addPostWriteListener(listener); +} + +// Topia 3 (direct) +ListenableTopiaEntity anEntity = ...; +anEntity.addPostWriteListener(listener); +{% endhighlight %} + +D'une manière générale, si toutes vos entités sont des ListenableTopiaEntity, nous vous encourageons à utiliser ce +contrat à la place de TopiaEntity. + +Ces méthodes ont également été renommées, voici les correspondances : + + `addVetoableListener` -> `addPreReadListener` + `removeVetoableListener` -> `removePreReadListener` + `addPropertyListener` -> `addPostReadListener` + `removePropertyListener` -> `removePostReadListener` + `addVetoableChangeListener` -> `addPreWriteListener` + `removeVetoableChangeListener` -> `removePreWriteListener` + `addPropertyChangeListener` -> `addPostWriteListener` + `removePropertyChangeListener` -> `removePostWriteListener` + +À noter que les méthodes suivantes existent toujours, mais sont dorénavant isolées sur un autre contrat (ListenableBean) : + + `addPropertyChangeListener` + `removePropertyChangeListener` + + +Refonte des indexed/ordered +--------------------------- + + + +Vous devez migrer votre modèle zargo depuis la beta-3 suite à une confusion entre ordered et indexed (stéréotypes +à placer sur une association-end d'un lien entre deux entités). + + * Le stéréotype "indexed" est déprécié, vous devez le remplacer par l'attribut "ordered" + * L'attribut (ou le stéréotype) "ordered" maintient l'ordre d'insertion en base, il faut donc vous assurer qu'une + colonne <champ>_idx est bien présente dans la base de donnée et au besoin écrire la migration nécessaire (voir + [le guide dédié][ordered_to_indexed_migration]) pour ajouter la colonne et fixer les indexes manquants (en commençant à 0). + * Les types des collections générées ont changé afin d'utiliser un contrat plus proche du modèle (selon la présence + des stéréotypes "unique" et/ou "ordered"). + +NB : La documentation contient des [examples de migration pour la refonte des indexed/ordered][ordered_to_indexed_migration]. + +Code déprécié +------------- + +La migration vers ToPIA 3.0 voit un certain nombre de classes et attributs dépréciés. + +Gérer les impacts n'est pas *nécessaire* pour passer à ToPIA 3.0, mais c'est fortement recommandé. En effet, ce code +deviendra incompatible avec ToPIA 3.1, gérer les impacts au plus tôt permettra une migration en douceur. + +Pas vrai, à partir de la 3.0-alpha-6, on casse la rétro-compatibilité car sinon +c'est trop compliqué à gérer. + +### Propriétés TOPIA_* + + + + +Les constantes suivantes sont à remplacer par leurs homologues avec le prefix *PROPERTY_* : + + * `TopiaEntity#TOPIA_ID` devient `TopiaEntity#PROPERTY_TOPIA_ID` + * `TopiaEntity#TOPIA_CREATE_DATE` devient `TopiaEntity#PROPERTY_TOPIA_CREATE_DATE` + * `TopiaEntity#TOPIA_VERSION` devient `TopiaEntity#PROPERTY_TOPIA_VERSION` + * `TopiaEntityContextable#TOPIA_CONTEXT` devient `TopiaEntityContextable#PROPERTY_TOPIA_CONTEXT` + * `TopiaEntity#COMPOSITE` devient `TopiaEntityContextable#PROPERTY_COMPOSITE` + * `TopiaEntity#AGGREGATE` devient `TopiaEntityContextable#PROPERTY_AGGREGATE` + + +### TopiaId + + + +La classe sera supprimée. Pour manipuler les topiaId, il faut utiliser le **TopiaIdFactory**. + +La forme des topiaIds générés à changé. Si vous souhaitez conserver l'ancienne forme parce que vous +souhaitez conserver l'uniformité avec une base de données existante ou si vous utilisez +topia-service-security, vous devez explicitement le spécifier à Topia 3.0 via la configuration : + +{% highlight properties %} +topia.persistence.topiaIdFactoryClassName=org.nuiton.topia.persistence.internal.LegacyTopiaIdFactory +{% endhighlight %} + + +### TopiaContextImplementor est supprimé + +Cette classe joue un rôle central dans ToPIA 2. Elle n'existe plus dans ToPIA 3. + +TODO Expliquer par quoi remplacer. + +### TopiaEntity modifié + + +Les méthodes (`getComposite()`, `getAggregate()`) ont été déplacées vers le contrat [TopiaEntityContextable]. + + +### TopiaService#preInit(...) et TopiaService#postInit(...) + + + + +Les services implémentant **TopiaService** doivent changer leur code. +Les méthodes `preInit(TopiaContextImplementor)` et `postInit(TopiaContextImplementor)` doivent être remplacées par +`preInit(TopiaContext)` et `postInit(TopiaContext)` + +### Service de migration + + + + +Toute référence à **TopiaContextImplementor** a été supprimé et remplacée par +**TopiaContext**, cela change donc les signatures et rend le code incompatible, +il faut donc bien effectuer le remplacement dans les callbacks que vous avez +écrits. + + +### Gestion des exceptions + + + +**TopiaRuntimeException** est dépréciée. Il faut désormais déclarer **TopiaException** dans la clause catch. +**TopiaException** devient Runtime. Il n'est plus nécessaire de l'intercepter. + +### Généricité + + + +Les methodes suivantes sont désormais génériques : + + * `TopiaContext#find(...)` + * `TopiaContext#findAll(...)` + * `TopiaContext#findUnique(...)` + +Si le code précédent réalisait un cast, il faut le supprimer car cela peut causer maintenant une erreur de compilation. + + +### TopiaContextListener + + + + +L'API **TopiaContextListener** contenant des opérations de manipulation du schéma de base de données a été déprécié. +L'interface est remplacée par **TopiaSchemaListener**. Les méthodes présentes sur le **TopiaContext** qui servaient à +ajouter/supprimer un **TopiaContextListener** sont elles aussi dépréciées au profit de méthodes manipulant des +**TopiaSchemaListener**. + + +### Transformer pour la génération des Dao + + + + +Si vous utilisiez explicitement le Transformer de DAO dans la configuration de votre plugin Eugene, il faut maintenant +changer le nouveau Transformer : org.nuiton.topia.templates.EntityDaoTransformer + +Exemple de configuration : + +{% highlight xml %} +<plugin> + <groupId>org.nuiton.eugene</groupId> + <artifactId>eugene-maven-plugin</artifactId> + <executions> + <execution> + <id>generate-entities</id> + <phase>generate-sources</phase> + <configuration> + <inputs>classpath:model:/:myProject.objectmodel</inputs> + <defaultPackage>org.project.entities</defaultPackage> + <templates> + org.nuiton.topia.templates.EntityDaoTransformer + </templates> + </configuration> + <goals> + <goal>generate</goal> + </goals> + </execution> + </executions> +</plugin> +{% endhighlight %} + + + + + +Si vous aviez surchargé certains des DAO générés, vous devrez probablement les renommer et changer leur signature. +Surveillez les logs de génération des DAO à la recherche des messages tels que : + +{% highlight bash %} + WARN [main] (EntityDaoTransformer.java:384) warnOnLegacyClassDetected - Legacy DAO detected ! + - You should consider renaming 'org.project.entities.ZoneDAOImpl' to 'org.project.entities.AbstractZoneTopiaDao' + - Expected class declaration is : public class AbstractZoneTopiaDao<E extends Zone> extends GeneratedZoneTopiaDao<E> { +{% endhighlight %} + +Pour l'entité Zone, vous devez supprimer votre ZoneDAO et votre ZoneDAOImpl et déplacer les méthodes +* dans ZoneTopiaDao si la méthode ne peut être utilisée seulement pour l'entité Zone (et pas les sous-classes de Zone) +* dans AbstractZoneTopiaDao si la méthode peut être utilisée pour Zone ET les entités qui héritent de Zone + + +### Méthodes findAll, find et findUnique sur TopiaContext + + + + +Si vous utilisiez ces méthodes, il est désormais recommandé d'utiliser les méthodes sur le DAO associé à votre éntité +ou par l'intermédiare du **TopiaJpaSupport**. + +#### Depuis un DAO surchargé + +Pour faciliter la migration, ces méthodes ont été portées sur l'interface deprecated **TopiaDAO**. Donc si dans votre +DAO vous faisiez : + +{% highlight java %} +List<Zone> zones = context.find("FROM Zone WHERE name=:name" , startIndex, endIndex, "name", "Principale"); +{% endhighlight %} + +Il vous suffit de retirer le "context." : + +{% highlight java %} +List<Zone> zones = find("FROM Zone WHERE name=:name" , startIndex, endIndex, "name", "Principale"); +{% endhighlight %} + +#### Dans tous les cas + +Dans tous les cas, ces méthodes étant dépréciées, vous devriez consulter leur Javadoc pour savoir par quels appels de +méthodes les remplacer. S'il n'y pas de documentation, il faut remplacer le code appellant par le corps de la méthode dépréciée +(fonctionnalité « inline » dans l'IDE). + +### Amélioration du code sql + +Voir [TopiaSqlQuery], [TopiaSqlWork] et [TopiaSqlSupport]. + + +Refactoring des packages +------------------------ + + + +Un grand nombre de classes ont été déplacées. Voici les principaux renommages : + + org.nuiton.topia.TopiaException -> org.nuiton.topia.persistence.TopiaException + org.nuiton.topia.TopiaDaoSupplier -> org.nuiton.topia.persistence.TopiaDaoSupplier + org.nuiton.topia.TopiaJpaSupport -> org.nuiton.topia.persistence.support.TopiaJpaSupport + org.nuiton.topia.TopiaPersistenceContext -> org.nuiton.topia.persistence.TopiaPersistenceContext + org.nuiton.topia.TopiaNotFoundException -> org.nuiton.topia.persistence.TopiaNotFoundException + org.nuiton.topia.TopiaApplicationContextCache -> org.nuiton.topia.persistence.TopiaApplicationContextCache + org.nuiton.topia.TopiaTransaction -> org.nuiton.topia.persistence.TopiaTransaction + org.nuiton.topia.persistence.TopiaHibernateSessionRegistry -> org.nuiton.topia.persistence.internal.TopiaHibernateSessionRegistry + org.nuiton.topia.TopiaListenableSupport -> org.nuiton.topia.persistence.support.TopiaListenableSupport + org.nuiton.topia.persistence.HibernateProvider -> org.nuiton.topia.persistence.internal.HibernateProvider + org.nuiton.topia.TopiaContextFactory -> org.nuiton.topia.persistence.TopiaConfigurationConstants + + +Le refactoring peut s'effectuer facilement en appliquant les commandes suivantes : + +{% highlight bash %} +$ grep -m 1 -nr 'org.nuiton.topia.TopiaException' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/org.nuiton.topia.TopiaException/org.nuiton.topia.persistence.TopiaException/g' +$ grep -m 1 -nr 'org.nuiton.topia.TopiaDaoSupplier' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/org.nuiton.topia.TopiaDaoSupplier/org.nuiton.topia.persistence.TopiaDaoSupplier/g' +$ grep -m 1 -nr 'org.nuiton.topia.TopiaJpaSupport' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/org.nuiton.topia.TopiaJpaSupport/org.nuiton.topia.persistence.support.TopiaJpaSupport/g' +$ grep -m 1 -nr 'org.nuiton.topia.TopiaPersistenceContext' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/org.nuiton.topia.TopiaPersistenceContext/org.nuiton.topia.persistence.TopiaPersistenceContext/g' +$ grep -m 1 -nr 'org.nuiton.topia.TopiaNotFoundException' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/org.nuiton.topia.TopiaNotFoundException/org.nuiton.topia.persistence.TopiaNotFoundException/g' +$ grep -m 1 -nr 'org.nuiton.topia.TopiaApplicationContextCache' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/org.nuiton.topia.TopiaApplicationContextCache/org.nuiton.topia.persistence.TopiaApplicationContextCache/g' +$ grep -m 1 -nr 'org.nuiton.topia.TopiaTransaction' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/org.nuiton.topia.TopiaTransaction/org.nuiton.topia.persistence.TopiaTransaction/g' +$ grep -m 1 -nr 'org.nuiton.topia.persistence.TopiaHibernateSessionRegistry' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/org.nuiton.topia.persistence.TopiaHibernateSessionRegistry/org.nuiton.topia.persistence.internal.TopiaHibernateSessionRegistry/g' +$ grep -m 1 -nr 'org.nuiton.topia.TopiaListenableSupport' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/org.nuiton.topia.TopiaListenableSupport/org.nuiton.topia.persistence.support.TopiaListenableSupport/g' +$ grep -m 1 -nr 'org.nuiton.topia.persistence.HibernateProvider' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/org.nuiton.topia.persistence.HibernateProvider/org.nuiton.topia.persistence.internal.HibernateProvider/g' +$ grep -m 1 -nr 'org.nuiton.topia.TopiaContextFactory' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/org.nuiton.topia.TopiaContextFactory/org.nuiton.topia.persistence.TopiaConfigurationConstants/g' +$ grep -m 1 -nr 'TopiaContextFactory.CONFIG_' . | awk -F ':' '{print $1}' | xargs sed -i -e 's/TopiaContextFactory.CONFIG_/TopiaConfigurationConstants.CONFIG_/g' +{% endhighlight %} + +FAQ +--- + +Voici quelques appels faisables en ToPIA 2.x et leur équivalent dans ToPIA 3 + +### Démarrage d'une nouvelle transaction + +{% highlight java %} +// ToPIA 2.x : rootTx étant le TopiaContext root +TopiaContext transaction = rootTx.beginTransaction(); + +// ToPIA 3 : rootTx étant le MyModelTopiaApplicationContext +MyModelTopiaPersistenceContext transaction = rootTx.newPersistenceContext(); +// Note : MyModelTopiaPersistenceContext implémente le contrat TopiaTransaction +{% endhighlight %} + + +### Accès à un Dao depuis un autre Dao + +{% highlight java %} +// ToPIA 2.x +SpeciesDAO speciesDAO = MyModelDAOHelper.getSpeciesDAO(getContext()); + +// ToPIA 3 +SpeciesTopiaDao speciesDAO = topiaDaoSupplier.getDao(Species.class, SpeciesTopiaDao.class); +{% endhighlight %} + + + +### Accès à la session Hibernate + +{% highlight java %} +// ToPIA 2.x : tx étant le TopiaContext +((MyModelTopiaPersistenceContextImplementor) tx).getHibernate().flush(); + +// ToPIA 3 : tx étant le MyModelTopiaPersistenceContext +tx.getHibernateSupport().getHibernateSession().flush(); +{% endhighlight %} + + + +### Liste des classes d'implémentation (ou des contrats) + +{% highlight java %} +// ToPIA 2.x +MyModelDAOHelper.getContractClasses(); +MyModelDAOHelper.getImplementationClasses(); + +// ToPIA 3 +MyModelEntityEnum.getContractClasses(); +MyModelEntityEnum.getImplementationClasses(); +{% endhighlight %} + + + +### TopiaPersistenceHelper renommé + +Le contrat **TopiaPersistenceHelper** est renommé en [TopiaEntityEnumProvider]. À noter que le **MyModelTopiaApplicationContext** +implémente ce contrat. + + +[Créer un projet ToPIA en partant de rien]: {{site.baseurl}}/tutos/from_scratch.html +[migrate_to_topia_3]: {{site.baseurl}}/tutos/migrate_to_topia_3.html +[ordered_to_indexed_migration]: {{site.baseurl}}/tutos/ordered_to_indexed_migration.html +[TopiaSqlQuery]: {{site.apidocs}}?org/nuiton/topia/persistence/support/TopiaSqlQuery.html +[TopiaSqlWork]: {{site.apidocs}}?org/nuiton/topia/persistence/support/TopiaSqlWork.html +[TopiaSqlSupport]: {{site.apidocs}}?org/nuiton/topia/persistence/support/TopiaSqlSupport.html +[TopiaEntityEnumProvider]: {{site.apidocs}}?org/nuiton/topia/persistence/TopiaEntityEnumProvider.html +[TopiaEntityContextable]: {{site.apidocs}}?org/nuiton/topia/persistence/TopiaEntityContextable.html + -- To stop receiving notification emails like this one, please contact nuiton.org SCM administrator <admin+scm@nuiton.org>.
participants (1)
-
nuiton.org scm