diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..fdc9bb8b94a2cf3cf1c107f981ab2fc41db781ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +out + +# Local .terraform directories +**/.terraform/* +.DS_Store +# .tfstate files +*.tfstate +*.tfstate.* + +__pycache__ + +# Crash log files +crash.log + +# Ignore any .tfvars files that are generated automatically for each Terraform run. Most +# .tfvars files are managed as part of configuration and so should be included in +# version control. +# +# example.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +.idea diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..6a35011362b0d4179ec5e317302541b07f2d0712 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,111 @@ +stages: + - training_resources + - day1 + - day2 + - day3 + - clean_training_resources + - destroy_infra + +deploy_trainee_repository: + stage: training_resources + image: python:3 + tags: + - compute + - small + needs: [] + before_script: + - git config --global user.email "bot@takima.fr" + - git config --global user.name "TakiBot" + script: + - pip install -U python-dotenv + - pip install GitPython + - pip install -r ./resources/docs/requirements + - cd scripts + - python deploy_trainee_repo.py + variables: + GIT_TRAINEE_REPOSITORY: "https://gitlab.takima.io/school/formation-dev-web/es6-epf-trainees" + when: manual + cache: + paths: + - .cache/pip + - venv/ + only: + - main + +deploy_solution_day_2_step_1: + stage: day2 + image: python:3 + tags: + - compute + - small + before_script: + - git config --global user.email "bot@takima.fr" + - git config --global user.name "TakiBot" + script: + - pip install -U python-dotenv + - pip install GitPython + - pip install -r ./resources/docs/requirements + - cd scripts + - python deploy_solution.py day-2 step-1 + variables: + GIT_TRAINEE_REPOSITORY: "https://gitlab.takima.io/school/formation-dev-web/es6-epf-trainees" + when: manual + needs: [] + cache: + paths: + - .cache/pip + - venv/ + only: + - main + +deploy_all_solutions_day_2: + stage: day2 + image: python:3 + tags: + - compute + - small + before_script: + - git config --global user.email "bot@takima.fr" + - git config --global user.name "TakiBot" + script: + - pip install -U python-dotenv + - pip install GitPython + - pip install -r ./resources/docs/requirements + - cd scripts + - python deploy_solution.py day-2 step-1 + variables: + GIT_TRAINEE_REPOSITORY: "https://gitlab.takima.io/school/formation-dev-web/es6-epf-trainees" + when: manual + needs: [] + cache: + paths: + - .cache/pip + - venv/ + only: + - main + +clean_trainee_repository: + stage: clean_training_resources + image: python:3 + tags: + - compute + - small + before_script: + - git config --global user.email "bot@takima.fr" + - git config --global user.name "TakiBot" + script: + - pip install -U python-dotenv + - pip install GitPython + - pip install -r ./resources/docs/requirements + - cd scripts + - python clean_trainee_repo.py + variables: + GIT_TRAINEE_REPOSITORY: "https://gitlab.takima.io/school/formation-dev-web/es6-epf-trainees" + cache: + paths: + - .cache/pip + - venv/ + when: manual + needs: [] + only: + - main diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f3a0d578066917680b76a805e4add7cf73637340 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Bootcamp Java + +Le but de ce TP est d'apprendre à construire une application web java spring en 5 jours. + +Le premier jour est dédié à la POO, java et ses apis + +La deuxième journée est consacré à java et son écosystème. (maven, jdbc ...) + +Les trois derniers jours sont un hackaton pour s'essayer à spring. \ No newline at end of file diff --git a/docs/0.guide/000.setup.md b/old/docs/0.guide/000.setup.md similarity index 100% rename from docs/0.guide/000.setup.md rename to old/docs/0.guide/000.setup.md diff --git a/docs/0.guide/100.npm.md b/old/docs/0.guide/100.npm.md similarity index 100% rename from docs/0.guide/100.npm.md rename to old/docs/0.guide/100.npm.md diff --git a/docs/0.guide/1000.ending.md b/old/docs/0.guide/1000.ending.md similarity index 100% rename from docs/0.guide/1000.ending.md rename to old/docs/0.guide/1000.ending.md diff --git a/docs/0.guide/200.spa.md b/old/docs/0.guide/200.spa.md similarity index 100% rename from docs/0.guide/200.spa.md rename to old/docs/0.guide/200.spa.md diff --git a/docs/0.guide/202.webpack.md b/old/docs/0.guide/202.webpack.md similarity index 100% rename from docs/0.guide/202.webpack.md rename to old/docs/0.guide/202.webpack.md diff --git a/docs/0.guide/203.webpack-js.md b/old/docs/0.guide/203.webpack-js.md similarity index 100% rename from docs/0.guide/203.webpack-js.md rename to old/docs/0.guide/203.webpack-js.md diff --git a/docs/0.guide/204.webpack-css.md b/old/docs/0.guide/204.webpack-css.md similarity index 100% rename from docs/0.guide/204.webpack-css.md rename to old/docs/0.guide/204.webpack-css.md diff --git a/docs/0.guide/205.webpack-html.md b/old/docs/0.guide/205.webpack-html.md similarity index 100% rename from docs/0.guide/205.webpack-html.md rename to old/docs/0.guide/205.webpack-html.md diff --git a/docs/0.guide/300-es6-classes.md b/old/docs/0.guide/300-es6-classes.md similarity index 100% rename from docs/0.guide/300-es6-classes.md rename to old/docs/0.guide/300-es6-classes.md diff --git a/docs/0.guide/301-Let-and-const.md b/old/docs/0.guide/301-Let-and-const.md similarity index 100% rename from docs/0.guide/301-Let-and-const.md rename to old/docs/0.guide/301-Let-and-const.md diff --git a/docs/0.guide/302-arrow-functions.md b/old/docs/0.guide/302-arrow-functions.md similarity index 100% rename from docs/0.guide/302-arrow-functions.md rename to old/docs/0.guide/302-arrow-functions.md diff --git a/docs/0.guide/310.functionnal-programming.md b/old/docs/0.guide/310.functionnal-programming.md similarity index 100% rename from docs/0.guide/310.functionnal-programming.md rename to old/docs/0.guide/310.functionnal-programming.md diff --git a/docs/0.guide/320-promises.md b/old/docs/0.guide/320-promises.md similarity index 100% rename from docs/0.guide/320-promises.md rename to old/docs/0.guide/320-promises.md diff --git a/docs/0.guide/605.components.md b/old/docs/0.guide/605.components.md similarity index 100% rename from docs/0.guide/605.components.md rename to old/docs/0.guide/605.components.md diff --git a/docs/0.guide/700-babel.md b/old/docs/0.guide/700-babel.md similarity index 100% rename from docs/0.guide/700-babel.md rename to old/docs/0.guide/700-babel.md diff --git a/docs/0.guide/800-style.md b/old/docs/0.guide/800-style.md similarity index 100% rename from docs/0.guide/800-style.md rename to old/docs/0.guide/800-style.md diff --git a/docs/0.guide/900.practice/910.practise.md b/old/docs/0.guide/900.practice/910.practise.md similarity index 100% rename from docs/0.guide/900.practice/910.practise.md rename to old/docs/0.guide/900.practice/910.practise.md diff --git a/docs/0.guide/900.practice/920.bonuses.md b/old/docs/0.guide/900.practice/920.bonuses.md similarity index 100% rename from docs/0.guide/900.practice/920.bonuses.md rename to old/docs/0.guide/900.practice/920.bonuses.md diff --git a/docs/0.guide/900.practice/README.md b/old/docs/0.guide/900.practice/README.md similarity index 100% rename from docs/0.guide/900.practice/README.md rename to old/docs/0.guide/900.practice/README.md diff --git a/docs/0.guide/_000.lost-found.md b/old/docs/0.guide/_000.lost-found.md similarity index 100% rename from docs/0.guide/_000.lost-found.md rename to old/docs/0.guide/_000.lost-found.md diff --git a/docs/0.guide/_620-sourcemaps.md b/old/docs/0.guide/_620-sourcemaps.md similarity index 100% rename from docs/0.guide/_620-sourcemaps.md rename to old/docs/0.guide/_620-sourcemaps.md diff --git a/docs/10.cheatsheet/CHEATSHEET.md b/old/docs/10.cheatsheet/CHEATSHEET.md similarity index 100% rename from docs/10.cheatsheet/CHEATSHEET.md rename to old/docs/10.cheatsheet/CHEATSHEET.md diff --git a/docs/README.md b/old/docs/README.md similarity index 100% rename from docs/README.md rename to old/docs/README.md diff --git a/docs/assets/bundle.png b/old/docs/assets/bundle.png similarity index 100% rename from docs/assets/bundle.png rename to old/docs/assets/bundle.png diff --git a/docs/assets/chrome-dev-tools-network.png b/old/docs/assets/chrome-dev-tools-network.png similarity index 100% rename from docs/assets/chrome-dev-tools-network.png rename to old/docs/assets/chrome-dev-tools-network.png diff --git a/docs/assets/clutter-before-spa.gif b/old/docs/assets/clutter-before-spa.gif similarity index 100% rename from docs/assets/clutter-before-spa.gif rename to old/docs/assets/clutter-before-spa.gif diff --git a/docs/assets/commit.png b/old/docs/assets/commit.png similarity index 100% rename from docs/assets/commit.png rename to old/docs/assets/commit.png diff --git a/docs/assets/component-architecture.png b/old/docs/assets/component-architecture.png similarity index 100% rename from docs/assets/component-architecture.png rename to old/docs/assets/component-architecture.png diff --git a/docs/assets/components.png b/old/docs/assets/components.png similarity index 100% rename from docs/assets/components.png rename to old/docs/assets/components.png diff --git a/docs/assets/http-server-semver.png b/old/docs/assets/http-server-semver.png similarity index 100% rename from docs/assets/http-server-semver.png rename to old/docs/assets/http-server-semver.png diff --git a/docs/assets/icons/babel.png b/old/docs/assets/icons/babel.png similarity index 100% rename from docs/assets/icons/babel.png rename to old/docs/assets/icons/babel.png diff --git a/docs/assets/icons/bootstrap-64x64.png b/old/docs/assets/icons/bootstrap-64x64.png similarity index 100% rename from docs/assets/icons/bootstrap-64x64.png rename to old/docs/assets/icons/bootstrap-64x64.png diff --git a/docs/assets/icons/es6.png b/old/docs/assets/icons/es6.png similarity index 100% rename from docs/assets/icons/es6.png rename to old/docs/assets/icons/es6.png diff --git a/docs/assets/icons/lodash.png b/old/docs/assets/icons/lodash.png similarity index 100% rename from docs/assets/icons/lodash.png rename to old/docs/assets/icons/lodash.png diff --git a/docs/assets/icons/npm-64x64.png b/old/docs/assets/icons/npm-64x64.png similarity index 100% rename from docs/assets/icons/npm-64x64.png rename to old/docs/assets/icons/npm-64x64.png diff --git a/docs/assets/icons/sass.png b/old/docs/assets/icons/sass.png similarity index 100% rename from docs/assets/icons/sass.png rename to old/docs/assets/icons/sass.png diff --git a/docs/assets/icons/webpack.png b/old/docs/assets/icons/webpack.png similarity index 100% rename from docs/assets/icons/webpack.png rename to old/docs/assets/icons/webpack.png diff --git a/docs/assets/icons/yarn-64x64.png b/old/docs/assets/icons/yarn-64x64.png similarity index 100% rename from docs/assets/icons/yarn-64x64.png rename to old/docs/assets/icons/yarn-64x64.png diff --git a/docs/assets/mockup.png b/old/docs/assets/mockup.png similarity index 100% rename from docs/assets/mockup.png rename to old/docs/assets/mockup.png diff --git a/docs/assets/multiple-page-application.png b/old/docs/assets/multiple-page-application.png similarity index 100% rename from docs/assets/multiple-page-application.png rename to old/docs/assets/multiple-page-application.png diff --git a/docs/assets/mvc-architecture.png b/old/docs/assets/mvc-architecture.png similarity index 100% rename from docs/assets/mvc-architecture.png rename to old/docs/assets/mvc-architecture.png diff --git a/docs/assets/node_modules.png b/old/docs/assets/node_modules.png similarity index 100% rename from docs/assets/node_modules.png rename to old/docs/assets/node_modules.png diff --git a/docs/assets/not-bundle.png b/old/docs/assets/not-bundle.png similarity index 100% rename from docs/assets/not-bundle.png rename to old/docs/assets/not-bundle.png diff --git a/docs/assets/project-image.png b/old/docs/assets/project-image.png similarity index 100% rename from docs/assets/project-image.png rename to old/docs/assets/project-image.png diff --git a/docs/assets/shell-html.png b/old/docs/assets/shell-html.png similarity index 100% rename from docs/assets/shell-html.png rename to old/docs/assets/shell-html.png diff --git a/docs/assets/shell.png b/old/docs/assets/shell.png similarity index 100% rename from docs/assets/shell.png rename to old/docs/assets/shell.png diff --git a/docs/assets/single_page_application.png b/old/docs/assets/single_page_application.png similarity index 100% rename from docs/assets/single_page_application.png rename to old/docs/assets/single_page_application.png diff --git a/docs/assets/spa.png b/old/docs/assets/spa.png similarity index 100% rename from docs/assets/spa.png rename to old/docs/assets/spa.png diff --git a/done/back-end/.gitignore b/old/done/back-end/.gitignore similarity index 100% rename from done/back-end/.gitignore rename to old/done/back-end/.gitignore diff --git a/done/back-end/config.js b/old/done/back-end/config.js similarity index 100% rename from done/back-end/config.js rename to old/done/back-end/config.js diff --git a/done/back-end/package-lock.json b/old/done/back-end/package-lock.json similarity index 100% rename from done/back-end/package-lock.json rename to old/done/back-end/package-lock.json diff --git a/done/back-end/package.json b/old/done/back-end/package.json similarity index 100% rename from done/back-end/package.json rename to old/done/back-end/package.json diff --git a/done/back-end/server.js b/old/done/back-end/server.js similarity index 100% rename from done/back-end/server.js rename to old/done/back-end/server.js diff --git a/done/front-end/.babelrc b/old/done/front-end/.babelrc similarity index 100% rename from done/front-end/.babelrc rename to old/done/front-end/.babelrc diff --git a/done/front-end/browserslist b/old/done/front-end/browserslist similarity index 100% rename from done/front-end/browserslist rename to old/done/front-end/browserslist diff --git a/done/front-end/package-lock.json b/old/done/front-end/package-lock.json similarity index 100% rename from done/front-end/package-lock.json rename to old/done/front-end/package-lock.json diff --git a/done/front-end/package.json b/old/done/front-end/package.json similarity index 100% rename from done/front-end/package.json rename to old/done/front-end/package.json diff --git a/done/front-end/src/app/components/footer/footer.component.html b/old/done/front-end/src/app/components/footer/footer.component.html similarity index 100% rename from done/front-end/src/app/components/footer/footer.component.html rename to old/done/front-end/src/app/components/footer/footer.component.html diff --git a/done/front-end/src/app/components/footer/footer.component.js b/old/done/front-end/src/app/components/footer/footer.component.js similarity index 100% rename from done/front-end/src/app/components/footer/footer.component.js rename to old/done/front-end/src/app/components/footer/footer.component.js diff --git a/done/front-end/src/app/components/footer/footer.component.scss b/old/done/front-end/src/app/components/footer/footer.component.scss similarity index 100% rename from done/front-end/src/app/components/footer/footer.component.scss rename to old/done/front-end/src/app/components/footer/footer.component.scss diff --git a/done/front-end/src/app/components/game/card/card.component.html b/old/done/front-end/src/app/components/game/card/card.component.html similarity index 100% rename from done/front-end/src/app/components/game/card/card.component.html rename to old/done/front-end/src/app/components/game/card/card.component.html diff --git a/done/front-end/src/app/components/game/card/card.component.js b/old/done/front-end/src/app/components/game/card/card.component.js similarity index 100% rename from done/front-end/src/app/components/game/card/card.component.js rename to old/done/front-end/src/app/components/game/card/card.component.js diff --git a/done/front-end/src/app/components/game/card/card.component.scss b/old/done/front-end/src/app/components/game/card/card.component.scss similarity index 100% rename from done/front-end/src/app/components/game/card/card.component.scss rename to old/done/front-end/src/app/components/game/card/card.component.scss diff --git a/done/front-end/src/app/components/game/game.component.html b/old/done/front-end/src/app/components/game/game.component.html similarity index 100% rename from done/front-end/src/app/components/game/game.component.html rename to old/done/front-end/src/app/components/game/game.component.html diff --git a/done/front-end/src/app/components/game/game.component.js b/old/done/front-end/src/app/components/game/game.component.js similarity index 100% rename from done/front-end/src/app/components/game/game.component.js rename to old/done/front-end/src/app/components/game/game.component.js diff --git a/done/front-end/src/app/components/game/game.component.scss b/old/done/front-end/src/app/components/game/game.component.scss similarity index 100% rename from done/front-end/src/app/components/game/game.component.scss rename to old/done/front-end/src/app/components/game/game.component.scss diff --git a/done/front-end/src/app/components/navbar/navbar.component.html b/old/done/front-end/src/app/components/navbar/navbar.component.html similarity index 100% rename from done/front-end/src/app/components/navbar/navbar.component.html rename to old/done/front-end/src/app/components/navbar/navbar.component.html diff --git a/done/front-end/src/app/components/navbar/navbar.component.js b/old/done/front-end/src/app/components/navbar/navbar.component.js similarity index 100% rename from done/front-end/src/app/components/navbar/navbar.component.js rename to old/done/front-end/src/app/components/navbar/navbar.component.js diff --git a/done/front-end/src/app/components/navbar/navbar.component.scss b/old/done/front-end/src/app/components/navbar/navbar.component.scss similarity index 100% rename from done/front-end/src/app/components/navbar/navbar.component.scss rename to old/done/front-end/src/app/components/navbar/navbar.component.scss diff --git a/done/front-end/src/app/components/score/score.component.html b/old/done/front-end/src/app/components/score/score.component.html similarity index 100% rename from done/front-end/src/app/components/score/score.component.html rename to old/done/front-end/src/app/components/score/score.component.html diff --git a/done/front-end/src/app/components/score/score.component.js b/old/done/front-end/src/app/components/score/score.component.js similarity index 100% rename from done/front-end/src/app/components/score/score.component.js rename to old/done/front-end/src/app/components/score/score.component.js diff --git a/done/front-end/src/app/components/score/score.component.scss b/old/done/front-end/src/app/components/score/score.component.scss similarity index 100% rename from done/front-end/src/app/components/score/score.component.scss rename to old/done/front-end/src/app/components/score/score.component.scss diff --git a/done/front-end/src/app/components/welcome/welcome.component.html b/old/done/front-end/src/app/components/welcome/welcome.component.html similarity index 100% rename from done/front-end/src/app/components/welcome/welcome.component.html rename to old/done/front-end/src/app/components/welcome/welcome.component.html diff --git a/done/front-end/src/app/components/welcome/welcome.component.js b/old/done/front-end/src/app/components/welcome/welcome.component.js similarity index 100% rename from done/front-end/src/app/components/welcome/welcome.component.js rename to old/done/front-end/src/app/components/welcome/welcome.component.js diff --git a/done/front-end/src/app/components/welcome/welcome.component.scss b/old/done/front-end/src/app/components/welcome/welcome.component.scss similarity index 100% rename from done/front-end/src/app/components/welcome/welcome.component.scss rename to old/done/front-end/src/app/components/welcome/welcome.component.scss diff --git a/done/front-end/src/app/scripts/component.js b/old/done/front-end/src/app/scripts/component.js similarity index 100% rename from done/front-end/src/app/scripts/component.js rename to old/done/front-end/src/app/scripts/component.js diff --git a/done/front-end/src/app/scripts/router.js b/old/done/front-end/src/app/scripts/router.js similarity index 100% rename from done/front-end/src/app/scripts/router.js rename to old/done/front-end/src/app/scripts/router.js diff --git a/done/front-end/src/app/scripts/utils.js b/old/done/front-end/src/app/scripts/utils.js similarity index 100% rename from done/front-end/src/app/scripts/utils.js rename to old/done/front-end/src/app/scripts/utils.js diff --git a/done/front-end/src/app/styles/_colors.scss b/old/done/front-end/src/app/styles/_colors.scss similarity index 100% rename from done/front-end/src/app/styles/_colors.scss rename to old/done/front-end/src/app/styles/_colors.scss diff --git a/done/front-end/src/app/styles/style.scss b/old/done/front-end/src/app/styles/style.scss similarity index 100% rename from done/front-end/src/app/styles/style.scss rename to old/done/front-end/src/app/styles/style.scss diff --git a/done/front-end/src/assets/cards/back.png b/old/done/front-end/src/assets/cards/back.png similarity index 100% rename from done/front-end/src/assets/cards/back.png rename to old/done/front-end/src/assets/cards/back.png diff --git a/done/front-end/src/assets/cards/card-0.png b/old/done/front-end/src/assets/cards/card-0.png similarity index 100% rename from done/front-end/src/assets/cards/card-0.png rename to old/done/front-end/src/assets/cards/card-0.png diff --git a/done/front-end/src/assets/cards/card-1.png b/old/done/front-end/src/assets/cards/card-1.png similarity index 100% rename from done/front-end/src/assets/cards/card-1.png rename to old/done/front-end/src/assets/cards/card-1.png diff --git a/done/front-end/src/assets/cards/card-2.png b/old/done/front-end/src/assets/cards/card-2.png similarity index 100% rename from done/front-end/src/assets/cards/card-2.png rename to old/done/front-end/src/assets/cards/card-2.png diff --git a/done/front-end/src/assets/cards/card-3.png b/old/done/front-end/src/assets/cards/card-3.png similarity index 100% rename from done/front-end/src/assets/cards/card-3.png rename to old/done/front-end/src/assets/cards/card-3.png diff --git a/done/front-end/src/assets/cards/card-4.png b/old/done/front-end/src/assets/cards/card-4.png similarity index 100% rename from done/front-end/src/assets/cards/card-4.png rename to old/done/front-end/src/assets/cards/card-4.png diff --git a/done/front-end/src/assets/cards/card-5.png b/old/done/front-end/src/assets/cards/card-5.png similarity index 100% rename from done/front-end/src/assets/cards/card-5.png rename to old/done/front-end/src/assets/cards/card-5.png diff --git a/done/front-end/src/assets/cards/card-6.png b/old/done/front-end/src/assets/cards/card-6.png similarity index 100% rename from done/front-end/src/assets/cards/card-6.png rename to old/done/front-end/src/assets/cards/card-6.png diff --git a/done/front-end/src/assets/cards/card-7.png b/old/done/front-end/src/assets/cards/card-7.png similarity index 100% rename from done/front-end/src/assets/cards/card-7.png rename to old/done/front-end/src/assets/cards/card-7.png diff --git a/done/front-end/src/assets/cards/card-8.png b/old/done/front-end/src/assets/cards/card-8.png similarity index 100% rename from done/front-end/src/assets/cards/card-8.png rename to old/done/front-end/src/assets/cards/card-8.png diff --git a/done/front-end/src/assets/cards/card-9.png b/old/done/front-end/src/assets/cards/card-9.png similarity index 100% rename from done/front-end/src/assets/cards/card-9.png rename to old/done/front-end/src/assets/cards/card-9.png diff --git a/done/front-end/src/assets/happy_homer.jpg b/old/done/front-end/src/assets/happy_homer.jpg similarity index 100% rename from done/front-end/src/assets/happy_homer.jpg rename to old/done/front-end/src/assets/happy_homer.jpg diff --git a/done/front-end/src/assets/logo_take_my_money.png b/old/done/front-end/src/assets/logo_take_my_money.png similarity index 100% rename from done/front-end/src/assets/logo_take_my_money.png rename to old/done/front-end/src/assets/logo_take_my_money.png diff --git a/done/front-end/src/favicon.ico b/old/done/front-end/src/favicon.ico similarity index 100% rename from done/front-end/src/favicon.ico rename to old/done/front-end/src/favicon.ico diff --git a/done/front-end/src/index.html b/old/done/front-end/src/index.html similarity index 100% rename from done/front-end/src/index.html rename to old/done/front-end/src/index.html diff --git a/done/front-end/src/main.js b/old/done/front-end/src/main.js similarity index 100% rename from done/front-end/src/main.js rename to old/done/front-end/src/main.js diff --git a/done/front-end/webpack.config.js b/old/done/front-end/webpack.config.js similarity index 100% rename from done/front-end/webpack.config.js rename to old/done/front-end/webpack.config.js diff --git a/master3.json b/old/master3.json similarity index 100% rename from master3.json rename to old/master3.json diff --git a/press.sh b/old/press.sh similarity index 100% rename from press.sh rename to old/press.sh diff --git a/resources/arrow-fn/router.js b/old/resources/arrow-fn/router.js similarity index 100% rename from resources/arrow-fn/router.js rename to old/resources/arrow-fn/router.js diff --git a/resources/arrow-fn/score.js b/old/resources/arrow-fn/score.js similarity index 100% rename from resources/arrow-fn/score.js rename to old/resources/arrow-fn/score.js diff --git a/resources/arrow-fn/welcome.js b/old/resources/arrow-fn/welcome.js similarity index 100% rename from resources/arrow-fn/welcome.js rename to old/resources/arrow-fn/welcome.js diff --git a/resources/classes/router.js b/old/resources/classes/router.js similarity index 100% rename from resources/classes/router.js rename to old/resources/classes/router.js diff --git a/resources/classes/score.js b/old/resources/classes/score.js similarity index 100% rename from resources/classes/score.js rename to old/resources/classes/score.js diff --git a/resources/classes/welcome.js b/old/resources/classes/welcome.js similarity index 100% rename from resources/classes/welcome.js rename to old/resources/classes/welcome.js diff --git a/resources/component/card.component.js b/old/resources/component/card.component.js similarity index 100% rename from resources/component/card.component.js rename to old/resources/component/card.component.js diff --git a/resources/component/component.js b/old/resources/component/component.js similarity index 100% rename from resources/component/component.js rename to old/resources/component/component.js diff --git a/resources/component/game.component.js b/old/resources/component/game.component.js similarity index 100% rename from resources/component/game.component.js rename to old/resources/component/game.component.js diff --git a/resources/component/score.component.js b/old/resources/component/score.component.js similarity index 100% rename from resources/component/score.component.js rename to old/resources/component/score.component.js diff --git a/resources/component/welcome.component.js b/old/resources/component/welcome.component.js similarity index 100% rename from resources/component/welcome.component.js rename to old/resources/component/welcome.component.js diff --git a/resources/html-import/score.js b/old/resources/html-import/score.js similarity index 100% rename from resources/html-import/score.js rename to old/resources/html-import/score.js diff --git a/resources/html-import/welcome.js b/old/resources/html-import/welcome.js similarity index 100% rename from resources/html-import/welcome.js rename to old/resources/html-import/welcome.js diff --git a/resources/init/back-end/.gitignore b/old/resources/init/back-end/.gitignore similarity index 100% rename from resources/init/back-end/.gitignore rename to old/resources/init/back-end/.gitignore diff --git a/resources/init/back-end/config.js b/old/resources/init/back-end/config.js similarity index 100% rename from resources/init/back-end/config.js rename to old/resources/init/back-end/config.js diff --git a/resources/init/back-end/package-lock.json b/old/resources/init/back-end/package-lock.json similarity index 100% rename from resources/init/back-end/package-lock.json rename to old/resources/init/back-end/package-lock.json diff --git a/resources/init/back-end/package.json b/old/resources/init/back-end/package.json similarity index 100% rename from resources/init/back-end/package.json rename to old/resources/init/back-end/package.json diff --git a/resources/init/back-end/server.js b/old/resources/init/back-end/server.js similarity index 100% rename from resources/init/back-end/server.js rename to old/resources/init/back-end/server.js diff --git a/resources/init/front-end/src/app/scripts/game.js b/old/resources/init/front-end/src/app/scripts/game.js similarity index 100% rename from resources/init/front-end/src/app/scripts/game.js rename to old/resources/init/front-end/src/app/scripts/game.js diff --git a/resources/init/front-end/src/app/scripts/score.js b/old/resources/init/front-end/src/app/scripts/score.js similarity index 100% rename from resources/init/front-end/src/app/scripts/score.js rename to old/resources/init/front-end/src/app/scripts/score.js diff --git a/resources/init/front-end/src/app/scripts/utils.js b/old/resources/init/front-end/src/app/scripts/utils.js similarity index 100% rename from resources/init/front-end/src/app/scripts/utils.js rename to old/resources/init/front-end/src/app/scripts/utils.js diff --git a/resources/init/front-end/src/app/scripts/welcome.js b/old/resources/init/front-end/src/app/scripts/welcome.js similarity index 100% rename from resources/init/front-end/src/app/scripts/welcome.js rename to old/resources/init/front-end/src/app/scripts/welcome.js diff --git a/resources/init/front-end/src/app/styles/bootstrap.css b/old/resources/init/front-end/src/app/styles/bootstrap.css similarity index 100% rename from resources/init/front-end/src/app/styles/bootstrap.css rename to old/resources/init/front-end/src/app/styles/bootstrap.css diff --git a/resources/init/front-end/src/app/styles/style.css b/old/resources/init/front-end/src/app/styles/style.css similarity index 100% rename from resources/init/front-end/src/app/styles/style.css rename to old/resources/init/front-end/src/app/styles/style.css diff --git a/resources/init/front-end/src/app/views/game.html b/old/resources/init/front-end/src/app/views/game.html similarity index 100% rename from resources/init/front-end/src/app/views/game.html rename to old/resources/init/front-end/src/app/views/game.html diff --git a/resources/init/front-end/src/app/views/score.html b/old/resources/init/front-end/src/app/views/score.html similarity index 100% rename from resources/init/front-end/src/app/views/score.html rename to old/resources/init/front-end/src/app/views/score.html diff --git a/resources/init/front-end/src/app/views/welcome.html b/old/resources/init/front-end/src/app/views/welcome.html similarity index 100% rename from resources/init/front-end/src/app/views/welcome.html rename to old/resources/init/front-end/src/app/views/welcome.html diff --git a/resources/init/front-end/src/assets/cards/back.png b/old/resources/init/front-end/src/assets/cards/back.png similarity index 100% rename from resources/init/front-end/src/assets/cards/back.png rename to old/resources/init/front-end/src/assets/cards/back.png diff --git a/resources/init/front-end/src/assets/cards/card-0.png b/old/resources/init/front-end/src/assets/cards/card-0.png similarity index 100% rename from resources/init/front-end/src/assets/cards/card-0.png rename to old/resources/init/front-end/src/assets/cards/card-0.png diff --git a/resources/init/front-end/src/assets/cards/card-1.png b/old/resources/init/front-end/src/assets/cards/card-1.png similarity index 100% rename from resources/init/front-end/src/assets/cards/card-1.png rename to old/resources/init/front-end/src/assets/cards/card-1.png diff --git a/resources/init/front-end/src/assets/cards/card-2.png b/old/resources/init/front-end/src/assets/cards/card-2.png similarity index 100% rename from resources/init/front-end/src/assets/cards/card-2.png rename to old/resources/init/front-end/src/assets/cards/card-2.png diff --git a/resources/init/front-end/src/assets/cards/card-3.png b/old/resources/init/front-end/src/assets/cards/card-3.png similarity index 100% rename from resources/init/front-end/src/assets/cards/card-3.png rename to old/resources/init/front-end/src/assets/cards/card-3.png diff --git a/resources/init/front-end/src/assets/cards/card-4.png b/old/resources/init/front-end/src/assets/cards/card-4.png similarity index 100% rename from resources/init/front-end/src/assets/cards/card-4.png rename to old/resources/init/front-end/src/assets/cards/card-4.png diff --git a/resources/init/front-end/src/assets/cards/card-5.png b/old/resources/init/front-end/src/assets/cards/card-5.png similarity index 100% rename from resources/init/front-end/src/assets/cards/card-5.png rename to old/resources/init/front-end/src/assets/cards/card-5.png diff --git a/resources/init/front-end/src/assets/cards/card-6.png b/old/resources/init/front-end/src/assets/cards/card-6.png similarity index 100% rename from resources/init/front-end/src/assets/cards/card-6.png rename to old/resources/init/front-end/src/assets/cards/card-6.png diff --git a/resources/init/front-end/src/assets/cards/card-7.png b/old/resources/init/front-end/src/assets/cards/card-7.png similarity index 100% rename from resources/init/front-end/src/assets/cards/card-7.png rename to old/resources/init/front-end/src/assets/cards/card-7.png diff --git a/resources/init/front-end/src/assets/cards/card-8.png b/old/resources/init/front-end/src/assets/cards/card-8.png similarity index 100% rename from resources/init/front-end/src/assets/cards/card-8.png rename to old/resources/init/front-end/src/assets/cards/card-8.png diff --git a/resources/init/front-end/src/assets/cards/card-9.png b/old/resources/init/front-end/src/assets/cards/card-9.png similarity index 100% rename from resources/init/front-end/src/assets/cards/card-9.png rename to old/resources/init/front-end/src/assets/cards/card-9.png diff --git a/resources/init/front-end/src/assets/happy_homer.jpg b/old/resources/init/front-end/src/assets/happy_homer.jpg similarity index 100% rename from resources/init/front-end/src/assets/happy_homer.jpg rename to old/resources/init/front-end/src/assets/happy_homer.jpg diff --git a/resources/init/front-end/src/assets/logo_take_my_money.png b/old/resources/init/front-end/src/assets/logo_take_my_money.png similarity index 100% rename from resources/init/front-end/src/assets/logo_take_my_money.png rename to old/resources/init/front-end/src/assets/logo_take_my_money.png diff --git a/resources/init/front-end/src/index.html b/old/resources/init/front-end/src/index.html similarity index 100% rename from resources/init/front-end/src/index.html rename to old/resources/init/front-end/src/index.html diff --git a/resources/init/html-import/score.js b/old/resources/init/html-import/score.js similarity index 100% rename from resources/init/html-import/score.js rename to old/resources/init/html-import/score.js diff --git a/resources/let-const/router.js b/old/resources/let-const/router.js similarity index 100% rename from resources/let-const/router.js rename to old/resources/let-const/router.js diff --git a/resources/let-const/score.js b/old/resources/let-const/score.js similarity index 100% rename from resources/let-const/score.js rename to old/resources/let-const/score.js diff --git a/resources/let-const/welcome.js b/old/resources/let-const/welcome.js similarity index 100% rename from resources/let-const/welcome.js rename to old/resources/let-const/welcome.js diff --git a/resources/router/main.js b/old/resources/router/main.js similarity index 100% rename from resources/router/main.js rename to old/resources/router/main.js diff --git a/resources/router/router.js b/old/resources/router/router.js similarity index 100% rename from resources/router/router.js rename to old/resources/router/router.js diff --git a/resources/template-literals/router.js b/old/resources/template-literals/router.js similarity index 100% rename from resources/template-literals/router.js rename to old/resources/template-literals/router.js diff --git a/resources/template-literals/score.js b/old/resources/template-literals/score.js similarity index 100% rename from resources/template-literals/score.js rename to old/resources/template-literals/score.js diff --git a/resources/template-literals/welcome.js b/old/resources/template-literals/welcome.js similarity index 100% rename from resources/template-literals/welcome.js rename to old/resources/template-literals/welcome.js diff --git a/template/.gitlab-ci-m3-press-milestone.yml b/old/template/.gitlab-ci-m3-press-milestone.yml similarity index 100% rename from template/.gitlab-ci-m3-press-milestone.yml rename to old/template/.gitlab-ci-m3-press-milestone.yml diff --git a/templates/.gitlab-ci-m3-press-milestone.yml b/old/templates/.gitlab-ci-m3-press-milestone.yml similarity index 100% rename from templates/.gitlab-ci-m3-press-milestone.yml rename to old/templates/.gitlab-ci-m3-press-milestone.yml diff --git a/trainer/CR.md b/old/trainer/CR.md similarity index 100% rename from trainer/CR.md rename to old/trainer/CR.md diff --git a/trainer/questions.md b/old/trainer/questions.md similarity index 100% rename from trainer/questions.md rename to old/trainer/questions.md diff --git a/resources/boilerplate/emptydocs/.gitignore b/resources/boilerplate/emptydocs/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..45ddf0ae397075d91d1660f81bc5f6c39f60f9fb --- /dev/null +++ b/resources/boilerplate/emptydocs/.gitignore @@ -0,0 +1 @@ +site/ diff --git a/resources/boilerplate/emptydocs/README.md b/resources/boilerplate/emptydocs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d043d94c81e6a8f2fa646eddaa651475674caf79 --- /dev/null +++ b/resources/boilerplate/emptydocs/README.md @@ -0,0 +1,4 @@ +# Kubernetes in Action +------ + +Hello World! diff --git a/resources/boilerplate/emptydocs/docs/assets/takima-logo.jpg b/resources/boilerplate/emptydocs/docs/assets/takima-logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27efddb9a5829f7afdef9c4904999a347db18256 Binary files /dev/null and b/resources/boilerplate/emptydocs/docs/assets/takima-logo.jpg differ diff --git a/resources/boilerplate/emptydocs/docs/index.md b/resources/boilerplate/emptydocs/docs/index.md new file mode 100644 index 0000000000000000000000000000000000000000..415e64d6159a711b0432b671d39488075f1de6b8 --- /dev/null +++ b/resources/boilerplate/emptydocs/docs/index.md @@ -0,0 +1 @@ +# Java in Action - Guide \ No newline at end of file diff --git a/resources/boilerplate/emptydocs/mkdocs.yml b/resources/boilerplate/emptydocs/mkdocs.yml new file mode 100644 index 0000000000000000000000000000000000000000..ad9a47ae772f1d466cd3eadceb5a5f17055b875c --- /dev/null +++ b/resources/boilerplate/emptydocs/mkdocs.yml @@ -0,0 +1,16 @@ +site_name: Bootcamp Java +nav: + - Briefing: chapters/tp1/index.md +theme: + name: 'material' + features: + - navigation.tabs + - navigation.instant + +markdown_extensions: + - markdown.extensions.admonition: + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences diff --git a/resources/boilerplate/java-one-trainees/.gitlab-ci.yml b/resources/boilerplate/java-one-trainees/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..0cc783aef2c5688d9d05ed045d9742223ea470a5 --- /dev/null +++ b/resources/boilerplate/java-one-trainees/.gitlab-ci.yml @@ -0,0 +1,11 @@ +image: python:3 + +pages: + stage: deploy + script: + - ls public + artifacts: + paths: + - public + rules: + - if: '$CI_COMMIT_BRANCH == "main"' diff --git a/resources/docs/.gitignore b/resources/docs/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..45ddf0ae397075d91d1660f81bc5f6c39f60f9fb --- /dev/null +++ b/resources/docs/.gitignore @@ -0,0 +1 @@ +site/ diff --git a/resources/docs/2022-01-28-20-24-21.060-VBoxHeadless-27399.log b/resources/docs/2022-01-28-20-24-21.060-VBoxHeadless-27399.log new file mode 100644 index 0000000000000000000000000000000000000000..7ef1a420474a811ac0814fd88b527af9b11f5fa7 --- /dev/null +++ b/resources/docs/2022-01-28-20-24-21.060-VBoxHeadless-27399.log @@ -0,0 +1,12 @@ +Log created: 2022-01-28T20:24:21.603684000Z +Process ID: 27399 (0x6b07) +Parent PID: 1 (0x1) +Executable: /usr/lib/virtualbox/VBoxHeadless +Arg[0]: /usr/lib/virtualbox/VBoxHeadless +Arg[1]: --comment +Arg[2]: minikube +Arg[3]: --startvm +Arg[4]: 26d2a365-7d4c-4592-aefc-26080a946bb9 +Arg[5]: --vrde +Arg[6]: config +AddRef: illegal refcnt=3221225469 state=2 diff --git a/resources/docs/Dockerfile b/resources/docs/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a03c714b2367083b0fae045ab7195f2a0954f0c4 --- /dev/null +++ b/resources/docs/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.7-alpine3.14 + +WORKDIR /home + +COPY docs docs +COPY mkdocs.yml . +COPY requirements . + +RUN pip install -r requirements + +RUN mkdocs build +# site dir generated with previous cmd +WORKDIR /home/site + +ENTRYPOINT ["python3", "-m", "http.server", "8000"] \ No newline at end of file diff --git a/resources/docs/README.md b/resources/docs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..50f026c65f6c3a2657370da52a20080630c9f5a2 --- /dev/null +++ b/resources/docs/README.md @@ -0,0 +1,19 @@ + +# Démarrer en local la documentation + +## Avec Docker +```bash +./run.sh + +#then go to http://localhost:8000 +``` + +## Avec Python (orienté développement, hot-reload) +```bash +# install deps +pip install -r requirements + +# start mkdocs server +python -m mkdocs serve +``` + diff --git a/resources/docs/docs/assets/architectural_diagram.png b/resources/docs/docs/assets/architectural_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..1891741994dbc5dd50bdcf3f130b2b5c0d218cd7 Binary files /dev/null and b/resources/docs/docs/assets/architectural_diagram.png differ diff --git a/resources/docs/docs/assets/architectural_diagram_dao.png b/resources/docs/docs/assets/architectural_diagram_dao.png new file mode 100644 index 0000000000000000000000000000000000000000..b817269a58c6108e30db3f690d034f6e1e412db7 Binary files /dev/null and b/resources/docs/docs/assets/architectural_diagram_dao.png differ diff --git a/resources/docs/docs/assets/architectural_diagram_dto.png b/resources/docs/docs/assets/architectural_diagram_dto.png new file mode 100644 index 0000000000000000000000000000000000000000..832775f4df32ae9261d1a8356a22dd9cd5c78c47 Binary files /dev/null and b/resources/docs/docs/assets/architectural_diagram_dto.png differ diff --git a/resources/docs/docs/assets/architectural_diagram_model.png b/resources/docs/docs/assets/architectural_diagram_model.png new file mode 100644 index 0000000000000000000000000000000000000000..c54ee9becb1aa86d324c1eef358d8efe40d4a563 Binary files /dev/null and b/resources/docs/docs/assets/architectural_diagram_model.png differ diff --git a/resources/docs/docs/assets/architectural_diagram_project.png b/resources/docs/docs/assets/architectural_diagram_project.png new file mode 100644 index 0000000000000000000000000000000000000000..f5d624a442d37fb763e9db6f11d34dcadceec7b6 Binary files /dev/null and b/resources/docs/docs/assets/architectural_diagram_project.png differ diff --git a/resources/docs/docs/assets/architectural_diagram_service.png b/resources/docs/docs/assets/architectural_diagram_service.png new file mode 100644 index 0000000000000000000000000000000000000000..d14c6123fe17099e7b8839ed3480a85322729e11 Binary files /dev/null and b/resources/docs/docs/assets/architectural_diagram_service.png differ diff --git a/resources/docs/docs/assets/db_schema.png b/resources/docs/docs/assets/db_schema.png new file mode 100644 index 0000000000000000000000000000000000000000..761a47deec7281d0560f3e686989d8c0a9482110 Binary files /dev/null and b/resources/docs/docs/assets/db_schema.png differ diff --git a/resources/docs/docs/assets/img.png b/resources/docs/docs/assets/img.png new file mode 100644 index 0000000000000000000000000000000000000000..09e9e1ad8b43088b7756754899278081bc4bf446 Binary files /dev/null and b/resources/docs/docs/assets/img.png differ diff --git a/resources/docs/docs/assets/logo-takima.jpg b/resources/docs/docs/assets/logo-takima.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27efddb9a5829f7afdef9c4904999a347db18256 Binary files /dev/null and b/resources/docs/docs/assets/logo-takima.jpg differ diff --git a/resources/docs/docs/assets/logo.png b/resources/docs/docs/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d382958b699f4e6cd4dbf4db3b8e8894ed1befde Binary files /dev/null and b/resources/docs/docs/assets/logo.png differ diff --git a/resources/docs/docs/chapters/tp1/about.md b/resources/docs/docs/chapters/tp1/about.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/resources/docs/docs/chapters/tp1/api_collection.md b/resources/docs/docs/chapters/tp1/api_collection.md new file mode 100644 index 0000000000000000000000000000000000000000..9fba3ea006f7bd18dcf4c6eb176295ac6689bfea --- /dev/null +++ b/resources/docs/docs/chapters/tp1/api_collection.md @@ -0,0 +1,203 @@ +# **Collection** + +## Modification de notre classe `Travel` + +Après avoir élaboré les classes pour structurer nos objets, nous allons maintenant explorer les structures de +données avancées, notamment les collections. Les collections en Java constituent des outils puissants pour gérer et +manipuler des ensembles de données de manière dynamique. + +!!! abstract "API Collection" + + Java met à disposition l'API Collection qui propose un ensemble d'interfaces et d'implémentations permettant de + stocker des ensembles d'objets. + + On y trouve 3 interfaces principales (avec les implémentations principales correspondantes) : + + * List : ArrayList et LinkedList + * Set : HashSet, LinkedHashSet et TreeSet + * Queue : LinkedList (implémenté par List et Queue), ArrayDeque et PriorityQueue + + Pour plus d'informations, vous pouvez consulter la [documentation](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collection.html){:target="_blank"} officielle de Java sur les collections. + +### Ajout d'une collection de participants + +Actuellement, notre classe `Travel` ne contient pas de collection de participants, allons en rajouter une. +Pour notre besoin, on veut qu'un utilisateur ne puisse pas s'inscrire deux fois (un ensemble sans répétitions, il faut donc mettre une collection qui **interdise les doublons**). + +!!! note "À vous de jouer !" + + - Essayez d'ajouter un nouvel attribut participants dans notre classe `Travel`. + + ??? tip "Indice" + Ici, participants est un Set d'utilisateurs `Set<User>`, mais pour l'implémentation, nous utilisons + `HashSet<User>`. Cela offre une certaine flexibilité, car si vous souhaitez changer l'implémentation + sous-jacente à l'avenir, par exemple, passer de HashSet à LinkedHashSet, vous pouvez le faire facilement sans modifier le reste du code qui utilise l'attribut participants. + +Lorsqu'**une classe utilise une autre classe comme type d'attribut**, cela est appelé la **composition**. Cette +approche permet à notre classe `Travel` de manipuler et de gérer ses utilisateurs `User` de manière indépendante. + +!!! quote "" + En programmation orientée objet, **privilégier la composition plutôt que l'héritage** permet une meilleure modularité, + une réutilisabilité accrue du code et une gestion plus souple des dépendances entre les classes. + +Nous sommes confrontés à un problème : avec une disponibilité fixe, en cas de désistement d'un voyageur déjà +enregistré, nous perdons de la valeur, alors que nous pourrions bien instaurer une vérification et éventuellement +inscrire le surplus d'utilisateurs dans une **file d'attente**. + +!!! note "À vous de jouer !" + + À la manière de l'ajout de la liste de participants au voyage, ajoutez un attribut à la classe `Travel` qui + représenterait une collection de participants dans une file d'attente en choisissant la collection la plus + appropriée. + + ??? question "Quelle serait la collection à choisir ? Pourquoi ?" + + Ici, on pourrait très bien imaginer l'utilisation d'une Queue. + Comme nous l'avons vu en point tech, une FIFO (first in, first out) permettrait de modéliser cette file + d'attente et les méthodes proposées par l'API facilitent la modélisation de ce cas d'utilisation. + + En effet, il suffit d'appeler la méthode `poll()` sur la collection pour récupérer l'utilisateur suivant + dans la file d'attente et de l'ajouter dans la liste des participants. + +!!! bug "Bug possible sans `equals` et `hashCode`" + + Comme vu dans la partie POO, si vous ne surchargez pas vos méthodes `equals` et `hashCode`, + vous pouvez introduire des bugs. + + Ajoutez ces deux user identiques dans votre `Set` de `participants` : + ```java + User sameUser1 = new User(0, "Same", "User", "sameuser@gmail.com"); + User sameUser2 = new User(0, "Same", "User", "sameuser@gmail.com"); + ``` + Commentez temporairement les méthodes `hashCode` et `equals`de `User` puis loggez les user de `participants`, que remarquez-vous ? + + +Désormais, nous sommes en mesure de stocker les différents participants à notre `Travel`. Il serait intéressant de pouvoir agir sur cette collection. +Nous vous proposons d'ajouter deux méthodes dans la classe `Main` pour pouvoir inscrire et désinscrire un `User`. + +!!! note "À vous de jouer !" + + Voici leur signature respective : + + ```java + public boolean subscribe(User user, Travel travel) + ``` + + ```java + public void unsubscribe(User user, Travel travel) + ``` + + Les points à prendre en compte lorsque vous implémenterez ces deux méthodes : + + ??? example "_Pour l'inscription :_" + + - retourner true si l'inscription a réussi (donc dans le cas ou l'utilisateur est bien ajouté à la liste des + participants). + - retourner false sinon + + L'inscription ne doit réussir que si l'utilisateur n'est pas déjà inscrit dans le voyage ! + + Cette méthode aura la responsabilité de récupérer le voyage en question, vérifier la disponibilité de ce dernier et inscrire l'utilisateur dans la bonne file (participants ou file d'attente). + + ??? example "_Pour la désinscription :_" + + Nous ne mettrons pas de retour sur la méthode. + + - La première garantie est qu'après un appel à cette méthode, l'utilisateur n'est pas dans la liste des + participants : il est donc supprimé s'il est trouvé. + - La seconde garantie est que si un utilisateur est effectivement désinscrit du voyage et qu'il y a + un/plusieurs utilisateur(s) en file d'attente, le premier utilisateur de cette file passe en liste + d'inscription effective. + +Super ! Nous pouvons maintenant inscrire autant d'utilisateurs que nous voulons pour notre voyage ! Par contre, remarquez que ça commence à faire beaucoup de logique dans `Main.java` et le code manque de structure. Pour +remédier à cela, il serait temps de déplacer le code dans des classes dédiées, appelées **services**. + +## Services + +L'objectif de cette partie est de réaliser la couche service de notre diagramme. + + + +La couche service va contenir la logique métier de notre application. Elle utilise les DAOs pour agir sur la base de données et transmettra les données à la couche présentation. + +### Création du service `Travel` + +!!! note "À vous de jouer !" + + - Créez un package `service` qui comportera l'ensemble de nos services. + - Créez la classe `TravelService` et déplacez les fonctions `subscribe` et `unsubscribe`. + + Maintenant que nous avons utilisé les collections, modifiez le tableau de discounts pour utiliser une List (**Les collections sont plus aisées à manipuler que les tableaux**). + + - Créez une fonction `applyDiscounts` dans le service en reprenant le code déjà écrit dans le main. + + +Nous sommes confrontés à une lacune : la simple indication "true" ou "false" lors de l'inscription ne clarifie pas +suffisamment la situation. Cela peut poser un problème, que ce soit pour nous (risque d'oublier l'implémentation) ou +pour toute personne extérieure qui n'est pas familière avec le code. Ainsi, pour enlever cette ambiguïté, nous opterons +pour le renvoi d'une exception afin de mieux communiquer. + +!!! note "À vous de jouer !" + + - Modifier `subscribe` pour qu'elle ne renvoie rien et qu'elle propage une Exception. + + ??? tip "Indice" + + ```java + ... + public void subscribe(...) throws Exception { + if (// l'utilisateur est inscrit) { + throw new Exception("L'utilisateur est déjà inscrit ou en file d'attente pour ce voyage."); + } + ... + } + ``` + +Pour utiliser le service, on doit faire comme suit : + +```java +... +// Instanciation du TravelService +TravelService travelService = new TravelService(); + +// Utilisation des fonctionnalités du service +travelService.subscribe(user, travel); +... +``` + +Cependant, en raison de la possibilité d'une exception, il est nécessaire d'encadrer cet appel avec un bloc +try/catch pour la capturer et la gérer : +```java +try { + travelService.subscribe(user, travel); +} catch (Exception e) { + System.out.println(e.getMessage()); +} +``` + +### Essayez votre code dans le Main + +Ça y est ! Nos utilisateurs peuvent désormais s'inscrire aux différents voyages. En fonction +de la disponibilité de chaque voyage, l'utilisateur est possiblement inscrit en file d'attente. + +!!! note "À vous de jouer !" + + - Inscrivez les utilisateurs à un voyage en passant par la méthode `subscribe` du service `Travel`. + - Variez la disponibilité d'un voyage et vérifiez que la file d'attente est bien remplie dans le cas où un voyage serait déjà plein. + - Désinscrivez un utilisateur et vérifiez que le premier utilisateur dans la file d'attente passe bien en participant. + +## **Récapitulatif** + +Bravo ! Vous avez ajouté avec succès la fonctionnalité d'inscription à un voyage pour un utilisateur ! Si vous avez tout compris, **vous pouvez passer au chapitre suivant** ! + +!!! success + ``` + . + └── model + │ └── Travel.java + └── service + │ └── TravelService.java + └── Main.java + ``` + +!!! warning "N'oubliez pas de `commit` votre travail !" diff --git a/resources/docs/docs/chapters/tp1/api_date.md b/resources/docs/docs/chapters/tp1/api_date.md new file mode 100644 index 0000000000000000000000000000000000000000..f244dcebc43ef3201de259eb2b29e040eb19fc37 --- /dev/null +++ b/resources/docs/docs/chapters/tp1/api_date.md @@ -0,0 +1,172 @@ +# **API Date** + +## Introduction aux dates et heures + +Maintenant que nous avons introduit les aéroports et leurs fuseaux horaires, il est temps de nous concentrer sur un +aspect crucial des voyages : les dates et les heures. Nous allons utiliser l'API `java.time` pour cela. + +!!! info "API `java.time`" + + L'API `java.time` comporte 5 implémentations importantes : + + * `LocalDate`: Représente une date sans heure ni fuseau horaire (par exemple, 2023-12-15). + * `LocalTime`: Représente une heure sans date ni fuseau horaire (par exemple, 10:15:30). + * `LocalDateTime`: Combine LocalDate et LocalTime, représentant une date et une heure sans fuseau horaire. + * `ZonedDateTime`: Comme LocalDateTime, mais avec la prise en charge des fuseaux horaires. + * `Instant`: Représente un moment précis sur la timeline UTC. + * `Period`, `Duration` : Représente des intervalles dans le temps. + +Exemples d'utilisation : + +```java +LocalTime uneHeure = LocalTime.of(14, 30); // 14h30 +LocalDateTime dateEtHeure = LocalDateTime.of(2023, Month.JULY, 15, 14, 30); // 15 juillet 2023, 14h30 +ZonedDateTime dateHeureZone = ZonedDateTime.of(2023, 7, 15, 14, 30, 0, 0, ZoneId.of("Europe/Paris")); // 15 juillet 2023, 14h30, heure de Paris +Instant instant = Instant.now(); // Capture le moment courant en UTC +Period period = Period.between(dateEtHeure.toLocalDate(), dateHeureZone.toLocalDate()); +Duration duration = Duration.between(instant, instant); +``` + +!!! info "`Period` vs `Duration`" + + `Period` : utilise les unités année, mois et jour pour représenter une période de temps (prend des `LocalDate` en paramètre). + + `Duration` : représente un intervalle de temps en secondes ou en nanosecondes et convient mieux pour traiter les + cas qui requièrent plus de précision (prend des `Instant` en paramètre). + +## Intégration des dates du voyage + +!!! note "À vous de jouer" + + * Ajoutez un attribut `departure` et `arrival` dans la classe `Travel` pour gérer les dates de départ et d'arrivée ainsi que les heures. Vous utiliserez un `Instant`. + * Ajoutez dans le constructeur une levée d'exception si la date de départ est postérieure à la date d'arrivée. Vous pourrez utiliser une `IllegalArgumentException` avec un message indiquant que la date de départ est après la date d'arrivée. + +!!! question "Pourquoi un `Instant` plutôt qu'un `ZonedDateTime` ?" + + On privilégie l'utilisation d'`Instant` plutôt que ZonedDateTime car les dates/heures dépendent des fuseaux horaires, qui varient selon les régions du monde. + + Ainsi, il est plus judicieux de convertir l'`Instant` en date/heure dans le frontend, permettant d'afficher la + date et l'heure selon le fuseau horaire de chaque utilisateur. (Dans notre cas, nous ferons la conversion dans notre + vue, au niveau du **DTO**). + +## DTO de sortie + +Les DTO (Data Transfer Object) représentent notre modèle tel qu'il est exposé à l'extérieur. Ils permettent de contrôler les objets entrants et sortants de notre application, on parle donc de `request DTO` et de `response DTO`. Par souci de simplicité, on se focalisera uniquement sur les DTO de sortie dans notre application. + +Les DTO de sortie représentent ce qui est exposé à l'extérieur (par exemple au frontend). On n'envoie pas directement le modèle, car il pourrait contenir des informations sensibles ou inutiles. Dans une application web complète, on ajouterait des controllers à nos DTO pour qu'ils puissent être transmis à notre frontend via le protocole HTTP. + + + +En fait, jusqu'à présent, nous vous avions demandé de créer des classes, mais depuis Java 17, il est possible de concevoir +des classes immuables avec moins de code boilerplate avec les **records**. + +!!! abstract "Record" + Avec les records, il **n'est plus nécessaire** de créer les méthodes _equals()_, _hashCode()_ et _toString()_, + ainsi que les _constructeurs_ et les _accesseurs_. + + !!! example "Exemple" + Notre classe `User` aurait pu être définie comme suit : + ```java + public record User(long id, + String firstName, + String lastName, + String contact) {} + ``` + +C'est l'occasion d'utiliser les **records** pour nos DTO. + +### Création du DTO de sortie `Travel` + +!!! note "À vous de jouer" + + - Créez un fichier `TravelResponseDto` qui correspondra à une représentation du modèle `Travel` contenant seulement + les informations à exposer (pour les dates en renverra des `ZonedDateTime`). + +### Création d'un mapper pour convertir les modèles en DTO + +Maintenant que nous avons nos DTO, il faut pouvoir convertir nos classes dans leur DTO correspondants. +Nous allons créer des méthodes permettant de faire cela. + +Une première stratégie serait de créer des classes avec des méthodes de mapping _**public**_ et _**static**_ à +l'intérieur. + +??? example "Exemple" + + ```java + public class Main { + public static void main(String[] args) { + // Utilisation de la méthode statique sans instance de la classe + int result = MathUtils.add(5, 3); + } + + public static class MathUtils { + public static int add(int a, int b) { + return a + b; + } + } + } + ``` + +!!! note "À vous de jouer" + + - Créez une classe `TravelDtoMapper` avec une méthode `fromTravel` pour convertir le modèle `Travel` dans son DTO + de sortie. + - La méthode `fromTravel` devra gérer la conversion d'Instant en date / heure exprimée dans le fuseau hoaire + de l'aéroport de départ et d'arrivée. Utilisez l'`AirportManager` créé dans la partie précédente pour lier + l'aéroport à son fuseau horaire. + +## <span style="color:orange"> **Optionnel** </span> + +### Calcul de durée et gestion des retards + +!!! note "À vous de jouer" + + Faites les modifications suivantes dans la classe `TravelService` : + + * Créez une méthode `computeTravelDuration` qui calcule la durée totale du voyage ( [méthode de la librairie`Duration`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/Duration.html){:target="_blank"} ). + + * Créez une méthode qui permet de modifier les dates des vols en cas de retard. La méthode prendra en paramètre un + objet `Travel` et un `delay` qui correspondra à une durée de retard, par exemple 2 heures, 2 jours ou encore 1 semaine ( [méthode de la librairie`Instant`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/Instant.html){:target="_blank"} ). + +### Essayez votre code dans le Main + +!!! note "À vous de jouer" + + Faites les modification suivantes dans le `Main` : + + Jérôme s'inscrit à un voyage entre Paris et Tokyo du 26/04/2023, l'avion décolle à 12h10 et arrive le 27/04/2023 à + 06h36. + + * Vérifiez que la méthode `computeTravelDuration` retourne bien 11 heures et 26 minutes. + * Ajoutez un retard de 5h à la date de départ et assurez-vous que cela fonctionne en affichant la date de départ dans le bon fuseau horaire. + + ??? tip "Date formatter" + + ```java + // Création de l'objet LocalDateTime pour la date et l'heure spécifiées + LocalDateTime dateTime = LocalDateTime.of(2024, Month.DECEMBER, 21, 12, 30); + + // Conversion en Instant + Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); + + // Convertir Instant en LocalDateTime + LocalDateTime newLocalDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + ``` + +## **Récapitulatif** + +Félicitations ! Vous avez maintenant intégré la gestion des dates et des heures dans votre application. Vous êtes dorénavant prêt à gérer des voyages dans différents fuseaux horaires et à effectuer des opérations complexes sur les dates. + +!!! check + ``` + . + └── model + │ ├── Travel.java + │ └── responsedto + │ └── TravelResponseDto.java + ├── mapper + │ └── TravelDtoMapper.java + └── Main.java + ``` + +!!! warning "N'oubliez pas de `commit` votre travail !" diff --git a/resources/docs/docs/chapters/tp1/api_map.md b/resources/docs/docs/chapters/tp1/api_map.md new file mode 100644 index 0000000000000000000000000000000000000000..784cac5af710a83a01b2bfd9e217e5e67072b127 --- /dev/null +++ b/resources/docs/docs/chapters/tp1/api_map.md @@ -0,0 +1,151 @@ +# **API Map** + +## Rappels sur les `Map` + +Après avoir découvert et manipulé l'API Collection, abordons une autre interface importante : **Map**, qui d'ailleurs ne +fait pas partie de l'API Collection, mais permet de gérer des collections de données. Dans la partie suivante, +nous aurons besoin d'afficher les heures de décollage de l'avion dans le fuseau horaire de l'aéroport. Pour préparer +le terrain, nous allons faire correspondre chaque aéroport avec son fuseau horaire associé. Nous utiliserons une collection de type clé / valeur : les `Map`. + +!!! info "Les implémentations de Map" + + En Java, l'interface Map a plusieurs implémentations, chacune ayant des caractéristiques et des utilisations spécifiques. Voici les plus courantes : + + * `HashMap` : Accès rapide, ordre non garanti. + * `LinkedHashMap` : Accès rapide, ordre d'insertion conservé. + * `TreeMap` : Clés ordonnées selon un critère de tri. + +**Exemple d'utilisation** + +```java +Map<Integer, String> ages = new HashMap<>(); +Map<Long, Double> linkedAges = new LinkedHashMap<>(); +Map<String, Float> sortedAges = new TreeMap<>(); +``` + +??? question "Pourquoi utilise-t-on les classes wrapper ?" + Il est possible d'utiliser des types primitifs (comme int, float, char), mais ils sont automatiquement encapsulés dans + leurs classes wrapper correspondantes (Integer, Float, Character, etc.) grâce à l'autoboxing lorsqu'ils sont + utilisés dans un Map. On préfère donc directement utiliser les wrappers. + +On ajoute un élément(paire clé/valeur) à une `Map` avec la méthode `put`: + +```java +ages.put("Alice", 30); +``` + +On accède à la valeur d'une clé avec `get` : + +```java +Integer aliceAge = ages.get("Alice"); +``` + +On supprime une clé/valeur avec `remove` (notez que la valeur est renvoyée en retour) : + +```java +Integer aliceAge = ages.remove("Alice"); +``` + +## Création de l'enum `Airport` + +Pour gérer les différents fuseaux horaires, nous commencerons par créer une enum appelée `Airport` qui representera l'ensemble des aéroports disponibles pour nos voyages. + +!!! note "À vous de jouer" + Commencez par déclarer l'énumération `Airport` avec les valeurs suivantes : + + - `PARIS_CHARLES_DE_GAULLE("CDG")` + - `PARIS_ORLY("ORY")` + - `NEW_YORK_JFK("JFK")` + - `NEW_YORK_NEWARK("EWR")` + - `NEW_YORK_LAGUARDIA("LGA")` + - `TOKYO_NARITA("NRT")` + - `TOKYO_HANEDA("HND")` + + Chaque constante aura un attribut `acronym` pour stocker son acronyme. N'oubliez pas d'ajouter un constructeur pour initialiser cet attribut, ainsi qu'une méthode `getAcronym()` pour le récupérer. + + ??? example "Exemple" + Cet exemple crée une énumération `Color` avec trois constantes représentant des couleurs, chacune avec un nom français. + Chaque constante possède une méthode pour obtenir son nom français et la méthode `toString()` est redéfinie pour retourner ce nom lors de l'affichage. + + ```java + public enum Color { + RED("Rouge"), + GREEN("Vert"), + BLUE("Bleu"); + + private final String frenchName; + + Color(String frenchName) { + this.frenchName = frenchName; + } + + public String getFrenchName() { + return frenchName; + } + + @Override + public String toString() { + return frenchName; + } + } + ``` + + +Maintenant, pour faire correspondre chaque aéroport à son fuseau horaire, nous allons créer une classe `AirportManager`. Cette classe utilisera une `Map` pour stocker le fuseau horaire lié à l'aéroport. + +!!! note "À vous de jouer" + + Créez une classe `AirportManager` puis déclarez une Map statique `timeZone` qui associera chaque aéroport à son fuseau horaire. + + On pourra utiliser les coefficients suivants : + + * `PARIS_CHARLES_DE_GAULLE` : `Europe/Paris` + * `PARIS_ORLY` : `Europe/Paris` + * `NEW_YORK_JFK` : `America/New_York` + * `NEW_YORK_NEWARK` : `America/New_York` + * `NEW_YORK_LAGUARDIA` : `America/New_York` + * `TOKYO_NARITA` : `Asia/Tokyo` + * `TOKYO_HANEDA` : `Asia/Tokyo` + + ??? tip "Indice" + ```java + private static final Map<Color, String> colorToHexMap = new HashMap<>(); + + static { + colorToHexMap.put(Color.RED, "#FF0000"); + colorToHexMap.put(Color.GREEN, "#00FF00"); + colorToHexMap.put(Color.BLUE, "#0000FF"); + } + ``` + +!!! question "Pourquoi utiliser un attribut et une méthode statique ?" + + L'usage d'éléments statiques dans la classe `AirportManager` permet d'accéder aux fuseaux horaires des + aéroports sans créer une instance de la classe, ce qui est pratique car ces informations sont communes et ne + changent pas selon les instances. Cela simplifie l'accès aux données et économise la mémoire. + + ```java + AirportManager.AirportZoneMap.get(Airport.PARIS_CHARLES_DE_GAULLE); + ``` + +- Pensez à changer le type des attributs `departure` et `arrival` pour les définir comme étant de type `Airport`. + +!!! quote "" + Une autre manière de faire pour stocker les fuseaux horaires aurait été de les stocker directement dans l'enum, + dans un attribut d'enum (comme cela a été fait pour les acronymes). + +## **Récapitulatif** + +Dans cette partie, nous avons examiné comment utiliser l'énumération `Airport` et créer notre Map de fuseaux horaires. +Cette dernière sera utilisée ultérieurement pour afficher l'heure de décollage des avions pour chaque voyage dans leur +fuseau horaire spécifique. + +!!! check + ``` + . + └── utils + │ └── AirportManager.java + └── Main.java` + ``` + +!!! warning "N'oubliez pas de `Commit` votre travail !" diff --git a/resources/docs/docs/chapters/tp1/api_stream.md b/resources/docs/docs/chapters/tp1/api_stream.md new file mode 100644 index 0000000000000000000000000000000000000000..92564afd53be901633224b935c2a01c709628f10 --- /dev/null +++ b/resources/docs/docs/chapters/tp1/api_stream.md @@ -0,0 +1,180 @@ +# **API Stream** + +### Programmation fonctionnelle + +Comme vous le savez, Java est un langage de programmation multi-paradigme. Vous avez pu traiter la +programmation orientée objet, la programmation procédurale et nous allons désormais découvrir la +**programmation fonctionnelle**. + +Cette approche a été renforcée dans _Java 8_ avec l'introduction d'une nouvelle amélioration syntaxique puissante sous +la forme d'expressions **lambda**. + +!!! abstract "Lambda" + + Une expression lambda est une fonction anonyme que nous pouvons traiter de la même manière que n'importe quel + autre objet. Cela signifie qu'elle peut être utilisée de manière flexible, telle que **la passer en tant + qu'argument** de méthode ou encore **en valeur de retour**. + +L'interface fonctionnelle est un exemple qui permet de bien comprendre le principe de `lambda`. +Voici un exemple de **BiFunction** : + +!!! quote "" + ```java linenums="1" + public class Main { + public static void main(String[] args) { + // Les lambda sont ce que vous observez avec la notation fléchée + // Addition + // L'instance du lambda est stockée dans une variable + BiFunction<Double, Double, Double> addition = (a, b) -> { + return a + b; + }; + System.out.println("Addition : " + addition.apply(5.0, 3.0)); + + // Multiplication + BiFunction<Double, Double, Double> multiplication = (a, b) -> a * b; + System.out.println("Multiplication : " + multiplication.apply(4.0, 2.5)); + } + } + ``` + +!!! info "Voici quelques points à savoir sur les lambda :" + + - Il n'est pas obligatoire de mettre les parenthèses s'il n'y a qu'un seul paramètre. + - Vous pouvez omettre le bloc de code après la flèche s'il n'y a qu'une seule instruction `return` (_cf. ligne 12_). + +Le plus souvent, les lambda iront de pair avec un autre concept Java apparu dans la même version : **les +streams**. Les streams sont une extension naturelle des lambdas, offrant une approche de code plus lisible et +maintenable pour manipuler des collections de données. + +### Streams + +!!! abstract "Qu'est-ce qu'un Stream ?" + + Un Stream est un objet qui encapsule un flux de données sur lequel on peut effectuer des opérations. + Son utilisation est très répandue, voire privilégiée pour beaucoup de cas d'utilisation. + + Il existe deux types d'opérations que l'on peut effectuer sur des streams : + + - les opérations **terminales** : on récupère le résultat à l'issue de l'opération + - les opérations **intermédiaires** : opérations intermédiaires qui modifient le flux + + Concrètement, là où l'on aurait utilisé une boucle `for` sur une collection pour modifier ses éléments, nous allons désormais préférer utiliser des streams et appliquer des fonctions de modification sur les différents éléments de la collection. + +Toutes les collections qui étendent l'`API Collection` proposent une méthode `stream()` qui permet de récupérer un objet Stream sur lequel appliquer des opérations. +??? info "Opérations sur les Streams" + + **Opérations terminales** : + + - reduce(identity, accumulator, combiner) : combine les éléments d'une collection en utilisant une valeur initiale, une opération d'accumulation et une opération de combinaison + - forEach(element) : itère sur la collection avec `element` comme valeur courante + - collect : permet de transformer le stream en une valeur (List, Double, ...) + - min/max(comparator) : renvoie un Optional<T> qui contient la valeur min/max de la collection trouvée en appliquant `comparator` à tous les éléments deux à deux + - anyMatch(predicate) : évalue le fait qu'au moins un élément de la collection valide le `predicate` + - findFirst : renvoie un Optional<T> du premier élément du Stream + - findAny : renvoie un Optional<T> d'un élément du Stream + + **Opérations intermédiaires** : + + - filter(predicate) : renvoie une sous collection des éléments qui valident le `predicate` + - map(mapping) : renvoie une collection où l'on a appliqué `mapping` à tous les éléments + - distinct : renvoie un Stream qui ne contient pas de doublons + - sorted : trie les éléments du Stream + - limit(n) : limite le Stream à `n` éléments + +!!! example "Exemple" + + Étant donné la collection suivante : + ```java + List<String> chars = List.of("20", "35", "", "14", " ", "51"); + ``` + On veut écrire un stream qui renvoie la somme des tailles des chaînes de caractères présentes dans la liste. + + ```java + chars.stream() + .filter(c -> !c.isBlank()) + .mapToInt(c -> c.length()) + .sum() + ``` + Ce stream enlève les valeurs vides et calcule la somme des tailles des chaînes de caractères présentes dans la liste. + +## Requêtage de notre application + +Maintenant que vous avez appréhendé les Streams, il est l'heure d'ajouter des fonctionnalités de requêtage à notre application. + +Nous vous proposons un jeu de données à ajouter avant de commencer cette partie. + +!!! edit "À vous de jouer" + + Il faudra, pour chaque fonctionnalité, coder une méthode dans le service associé à la fonctionnalité. Quasiment toutes ces méthodes prendront un paramatre `List<Travel>`. + + **Voici les différentes fonctionnalités :** + + - Notre agence doit pouvoir exposer la liste de tous les voyages disponibles actuellement (donc pour lesquels il reste encore des places) + - Dans un second temps, nous voulons pouvoir paramétrer cette liste de voyages disponibles par une localisation. + Exemple : tous les voyages disponibles à destination de Tokyo + - Nos utilisateurs aimeraient faire une recherche détaillée sur les voyages que propose l'agence dans une certaine + fourchette de prix. + - Nos utilisateurs aimeraient avoir le voyage le plus proche de la date actuelle. + +## <span style="color:orange"> **Optionnel** </span> + +Si vous avez bien progressé et qu'il vous reste du temps, continuez à implémenter ces fonctionnalités : + +- L'agence doit pouvoir lister la liste des n premiers voyages triés par ordre croissant de prix. +- Elle devra pouvoir également lister tous les voyages disponibles avec une promotion en cours. +- Nous voulons que l'agence puisse lister tous les voyages auxquels le `User` Jean-Michel est inscrit (Il faudrait + pouvoir filtrer sur les user qui [match](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/Stream.html#anyMatch(java.util.function.Predicate)){:target="_blank"} la condition). + +- Il y a un nouveau CEO dans l'entreprise. Il veut pouvoir évaluer la rentabilité de notre agence et il souhaiterait + obtenir la moyenne du prix des voyages proposés par l'agence. + +## Essayez votre code dans le Main +!!! note "À vous de jouer" + + Vous pouvez maintenant tester votre service dans le main. + + === "TravelService.java" + ```java + ... + List<Travel> findAvailableTravels (List<Travel> travels){ + ... + } + ... + ``` + === "Main.java" + ```java + ... + // Ma liste de voyages + List<Travel> travels = new LinkedList(); + travels.add(new Travel(0, "To New York", Airport.PARIS_CHARLES_DE_GAULLE, Airport.NEW_YORK_JFK, + Instant.now().plus(Duration.ofDays(2)), + Instant.now().plus(Duration.ofDays(15)), "NewYork", 6, 3500)); + travels.add(new Travel(0, "Paris is next", Airport.TOKYO_HANEDA, Airport.PARIS_ORLY, + Instant.now().minus(Duration.ofDays(2)), + Instant.now().plus(Duration.ofDays(5)),"Paris", 10, 2900)); + + // instancier mon service + TravelService travelService = new TravelService(); + + // récupérer que les voyages disponible + List<Travel> availableTravels = findAvailableTravels(travels); + ... + ``` + +## **Récapitulatif** + +Excellent, nous avons clôturé la partie sur les streams et vous avez pu créer vos programmes de recherche. + +!!! check + ``` + . + └── service + │ └── TravelService.java + └── Main.java` + ``` + +!!! warning "N'oubliez pas de `Commit` votre travail !" + +# **Conclusion** + +On est arrivé à la fin de cette première partie de la formation. À demain pour de nouvelles aventures ! diff --git a/resources/docs/docs/chapters/tp1/index.md b/resources/docs/docs/chapters/tp1/index.md new file mode 100644 index 0000000000000000000000000000000000000000..ae3c1c993ecac7ab0ce0cb85624a219b4a67a44b --- /dev/null +++ b/resources/docs/docs/chapters/tp1/index.md @@ -0,0 +1,24 @@ +# Introduction + +Bienvenue dans le **Jour 1** de votre formation Java. + +Lors de ce premier jour de formation, nous vous invitons à plonger dans l'univers de **Java**, à créer vos premières classes et à explorer plus en détail les différentes API de ce langage. + +## **Au programme** + +- Déclaration d'une classe, ses attributs et ses méthodes. +- Utilisation des **records**. +- Les **énumérations** (enum). +- **Héritage** et **interfaces**. +- Les **collections** de données. +- Manipulation de **Date**. +- **Programmation fonctionnelle**. + +Afin de couvrir tous ces concepts, nous allons construire ensemble une application de gestion de voyages tout au long de cette formation. + +Nous vous proposons de découper le développement de cette application en deux parties : + +- **Une première partie** portera sur la conception des différentes briques de notre application : nous aurons l'occasion d'aborder les Collections ou encore les différentes API de Date existantes. +- **Une seconde partie** portera sur le requêtage de notre application : cela nous permettra de mettre en œuvre les Streams ainsi que les Lambda. + +Après avoir construit une telle application, les Collections, les Dates ou les Streams n'auront plus aucun secret pour vous ! diff --git a/resources/docs/docs/chapters/tp1/poo.md b/resources/docs/docs/chapters/tp1/poo.md new file mode 100644 index 0000000000000000000000000000000000000000..43860ec33ef81051083f3fb6317bd6f27bad94f5 --- /dev/null +++ b/resources/docs/docs/chapters/tp1/poo.md @@ -0,0 +1,505 @@ +# **Programmation Orientée Objet** + +Java repose solidement sur la POO qui offre une approche de programmation centrée sur les concepts de classes et +d'objets. Les classes définissent des modèles de données et de comportements, tandis que les objets sont des +instances spécifiques de ces classes. + +Voyons comment nous allons réaliser les modèles nécessaires qui vont structurer et contenir les données utilisées +dans le projet. + + + +## Création des classes + +Avant de plonger dans la création de nos classes, prenons un moment pour comprendre ce qu'est une classe et ses +éléments essentiels. + +??? abstract "Classe Java" + + Une classe contient : + + - **Attributs** : Ce sont les variables qui définissent les caractéristiques de l'objet. + - **Méthodes** : Ce sont les fonctions qui définissent le comportement de la classe. + - **Constructeur** : C'est une méthode spéciale portant le même nom que la classe, utilisée pour initialiser un objet + dès sa création. Si la classe n'a pas d'attribut, par défaut, la classe aura un constructeur sans paramètre. + + Les classes en Java doivent respecter le principe de l'**encapsulation**. Cela se réalise en définissant les + attributs comme étant _**private**_ et en fournissant des méthodes _**public**_ pour y accéder qui sont : + + - **Setters et Getters** : Ce sont des méthodes permettant de contrôler l'accès aux attributs. Les setters modifient + les valeurs, tandis que les getters les récupèrent. + + ??? example "Exemple" + + ```java + public class MyClass { + private int myNumber; + + public MyClass(int myNumber) { // Le constructeur + this.myNumber = myNumber; + } + + public int getMyNumber() { // Getter + return this.myNumber; + } + + public void setMyNumber(int myNumber) { // Setter + this.myNumber = myNumber; // Ici pour pouvoir faire la distinction entre + // myNumber en paramètre et celui déclaré au + // niveau de la classe, il suffit de rajouter + // this.nomDeLAttribut pour indiquer celui de + // la classe. Celui sans 'this.' réfère donc + // la variable passée en paramètre. + } + } + ``` + Dans l'exemple ci-dessus, nous avons utilisé `private` comme modificateur d'accès. + Il existe 4 modificateurs d'accès, en voici la liste avec leur _scope_ : + + | Modificateur d'accès | Classe | Package | Sous-classe | Tout le monde | + |:----------------------|:------------|:-------------|:---------------|:---------------| + | `public ` | Oui | Oui | Oui | Oui | + | `protected` | Oui | Oui | Oui | Non | + | Sans modificateur | Oui | Oui | Non | Non | + | `private` | Oui | Non | Non | Non | + + + +### Classe `Travel` + +On peut maintenant commencer la création de la classe `Travel` qui représentera un voyage proposé par l'agence. + +!!! edit "**À vous de jouer !**" + + - Créez un package `model`, dans le package `agencymanagement`, qui comportera l'ensemble de nos modèles. + - Créez la classe `Travel` en suivant la même logique que l'exemple d'une classe Java. + - Pour cette classe, nous aurons besoin d'un id, d'un nom, d'un aéroport de départ, d'un aéroport d'arrivée, d'une + destination, d'un nombre maximal de participants ainsi que d'un prix. + + ??? abstract "**Type de données primitifs**" + + | Type de données primitifs | Description | + |:--------------------------|:----------------------------| + | `byte` | Nombre entier sur 8 bits | + | `short` | Nombre entier sur 16 bits | + | `int` | Nombre entier sur 32 bits | + | `long ` | Nombre entier sur 64 bits | + | `float` | Nombre décimal sur 32 bits | + | `double` | Nombre décimal sur 64 bits | + | `boolean` | Valeur `true` ou `false` | + | `char` | Un caractère simple | + + Il existe un objet très utilisé en Java mais qui n'est pas un type primitif : l'objet `String`. Il est + utilisé pour contenir les chaînes de caractères. + +!!! warning "Attention" + + - Prenez soin de choisir les types appropriés et d'ajouter le constructeur, les getters et les setters. + Votre IDE peut vous les générer. + +### Classe `User` + +À présent, pour représenter les profils de nos utilisateurs, nous allons créer une classe `User`. + +Nous aimerions pouvoir construire notre utilisateur comme suit : + +```java +User tom = new User(0, "Tom", "Dupont", "tom.dupont@gmail.com"); +``` + +!!! note "À vous de jouer !" + + Essayez de construire ce modèle `User` en identifiant les informations qu'il doit contenir. + +#### Les méthodes _toString, equals et hashCode_ + +En plus des éléments courants comme les constructeurs, les getters et les setters, il est fréquent de redéfinir les +méthodes **toString**, **equals** et **hashCode** dans une classe. + +Ces méthodes sont héritées de la classe **Object** en Java. + +??? abstract "Concernant les méthodes toString, equals et hashCode" + + Par défaut, toutes les classes créées en Java héritent de la classe `Object`. Parmi les méthodes héritées + de `Object`, on retrouve `toString`, `equals` et `hashCode`. Si on redéfinit pas ces méthodes, elles se basent par défaut sur la référence de l'objet : + + - `toString` renvoie une représentation textuelle de l'objet qui est la référence. + - `equals` compare deux objets pour vérifier leur égalité en comparant les références. + - `hashCode` fournit un code de hachage unique pour l'objet en calculant le `hash` de la référence. + + ??? example "Exemple" + + ```java + public class MyClass { + ... + @Override + public boolean equals(Object o) { + // S'ils ont la même référence, c'est qu'il s'agit du même objet. + if (this == o) return true; // En Java, si le if ne contient qu'une ligne, il n'est + // pas nécessaire d'encadrer le code avec { ... } + + // Il faut que ça soit le même type d'objet. + // getClass() est aussi une méthode héritée de Object. + if (o == null || getClass() != o.getClass()) return false; + + // Maintenant qu'on sait que c'est le même type d'objet, on peut 'cast' Object en MyClass. + // Le `cast` est possible parce que MyClass est une sous-classe de Object. + MyClass myClass = (MyClass) o; + + return myClass.getId() == id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "MyClass{" + + "id=" + id + + '}'; + } + } + ``` + + ??? info "Il faut toujours surcharger `hashCode` lorsque vous surchargez `equals`" + D'après la documentation du [`hashCode()`](<https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#hashCode()>){:target="_blank"} : + > The general contract of hashCode is: + > + > * Whenever it is invoked on the same object more than once during an execution of a Java application, + > the hashCode method must consistently return the same integer, provided no information used in equals + > comparisons on the object is modified. This integer need not remain consistent from one execution of an + > application to another execution of the same application. + > + > * If two objects are equal according to the equals method, then calling the hashCode method on each of + > the two objects must produce the same integer result. + > + > * It is not required that if two objects are unequal according to the equals method, then calling the + > hashCode method on each of the two objects must produce distinct integer results. However, the programmer + > should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables. + + Cette documentation fournit des détails sur le contrat entre `hashCode()` et `equals()` et pourquoi il est + indispensable de surcharger la méthode `hashCode()` lorsque vous surchargez `equals()` pour maintenir le comportement + correct des objets dans les structures de données basées sur les hashs comme `HashSet`, `HashMap`, etc. + + +!!! note "À vous de jouer !" + + - Redéfinissez `equals` et `hashCode`. Deux utilisateurs sont différents si et seulement si leurs `id` sont différents. + + Votre IDE peut générer ces méthodes. + + +### Essayez votre code dans le Main + +!!! note "À vous de jouer !" + + Dans le `Main` : + + - Créez un voyage `firstTravel` dont le départ est prévu depuis Paris à destination de Tokyo, pour un maximum de + 5 participants au prix de 2000 euros. + - Créez un utilisateur nommé `John Doe` avec le contact suivant : `john.doe@gmail.com` et loggez ses + informations ( _pensez à redéfinir toString_). + + _Exemple_ : + ```java + User tom = new User(0, "Tom", "Dupont", "tom.dupont@gmail.com"); + ``` + + _Résultat attendu_ : + === "Console" + `Utilisateur John Doe, contact : john.doe@mail.com` + +## Proposition d'offres et intégration des réductions + +### Héritage, polymorphisme et classe abstraite + +#### Héritage et polymorphisme + +Comme pour plusieurs langages de programmation objet, Java dispose aussi d'un système d'héritage. Par contre, +un objet Java ne peut hériter que d'une seule classe. Le multi héritage en Java n'est pas possible. Quand on crée +un nouvel objet, si on ne le fait pas explicitement hériter d'une classe, il sera automatiquement hérité par `Object` +au moment de la compilation. La classe `Object` n'étend aucune autre classe. + +Le mot clé qui permet d'hériter d'une classe est `extends` : + +```java +public class MyClass extends ParentClass { + // ... +} +``` + +Pour voir comment fonctionne l'héritage, nous allons coder un cas pratique. + +Nous voulons proposer à nos utilisateurs différents types d'offres durant leur voyage, pouvant inclure des services de restauration ou d'hôtellerie. + +Nos offres partagent des caractéristiques entre elles, mais ont aussi leurs propres caractéristiques. +Nous allons retranscrire cela dans le code. + +!!! note "À vous de jouer" + + Nous vous laissons choisir le type pour chaque attribut. Dans `model` : + + * Créez les classes `Offer`, `RestaurentOffer` et `HotelOffer`. + * Ajoutez les attributs `id`, `score`, `description`, `nbOfReviews` dans `Offer`. + Nous souhaitons respecter le principe d'encapsulation, mais nous voulons aussi que les classes enfants puissent. + accéder aux attributs sans utiliser de getters et de setters. (cf. le tableau des [modificateurs d'accès](#creation-des-classes)). + * Ajoutez les attributs `nbOfPersons` et `nbOfStars` dans `RestaurentOffer`. + * Ajoutez les attributs `isBreakfastIncluded` et `nbOfAvailableDays` dans `HotelOffer`. + * Faites hériter `RestaurentOffer` et `HotelOffer` de `Offer`. + * Dans `Main.java`, créez une annonce de type `HotelOffer`. + + ??? tip "Indice" + + ```java + public HotelOffer(...) { + super(...); // Pour instancier les attributs de la classe parent. + // super est utilisé pour appeler des fonctions ou accéder + // à des attributs définis dans la classe parent. + ...; + } + ``` + * Créez une méthode `String getDetails()` dans les sous-classes `RestaurantOffer` et `HotelOffer`. + Loggez le détail de la classe `RestaurantOffer` + + ??? info "Les différentes manières de créer une String" + Il existe plusieurs moyens de créer une `String` en Java. Voici les façons les plus courantes de le faire : + ```java + return "Premier détail " + attribut1 + " second détail " + attribut2; + + return "Premier détail %s second détail %s".formatted(attribut1, attribut2)); + + StringBuilder sb = new StringBuilder(); + sb.append("Premier détail "); + sb.append(attribut1); + sb.append(" second détail "); + sb.append(attribut2); + return sb.toString(); + ``` + +À présent, nous souhaiterions savoir si nos offres sont valides ou pas. + +Pour cela, nous allons ajouter cette méthode dans la classe `Offer` : + +```java +public boolean isValid() { +return score >= 0 && + score <= 5 && + description != null && + !description.isBlank() && + nbOfReviews >= 0; +} +``` + +Mais comme vous vous en doutez, on voudrait faire de la validation pour chaque sous-type d'offre. + +Pour ce faire, nous allons devoir employer une des notions du polymorphisme en Java, qui est la surcharge de méthode. +La méthode enfant doit avoir la même signature que celle présente dans la classe parent. Elle devrait aussi être annotée par `@Override` même +si ce n'est pas requis pour compiler le code. Cela contribue à rendre le code source plus lisible. + +Il faudra aussi que la validation des méthodes enfants prennent en compte celle du parent. + +!!! note "À vous de jouer !" + + - Redéfinissez la méthode `isValid()` dans chaque classe enfant. + + ??? tip "Indice" + + ```java + @Override + public boolean isValid() { + return super.isValid() && + ... + } + ``` + +#### Classe abstraite + +Vous remarquerez qu'on a une méthode `getDetails()` dans toutes les sous-classes et on voudrait l'avoir pour toutes les futures sous classes. +Aussi, il y a peu d'intérêt à instancier uniquement la classe `Offre`. C'est là que rentrent en jeu les classes abstraites. + +Pour que l'on ne puisse pas instancier une classe `Offre` dans le code, il faut rendre cette classe abstraite de la +manière suivante : +```java +public abstract class Offer { + ... +} +``` + +De plus, si on veut garantir que l'ajout de `getDetails()` soit effectué à chaque fois qu'une nouvelle sous-classe sera créée, il faudra ajouter une méthode abstraite dans la classe abstraite pour rendre obligatoire la création de la méthode `getDetails()` : +```java +public abstract class Offer { + ... + // Ajouter cette ligne en dessous des attributs. + protected abstract String getDetails(); + ... +} +``` + +Regardez ce qui se passe lorsque vous supprimez une des méthodes `getDetails()` et que vous essayez de compiler. + +Pensez à rajouter `@Override` sur toutes les méthodes `getDetails()`. + +??? info "Modificateur d'accès et surcharge" + + Vous avez probablement remarqué que les modificateurs d'accès utilisés ne sont pas les mêmes entre la classe parent et les classes enfants. En Java, le modificateur d'accès d'une méthode de surcharge peut autoriser plus, mais pas moins, + d'accès que la méthode surchargée. Par exemple, une méthode d'instance `protected` dans la classe parent peut être + rendue `public`, mais pas `private`, dans la sous-classe. + + + +#### Interfaces + +On a presque défini toutes nos classes. On veut désormais offrir des réductions à notre clientèle, comment pourrait-on +les appliquer ? + +L'idée est de définir une **interface** commune appelée `Discount` qui énonce la méthode nécessaire pour calculer une réduction. Cette interface servira de contrat, en spécifiant le comportement attendu. + +Ensuite, nous créerons deux classes distinctes : `PercentDiscount` et `ValueDiscount`. Ces deux classes implémenteront l'interface `Discount`, ce qui signifie qu'elles doivent fournir une implémentation de la méthode définie dans cette interface. + +??? example "Exemple" + + Dans cet exemple, une interface Animal est définie avec une méthode shout() (crier). + + _Interface_ : + ```java + public interface Animal { + void shout(); // Toutes les déclarations de méthodes dans une interface sont implicitement `public` + // de sorte que vous pouvez omettre le modificateur `public`. + } + ``` + + Les classes Chien et Chat implémentent cette interface, ce qui signifie qu'elles devront fournir une implémentation + concrète de la méthode shout(). + + _Implémentation_ : + + ```java + // Classe Dog qui implémente l'interface Animal + public class Dog implements Animal { + + // ... + + @Override + public void shout() { + bark(); // Pour cet exemple, aboyer est utilisé pour implémenter crier + } + + // Autres méthodes spécifiques aux chiens + } + + // Classe Cat qui implémente l'interface Animal + public class Cat implements Animal { + + // ... + + @Override + public void shout() { + meow(); // Pour cet exemple, miauler est utilisé pour implémenter crier + } + + // Autres méthodes spécifiques aux chats + } + ``` + +!!! note "À vous de jouer" + + * Créez l'interface `Discount` et les classes `PercentDiscount` et `ValueDiscount`. + * Ajoutez un tableau de `Discount` à la classe `Travel`. + + ??? tip "Indice" + + === "Discount.java" + ```java + public interface Discount { + double apply(double price); + } + ``` + === "PercentDiscount" + ```java + ... + price * (1 - ((double) percent / 100)); // price * (1 - (percent / 100)) utilise une division entière + // si percent est un entier, conduisant à une troncature. + // Ajouter (double) avant percent assure une division en nombres décimaux, + // préservant les décimales dans le résultat. + ... + ``` + === "ValueDiscount" + ```java + ... + price - value; + ... + ``` + === "Travel.java" + ```java + ... + private Discount[] discounts; + ... + ``` + +Lorsque vous déclarez un tableau `discounts` dans la classe `Travel` comme étant de type `Discount`, cela +signifie que **discounts peut contenir n'importe quelle instance d'une classe qui implémente `Discount`**. Ainsi, +vous pouvez utiliser des instances de `PercentDiscount`, `ValueDiscount`, ou même d'autres classes qui +implémentent Discount sans modifier le code de la classe `Travel`. Cela s'appelle du **polymorphisme +paramétrique**. + +Le polymorphisme paramétrique est aussi connu sous le nom [generic types](https://docs.oracle.com/javase/tutorial/java/generics/types.html){:target="_blank"} ou templates. + + +### Essayez votre code dans le Main + +!!! note "À vous de jouer" + + Dans le `Main` : + + * Modifiez `firstTravel` en ajoutant une réduction de 50%. + ??? tip "Indice" + ```java + // TODO: Création d'un tableau de discount de taille 1 + // TODO: Ajout d'une PercentDiscout dans le tableau + // TODO: Ajout du tableau de discount dans le travel + ``` + + * Calculez le nouveau prix après l'application de cette réduction, affichez-le et assurez-vous qu'il est bien de + 1000 euros. + + ??? tip "Indice" + ```java + firstTravel.getDiscounts()[0]... + ``` + + * Ajoutez une nouvelle réduction de type `ValueDiscount` au tableau `discounts` et calculez le nouveau prix après + l'application de toutes les réductions. Affichez et assurez vous que le prix prend bien en compte toutes les réductions. + Pensez à augmenter la taille du tableau `Discount[]`. + + ??? tip "Indice" + Pensez à gérer le cas ou `firstTravel.getDiscounts().length` est 0. + ```java + double discountedPrice = ...; + for (Discount discount : firstTravel.getDiscounts()) { + discountedPrice = ... + } + ``` + +## **Récapitulatif** + +Bravo ! Vous avez terminé votre première initiation à Java. Pour aller plus loin, nous allons maintenant explorer +les API Java pour découvrir les fonctionnalités avancées que le langage a à offrir. + +!!! check + ``` + . + └── model + │ ├── Travel.java + │ ├── User.java + │ ├── Discount.java + │ ├── PercentDiscount.java + │ ├── ValueDiscount.java + │ ├── Offer.java + │ ├── RestaurentOffer.java + │ └── HotelOffer.java + └── Main.java` + ``` + +!!! warning "N'oubliez pas de `Commit` votre travail !" diff --git a/resources/docs/docs/chapters/tp1/prerequisites.md b/resources/docs/docs/chapters/tp1/prerequisites.md new file mode 100644 index 0000000000000000000000000000000000000000..916e223a5d02d60fb445f1498aac1b7ccc63ab56 --- /dev/null +++ b/resources/docs/docs/chapters/tp1/prerequisites.md @@ -0,0 +1,61 @@ +# Prérequis + +Avant de commencer, il est nécessaire d'installer quelques outils pour ce tutoriel. + +## Java JDK + +!!! info "Java est un langage de programmation, qui nécessite entre autres 2 outils : le **JDK** et le **JRE**" + + - Le **JRE** (Java Runtime Environment) fournit la **JVM** (Java Virtual Machine) indispensable à l'exécution des programmes + Java qui ont été préalablement compilés à l'aide du JDK. + - Le **JDK** (Java Development Kit), en plus de contenir le **JRE**, comprend également l'ensemble des outils nécessaires + pour développer du code Java, tels qu'un **compilateur**, un **debugger**, la **documentation JavaDoc** et bien d'autres fonctionnalités. + +### Installer le _JDK_ + +Installer la dernière version _LTS_ du _JDK_ sur votre machine. + +??? question "LTS ?" + + Depuis la version 9 de Java, une nouvelle version de Java est publiée [tous les 6 mois](https://www.oracle.com/java/technologies/java-se-support-roadmap.html){:target="_blank"}. + À chaque nouvelle version, Oracle arrête le support de la précédente. Certaines versions sont dites "LTS" (Long-Term Support) et sont supportées pendant une période de plusieurs années, au lieu de quelques mois. + +**Installation avec SDKMan** + +La procédure pour installer Java varie en fonction des versions, des plateformes, des systèmes d'exploitation, ... + +[SDKMan](https://sdkman.io/){:target="_blank"} est un outil "cross-platform" qui a pour but de rendre l'opération plus facile. + +```bash + curl -s "https://get.sdkman.io" | bash +``` + +**Installation avec un package manager** + +Il est possible d'installer le JDK à la main, sans _SDKMan_. + +??? example "Voir les instructions" + + * Debian-based + + ```bash + sudo apt install openjdk-21-jdk + ``` + + * Windows + + Télécharger l'installeur [ici](https://www.oracle.com/java/technologies/downloads){:target="_blank"} et suivre les instructions + + +## Installer un IDE + +Pour écrire du code Java, un simple éditeur de texte suffit, mais utiliser un _IDE_ rend les choses plus faciles, car il apporte de l'aide supplémentaire (coloration syntaxique, auto-complétion, ...). + +## **Choisir un IDE adapté** + +!!! info "Environnement de développement intégré (IDE)" + + Voici nos recommendations : + + * **Visual Studio Code (VSCode)** : pour sa légèreté et sa facilité d'utilisation ([Visual Studio Code](https://code.visualstudio.com/){:target="_blank"} et [le pack d'extension java](https://code.visualstudio.com/docs/java/extensions#_fundamental-java-development){:target="_blank"}) + * **IntelliJ IDEA** : un autre choix populaire parmi les développeurs Java, connu pour ses puissantes fonctionnalités et son support approfondi de Java ([IntelliJ Community](https://www.jetbrains.com/idea/download/){:target="_blank"}) diff --git a/resources/docs/docs/chapters/tp1/project_creation.md b/resources/docs/docs/chapters/tp1/project_creation.md new file mode 100644 index 0000000000000000000000000000000000000000..6eb97aad434076848e088aa82501909de9c79a21 --- /dev/null +++ b/resources/docs/docs/chapters/tp1/project_creation.md @@ -0,0 +1,87 @@ +# Création du projet + +Avant de plonger dans la programmation des fonctionnalités de notre application, il est essentiel de configurer +correctement notre environnement de développement. + +!!! edit "À vous de jouer" + - Ouvrez votre IDE et créez un nouveau projet que vous nommerez _**AgencyManagement**_. + - Ajoutez votre projet sur Git. + - Dans le dossier src, créez un nouveau package nommé _**io.takima.agencymanagement**_ + +!!! info + Un package est un dossier dont le but est de regrouper les classes qui fonctionnent ensemble. + + 1. Il porte un **nom unique**. + 2. Il doit être nommé en **minuscule**, avec un nom alphanumérique (a..z, 0..9, _). Le nom ne doit pas commencer par + un nombre. + 3. Le début du package doit identifier votre projet / organisation. A cet effet, on utilise par convention un nom de + domaine inversé comme nom de package. + + En Java, **chaque classe doit appartenir à un package**. + +!!! edit "À vous de jouer" + + - Déplacez la classe Main générée dans le package précédemment créé. + - Modifiez votre programme pour que ça affiche `Agency App` et exécutez-le depuis votre IDE. + + ??? tip + === "Main.java" + ```java + public class Main { + public static void main(String[] args) { + System.out.println("Agency App"); + } + } + ``` + === "Console" + ``` + Agency App + ``` + +Vous pouvez également compiler et exécuter votre code en ligne de commande : [voir cheatsheet](../../cheatsheets/tp1/command_line.md){:target="_blank"} + +## **Architecture utilisée** + +Nous utiliserons une architecture 3-tiers pour notre projet. C'est une des architectures les plus utilisées pour faire du +backend en Java : + +- La couche **persistance** est la couche qui gère l'interaction avec la base de données. Cette couche est mise en œuvre à l'aide de DAO (Data Access Objects). Les DAO sont des +composants responsables de la gestion des opérations d'accès aux données, agissant comme une interface entre le +reste de l'application et la base de données sous-jacente. +- La couche **service** est la couche qui applique les règles métiers de notre application. Elle utilise les DAO de la +couche persistence et transmet les informations à la couche présentation. +- La couche **présentation** permet d'exposer des informations à l'extérieur (par exemple au frontend). Elle fait +intervenir des controllers qui échangeront les données (par exemple via le protocole HTTP) avec un autre service (par +exemple un frontend). + + + +La partie **Model** permet de représenter les objets en base de données en objet Java. Ce n'est pas une couche en +tant que telle mais elle est essentielle pour que les couches persistance et service manipulent les données. + +La partie DTO permet de contrôler les échanges avec l'extérieur. Elle correspond à une représentation de notre +modèle et permet de l'exposer. + +Pour notre première session, nous nous limiterons aux couches model, service ainsi que la partie response DTO. La +gestion de la **couche persistance** sera réservée au **Jour 2**, tandis que la **couche de présentation** sera +explorée au cours du **Jour 3**. + +Voici un apercu des parties que nous traiterons au cours du Jour 1 (les parties grisées ne seront pas traitées). + + + +## **Récapitulatif** + +Vous disposez désormais d'un environnement de développement fonctionnel pour Java et avez découvert la structure +envisagée pour notre projet. Nous pouvons maintenant passer à notre première étape, qui consiste à définir nos classes. + +À la fin de cette partie, vous devriez avoir cette arborescence. + +!!! check + ``` + └── src + └── io + └── takima + └── agencymanagment + └── Main.java + ``` diff --git a/resources/docs/docs/chapters/tp2/database.md b/resources/docs/docs/chapters/tp2/database.md new file mode 100644 index 0000000000000000000000000000000000000000..343f03eb22d57086ef0b79b9b8f38fe6186fecc5 --- /dev/null +++ b/resources/docs/docs/chapters/tp2/database.md @@ -0,0 +1,403 @@ +# Utiliser une base de données + +Dans cette section, nous verrons comment utiliser une base de données avec Java et nous réaliserons nos **DAO**. + +## Préparation + +Pour la suite de ce tutoriel, nous utiliserons une base de données comportant les tables : +- travel, +- user, +- reservation (pour la relation d'un utilisateur inscrit pour un voyage) +- wait_list (pour la relation d'un utilisateur inscrit dans la file d'attente d'un voyage). + +### Récupérez les scripts de la base de données + +- Téléchargez les scripts d'initialisation : + + [Database init](../../downloads/db-init.zip){ .md-button .md-button--primary } + + Cette archive contient quatre scripts : + + - `db/V1.0.0__travel_user.table.sql` : Définit les tables `Travel` et `User`. + - `db/V1.0.1__travel_user.data.sql` : Ajoute quelques entrées aux tables mentionnées ci-dessus + +- Placez les scripts dans un nouveau dossier appelé `db`, à la racine de votre répertoire de travail. + +### Mise en place de la base de données + +Nous allons utiliser une base de données [_PostgreSQL_](https://www.postgresql.org/){:target="_blank"}. + +Pour des raisons de simplicité, la base de données tourne dans un conteneur Docker et nous vous fournissons tous les fichiers d'installation. + +- Installez [Docker Engine](https://docs.docker.com/engine/install/){:target="_blank"}. + + Vous pouvez ignorer cette étape si Docker est déjà installé sur votre système. Dans le cas contraire, voici comment procéder à son installation : [_installer Docker Engine_](../../cheatsheets/tp2/docker_install.md){:target="_blank"} + +- Téléchargez les fichiers `docker-compose` et placez-les à la racine de votre projet : + [Download docker-compose-db](../../downloads/docker-compose-db.zip){ .md-button .md-button--primary} + + Cette archive contient trois fichiers : + + - `docker-compose.yml` : Définit un service `db` qui démarre une instance PostgreSQL, initialisée avec les scripts que nous avons placés précédemment dans le dossier `db`. + - `docker-compose.override.yml` : Définit les paramètres Docker pour le développement, expose la base de données au port `5432` et génère un [Adminer](https://www.adminer.org/){:target="_blank"} pour gérer la base de données avec une *GUI*. + +- Exécutez la base de données avec la commande `docker compose`. + ```bash + docker compose up + ``` + +- Une fois l'exécution réussie, vous pouvez vous connecter à la base de données à l'aide de l'_Adminer_ sur [http://localhost:18080](http://localhost:18080){:target="_blank"} en utilisant : + - **nom d'utilisateur**=_madmin_ + - **mot de passe**=_madmin_ + - **nom de la base de données**=_agencymanagement_db_ + Ce sont les valeurs par défaut spécifiées dans le fichier docker-compose. + + Assurez-vous d'avoir quatre tables : `travel`, `user`, `reservation` et `wait_list`. + +  + +## JDBC + +La connexion à une base de données avec Java est normalisée par la [spécification JDBC](https://docs.oracle.com/javase/tutorial/jdbc/basics/index.html){:target="_blank"} pour "Java Database Connectivity". +C'est une API fondamentale pour la connectivité des bases de données dans l'écosystème Java. Elle offre les outils nécessaires pour établir des connexions, exécuter des requêtes SQL, récupérer et mettre à jour des données dans les bases de données, tout en gérant efficacement les connexions. + +!!! info + - Pour faire simple, _JDBC_ offre un objet [`Connection`](https://docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/Connection.html){:target="_blank"}, ainsi que le strict minimum pour exécuter des instructions SQL ([`Statement`](https://docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/Statement.html){:target="_blank"}, [`PreparedStatement`](https://docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/PreparedStatement.html){:target="_blank"}). + - Dans des projets plus avancés, on utilise des bibliothèques avancées telles que [**Hibernate**](https://hibernate.org/){:target="_blank"} ou [**jOOQ**](https://www.jooq.org/){:target="_blank"} au lieu du simple JDBC pour gérer nos bases de données SQL. + +### Installez le pilote JDBC / JDBC Driver + +JDBC est compatible avec les _SGBD_ (Systèmes de Gestion de Base de Données) les plus populaires. + +- Ajoutez la dernière dépendance [`PostgreSQL`](https://mvnrepository.com/artifact/org.postgresql/postgresql){:target="_blank"} à votre `pom.xml`. + +??? abstract "Affichez le pom.xml résultant" + ```xml + <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + <version>${postgres.version}</version> + <scope>runtime</scope> + </dependency> + ``` + +Le pilote JDBC est une bibliothèque qui fournit l'implémentation **spécifique** pour se connecter à un _SGBD_. En d'autres termes, il est **nécessaire à l'exécution** mais n'est jamais référencé directement dans le code. Par conséquent, la portée de Maven pour les pilotes JDBC **doit être `runtime`**. + +### JDBC Connection + +JDBC définit un objet `Connection` qui représente une seule connexion à la base de données. Vous pouvez obtenir cet objet `Connection` avec l'instruction suivante : +```java +public Connection getConnection() throws SQLException { + return DriverManager.getConnection("jdbc:postgresql://localhost:5432/agencymanagement_db", "user", "password"); +} +``` + +Nous allons utiliser l'objet `Connection` à plusieurs endroits, mais comme il est préférable de ne pas copier-coller cette méthode dans tout le code, nous allons définir une classe `ConnectionManager` pour s'en occuper. + +#### Le **singleton ConnectionManager** : + +Nous n'avons besoin que d'un seul `ConnectionManager` pour gérer l'objet `Connection`. Pour ce cas d'utilisation, +le design pattern [**Singleton**](https://en.wikipedia.org/wiki/Singleton_pattern){:target="_blank"} convient parfaitement. + +- Créez une nouvelle classe appelée `ConnectionManager`. Cette classe utilise le modèle **singleton**. +- Ajoutez une méthode appelée `getConnection()` permettant de récupérer la connexion. + +!!! tip + Comme l'a popularisé _Joshua Block_ dans son livre **_Effective Java_**, il est recommandé d'écrire des + singletons à l'aide des [Java enums](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html){:target="_blank"}. + + === "ConnectionManager.java" + ```java + package io.takima.agencymanagement + + import java.sql.Connection; + import java.sql.DriverManager; + import java.sql.SQLException; + + public enum ConnectionManager { + INSTANCE; + + private final String JDBC_URL = "jdbc:postgresql://localhost:5432/agencymanagement_db"; + private final String JDBC_USER = "madmin"; + private final String JDBC_PASSWORD = "madmin"; + + public Connection getConnection() throws SQLException { + return DriverManager.getConnection(JDBC_URL,JDBC_USER,JDBC_PASSWORD); + } + } + ``` + +## DAO + +La récupération et le mapping des données de la base de données vers Java doivent être effectués par des **DAO**. +Comme on l'a déjà mentionné, les DAO (Data Access Objects) servent d'interface intermédiaire entre la logique métier +et la base de données. Leur rôle principal est de simplifier l'accès aux données en isolant la logique d'accès, formant ainsi une couche de persistance. + +Étant donné que des DAO vous ont été fournis lors du jour 1, c'est maintenant à votre tour de créer vos propres DAO. + +### **PreparedStatement** + +L'objet `Connection` offre plusieurs méthodes pour exécuter des requêtes SQL. Dans cette étape, nous allons utiliser +[`Connection.prepareStatement`](https://docs.oracle.com/en/java/javase/21/docs/api/java.sql/java/sql/Connection.html#prepareStatement(java.lang.String)){:target="_blank"}. + +??? question "Pourquoi privilégier les _PreparedStatement_ plutôt que les _Statement_ ?" + Contrairement à la classe [`Statement`](https://docs.oracle.com/en/java/javase/21/docs/api/java.sql/java/sql/Statement.html){:target="_blank"}, la classe [`PreparedStatement`](https://docs.oracle.com/en/java/javase/21/docs/api/java.sql/java/sql/PreparedStatement.html){:target="_blank"} permet de définir des paramètres de manière dynamique et offre une protection contre les injections SQL. De plus, elle est mise en cache par le SGBD, ce qui la rend plus efficace lorsqu'elle est exécutée plusieurs fois. + +Codons ensemble la fonction nous permettra d'insérer des données, on fera ça pour un travel. + +```java +public void save(Travel travel) throws SQLException { + // Récupérer la connexion + Connection connection = cm.getConnection(); + // Ecriture de la requête SQL + PreparedStatement ps = connection.prepareStatement("INSERT INTO travel(id, name, departure_airport, " + + " arrival_airport, departure_date, arrival_date, destination, capacity, price) " + + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + // Attribution des paramêtres + ps.setLong(1, travel.getId()); + ps.setString(2, travel.getName()); + ps.setString(3, travel.getDepartureAirport().getAcronym()); + ps.setString(4, travel.getArrivalAirport().getAcronym()); + // Il faut transformer le Instant en Timestamp + ps.setTimestamp(5, java.sql.Timestamp.from(travel.getDepartureDate())); + ps.setTimestamp(6, java.sql.Timestamp.from(travel.getArrivalDate())); + ps.setString(7, travel.getDestination()); + ps.setInt(8, travel.getCapacity()); + ps.setDouble(9, travel.getPrice()); + + // Exécution de la requête + ps.executeQuery(); +} +``` + +!!! note "À vous de jouer" + + - Créez une classe nommée `JdbcTravelDao` qui utilise JDBC et implémente les fonctions suivantes : + + On veut commencer par lister tous les voyages proposés par notre agence. + + - Créez la méthode `List<Travel> findAll();`. + - Créez une `PreparedStatement` pour sélectionner tous les voyages. + - Créez la méthode `List<Travel> findTravelsByDestination(String destination)` pour lister tous les voyages ayant + une destination précise. + - Exécutez cette instruction, itérez sur le `ResultSet` et imprimez les noms des voyages. + + ??? tip "Indice" + === "JdbcTravelDao" + ```java + private final ConnectionManager cm = ConnectionManager.INSTANCE; + // dao fonctions + ... + ``` + === "findAll" + ```java + public List<String> findAll() throws SQLException { + Connection connection = cm.getConnection(); + PreparedStatement ps = connection.prepareStatement("SELECT t.name FROM travel t"); + + ... + } + ``` + === "findTravelsByDestination" + ```java + public List<String> findTravelsByDestination(String destination) { + Connection connection = cm.getConnection(); + PreparedStatement ps = connection.prepareStatement( "SELECT * FROM travel t + WHERE t.destination LIKE? ") ; + + ps.setString(1, "%" + destination + "%"); + } + ``` + === "ResultSet" + ```java + public List<String> findTravelsByDestination(String destination) throws SQLException { + // ... + + ResultSet rs = ps.executeQuery(); + List<String> travels = new LinkedList<>(); + + while (rs.next()) { + travels.add(rs.getString("name")); + } + + return travels; + } + ``` + +!!! bug "Note" + - Envisagez de nommer une méthode findById() au lieu de geById() : + + N'appelez pas la méthode getById() si elle risque de ne pas donner de résultat. Si la méthode renvoie un résultat null dans le cas d'une ressource non trouvée, il est préférable d'appeler la méthode findById(). + + - Renvoyez un Optional<X> plutôt que null : + + Si une méthode renvoie null, vous devez vérifier la nullité à chaque fois que vous appelez la méthode. Vous prenez le risque d'oublier cette vérification et d'avoir une `NullPointerException`. + +- N'oubliez pas de fermer les ressources une fois qu'elles ne sont plus nécessaires : + +!!! danger "Attention !" + ```java + connection.close(); + ``` + +Comme indiqué dans [la documentation](https://docs.oracle.com/en/java/javase/21/docs/api/java.sql/java/sql/Connection.html#close()){:target="_blank"}, la fermeture de la `Connection` ferme également les `PreparedStatement` et `ResultSet` associés. + +- N'oubliez pas de fermer la `Connection` en cas d'exception également. Comme `Connection` implémente +[`AutoClosable`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/AutoCloseable.html){:target="_blank"}, la méthode [_try-with-resources_](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html){:target="_blank"} est parfaitement adaptée à ce cas d'utilisation. + +!!! quote "" + === "try-catch" + ```java + Connection connection = null; + try { + connection = cm.getConnection(); + PreparedStatement ps = connection.prepareStatement("..."); + + ResultSet rs = ps.executeQuery(); + } catch (SQLException e) { + if (connection != null) { + connection.close(); + } + throw e; + } + ``` + + === "try-with-resources" + ```java + try (Connection connection = cm.getConnection()) { + + PreparedStatement ps = connection.prepareStatement("..."); + + ResultSet rs = ps.executeQuery(); + } + ``` + +!!! note "À vous de jouer" + - À votre tour, implémentez `List<User> findAllParticipantsByTravelId(long travelId);` pour lister tous les + voyageurs inscrits à un voyage précis. + - Respectez la structure proposée lors du premier jour et créez votre service `TravelService`, utilisant les fonctions du DAO. + - Testez votre code en appelant le service à partir de votre méthode main. + + ??? example "Exemple" + + ```java + + public static void main(String[] args) throws Exception { + List<String> travels = travelService.findAll(); + + travels.forEach(travel -> { + LOGGER.info("{}", travel); + }); + } + ``` + +## **Connection pool** + +Dans les applications d'entreprise réelles, la base de données a souvent beaucoup de lecture/écriture à faire et peut devenir un "goulot d'étranglement". + +Pour atténuer ce problème, nous utilisons souvent plusieurs objets `Connection` pour équilibrer la charge. + +Avec votre code actuel, il serait facile d'appeler `DriverManager.getConnection(jdbcUrl)` à chaque fois que vous en avez besoin, mais cela pose un certain nombre de problèmes : + +- Il est difficile de contrôler le nombre de connexions ouvertes. +- Un trop grand nombre de connexions ouvertes ralentit l'application. +- L'ouverture et la fermeture d'une connexion est un processus lourd. + +Idéalement, nous voulons ouvrir un nombre fixe de connexions et les réutiliser à l'infini. + +C'est exactement ce à quoi sert un **connexion pool**. + +### [HikariCP](https://github.com/brettwooldridge/HikariCP){:target="_blank"} + +HikariCP est en effet un pool de connexions pour Java, utilisé pour gérer et optimiser l'utilisation des connexions à une base de données. + +!!! note "À vous de jouer" + + - Ajoutez la [dépendance HikariCP](https://mvnrepository.com/artifact/com.zaxxer/HikariCP){:target="_blank"} à votre `pom.xml`. + ??? example "Afficher le pom.xml résultant" + ```xml + <dependency> + <groupId>com.zaxxer</groupId> + <artifactId>HikariCP</artifactId> + <version>${hikaricp.version}</version> + </dependency> + ``` + + - Modifiez le `ConnectionManager` pour utiliser _HikariCP_. + + ??? tip "Indice" + ```java + public enum ConnectionManager { + INSTANCE; + + private static final String JDBC_URL = "jdbc:postgresql://localhost:5432/agency_management_db"; + private static final HikariConfig config = new HikariConfig(); + private static final HikariDataSource ds; + + ... + } + ``` + +## Service / DAO + +Maintenant que nous avons compris comment les interactions avec notre base de données fonctionnent, revenons à notre diagramme. Vous remarquerez que les DAO sont appelés uniquement par leur service correspondant. D'autre part, les services peuvent être appelés entre eux. + + + +**_Exemple : _** + +Nous avons une fonction `findAll` dans notre DAO. Pour pouvoir l'utiliser dans le service, il faut faire : +!!! quote "" + === "JdbcTravelDao" + ```java + ... + public List<String> findAll() ... + ... + ``` + + === "TravelService" + ```java + ... + private final JdbcTravelDao jdbcTravelDao = new JdbcTravelDao(); + + public List<Travel> findAll() { + return jdbcTravelDao.findAll(); + } + ... + ``` + +!!! note "À vous de jouer" + + - Pour toutes les fonctions créées dans votre service (durant le jour 1), réalisez une fonction DAO que vous appellerez selon vos besoins : + + !!! quote "" + === "JdbcTravelDao" + ```java + ... + public List<String> findAvailableTravels() { + ... + } + ... + ``` + + === "TravelService" + ```java + ... + public List<Travel> findAvailableTravels() { + return jdbcTravelDao.findAvailableTravels(); + } + ... + ``` + +## Fichiers modifiés + +``` +docker-compose.yml +docker-compose.override.yml +pom.xml +src/main/java/io/takima/agencymanagement/Application.java +``` + +!!! warning "N'oubliez pas de `Commit` votre travail !" + diff --git a/resources/docs/docs/chapters/tp2/dependances.md b/resources/docs/docs/chapters/tp2/dependances.md new file mode 100644 index 0000000000000000000000000000000000000000..7ac79848e4a4ae76f1dca5ccf72616df3dc941e5 --- /dev/null +++ b/resources/docs/docs/chapters/tp2/dependances.md @@ -0,0 +1,244 @@ +# Travailler avec des dépendances + +Pendant l'écriture et le débogage de notre application, il peut être tentant d'utiliser beaucoup de `System.out.println` pour imprimer la _trace d'exécution_, pour "voir où cela commence à échouer". +```java +// ... +if (user == null) { + if (enableDebug) { + System.out.println("foo 1") + System.out.println("Error: user is null") + } + // ... +} +``` + +C'est une très mauvaise façon de créer des logs : + +- Elle alourdit à la fois le code et la sortie. +- Cela ralentit l'exécution du programme. +- Vous oublierez de les supprimer après le débogage, ce qui entraînera beaucoup de bruit dans le code. + +Pour faciliter le débogage de votre code, il est préférable d'utiliser un **Logger** car : + +- Il existe plusieurs **niveaux de log** (`DEBUG`, `INFO`, `WARN`, `ERROR`). Vous pouvez donc choisir celui qui convient le mieux à votre environnement (ex : `INFO` pour les besoins de production, `DEBUG` pour les environnements de développement...) +- Il peut **écrire les logs dans un fichier** plutôt que dans la console. +- Il est **indépendant de la plateforme**. +- Il offre de **meilleures performances** avec des flux tamponnés. +- Il permet aux bibliothèques installées de logger avec votre Logger afin d'éviter l'encombrement de plusieurs loggers. + +Dans ce chapitre, nous utiliserons l'API de journalisation **_SLF4J_**. + +## SLF4J + +### Installez SLF4J + +- Ajoutez la dernière version de [`slf4j-api`](https://mvnrepository.com/artifact/org.slf4j/slf4j-api){:target="_blank"} dans la partie `dependencies` du fichier `pom.xml`. + +??? example "Afficher le pom.xml résultant" + + ```xml + <project> + <dependencies> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>2.0.6</version> + </dependency> + </dependencies> + </project> + ``` + +!!! tip "**tip**" + - Lorsque vous cherchez une bibliothèque Maven, ne cherchez pas sur Google. Parcourez simplement [ Maven central](https://mvnrepository.com){:target="_blank"}. + - Si vous utilisez un IDE comme Intellij, lancez la synchronisation des dépendances Maven lorsque vous ajoutez de nouvelles dépendances, pour lui permettre de vous donner les bonnes suggestions d'importation. + +### Utilisez SLF4J + +* Modifiez App.java et réécrivez le message initial Hello world ! pour utiliser le logger. + +??? example "app.java" + ```java + package io.takima.agencymanagement; + + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + public class App { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + public static void main(String[] args) { + LOGGER.info("Hello World!"); + } + } + ``` + +* Lancez le code : + +!!! failure + ``` + SLF4J: No SLF4J providers were found. + SLF4J: Defaulting to no-operation (NOP) logger implementation + SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details. + ``` + + Comme l'indique l'erreur, _SLF4J_ a besoin d'une implémentation. `slf4j-api` est seulement une API qui ne fait donc rien par elle-même : elle s'appuie sur d'autres dépendances pour implémenter les fonctionnalités de logging. + Dans ce chapitre, nous utiliserons [`slf4j-log4j12`](https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12){:target="_blank"} comme **implémentation pour SLF4J**. + +## Installez et configurez `log4j` + +- Ajoutez [`slf4j-log4j12`](https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12){:target="_blank"} dans la partie `dependencies` du fichier `pom.xml`. + +??? example "Afficher le pom.xml résultant" + + ```xml + <project> + <dependencies> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + <version>2.0.6</version> + </dependency> + </dependencies> + </project> + ``` + +!!! info + + Certaines dépendances peuvent avoir des versions alignées (ex : `slf4j-log4j12` avec `slf4j-api`). Pour les garder alignées et éviter le copier-coller, c'est une bonne pratique d'utiliser `<properties>` au-dessus du `pom.xml` pour déclarer les versions comme des variables. + + ??? info "pom.xml" + ```xml + <properties> + <java.version>17</java.version> + <maven.compiler.source>${java.version}</maven.compiler.source> + <maven.compiler.target>${slf4j.version}</maven.compiler.target> + <slf4j.version>2.0.6</slf4j.version> + </properties> + <dependencies> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + <version>${slf4j.version}</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>${slf4j.version}</version> + </dependency> + </dependencies> + ``` + +### Configurez `Log4j` + +- Créez un dossier dans `src/main/resources` s'il n'existe pas. +- Mettez-y un nouveau fichier appelé [`log4j.properties`](https://logging.apache.org/log4j/2.x/manual/configuration.html#Properties){:target="_blank"}, avec la configuration suivante. + +```properties + log4j.rootLogger=DEBUG, stdout + + log4j.appender.stdout=org.apache.log4j.ConsoleAppender + log4j.appender.stdout.Target=System.out + log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +``` + +- Exécutez à nouveau le code. Cette fois, le journal devrait apparaître sur la sortie standard : + + ``` + INFO App:14 - Hello World! + ``` + +??? failure "Error: No SLF4J providers were found." + + Vous avez à nouveau cette erreur ? + + Avez-vous correctement défini le `<scope>` à `compile` pour la dépendance `slf4j-log4j12` ? + +## Exécutez sans IDE + +Dans cette étape, nous allons compiler et exécuter le code **sans avoir besoin d'un IDE**. + +```bash +mvn clean package +java -jar target/takima-agencymanagement-1.0-SNAPSHOT.jar +``` + +!!! failure + + ``` + Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory + at io.takima.agencymanagement.App.<clinit>(App.java:11) + Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory + at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) + at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) + at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) + ... 1 more + ``` + +Une autre erreur d'exécution. Cette fois, Java exécute le `jar`, mais notre application dépend d'une dépendance externe `org/slf4j/LoggerFactory`. +Parce que cette dépendance est gérée par Maven, elle a été installée dans votre dépôt local `.m2`. Cependant, Java ne va pas automatiquement y chercher des classes supplémentaires, d'où l'erreur. + +Pour activer cette dépendance, vous devez configurer le classpath de la _JVM_ avec l'option `java -classpath`. +Dans l'étape suivante, nous allons explorer une technique appelée **_fat JAR_**. + +### Fat JAR + +Un _fat JAR_, également appelé _uber JAR_, ou [_jar-with-dependencies_](https://maven.apache.org/plugins/maven-assembly-plugin/descriptor-refs.html#jar-with-dependencies){:target="_blank"}, est un `jar` qui regroupe toutes les dépendances nécessaires, de sorte que l'application ne dépende pas des dépendances installées localement. + +Ce type de `jar` peut être construit avec l'aide de [`maven-assembly-plugin`](https://maven.apache.org/plugins/maven-assembly-plugin/descriptor-refs.html#jar-with-dependencies){:target="_blank"}. + +!!! info + - Un _fat JAR_ ne nécessite pas de configurer le _CLASSPATH_. + - Un _fat JAR_ a une taille plus importante que les _JARs_ normaux. + +- Editez le `pom.xml` et ajoutez ou remplacez le `maven-jar-plugin` existant, avec la configuration suivante : + +!!! quote "" + ```xml + <!-- Maven Assembly Plugin --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <!-- get all project dependencies --> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + <!-- MainClass in manifest make a jar executable --> + <archive> + <manifest> + <mainClass>io.takima.agencymanagement.App</mainClass> + </manifest> + </archive> + </configuration> + <executions> + <execution> + <id>make-assembly</id> + <!-- bind to the packaging phase --> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + </execution> + </executions> + </plugin> + ``` + +- Créez à nouveau le paquet, puis exécutez le fichier `*-with-dependencies.jar`. + + ```bash + mvn clean package assembly:single + java -jar target/takima-agencymanagement-1.0-SNAPSHOT-jar-with-dependencies.jar + ``` + +??? bug "Le jar-with-dependencies n'est pas généré ?" + Il se peut que vous deviez supprimer la balise `pluginManagement` dans le fichier pom.xml. + +## Fichiers modifiés + +``` +src/main/java/io/takima/agencymanagement/App.java +src/main/resources/log4j.{properties|xml} +``` + +!!! warning "N'oubliez pas de `Commit` votre travail !" diff --git a/resources/docs/docs/chapters/tp2/entite.md b/resources/docs/docs/chapters/tp2/entite.md new file mode 100644 index 0000000000000000000000000000000000000000..7a7a6a76dd344b4b7ab01adf17f4e0e8994f8bdd --- /dev/null +++ b/resources/docs/docs/chapters/tp2/entite.md @@ -0,0 +1,193 @@ +# Les **entités** + +Dans cette section, nous allons revisiter les classes que nous avons écrites lors du jour 1. + +!!! info + Nous appelons généralement **Entité**, une classe de domaine qui est la représentation **classe** d'une **table** + dans la base de données. Dans ce cas, chaque ligne de ladite _table_ est une instance d'objet de ladite _classe_. + + **Correspondance entre les types SQL et Java :** + + | Types SQL | Types Java | + | :-------------------------------------------------------------- | :---------------- | + | `BIGSERIAL`: `BIGINT` auto-incrementé | `long` | + | `CHAR(n)` | `String` | + | `VARCHAR`: `CHAR(n)` avec une longueur variable | `String` | + | `DOUBLE PRECISION` | `double` | + | `INT` | `int` | + +## Builder pattern + +Partons du principe que, lorsque l'on crée un voyage, on ne connaît pas toutes les informations dès le départ. On voudrait donc pouvoir créer un objet voyage en se passant de certains attributs, pour les préciser dans un second temps. + +Pour créer un `Travel` avec uniquement un nom, nombre de places, aéroport de départ et d'arrivée, on doit appeler le +constructeur avec des valeurs nulles. + +```java +// Create +Travel travel = new Travel( + 1, + "Voyage vers les montagnes", + 20, + Airport.PARIS_CHARLES_DE_GAULLE, + Airport.TOKYO_HANEDA, + Instant.now(), + Instant.now().plusSeconds(86400), + "France", + 0.0, //null + null, //disocunts + null, //participants + null; //waitList +``` + +Le constructeur ci-dessus n'est pas très pratique, car il nécessite un grand nombre de paramètres dans un ordre très +spécifique. +Le [design pattern **Builder**](https://en.wikipedia.org/wiki/Builder_pattern){:target="_blank"} est une bonne solution pour contourner ce +problème. + +### Travel.Builder + +- Créez une nouvelle classe statique `Builder` à l'intérieur de la classe `Travel`. + +??? example "Travel.Builder" + + ```java + public class Travel { + // ... + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(Travel travel) { + return new Builder(travel); + } + + public static final class Builder { + private Long id; + private String name; + private Airport departureAirport; + private Airport arrivalAirport; + private Instant departureDate; + private Instant arrivalDate; + private String destination; + private int capacity; + private double price; + private List<Discount> discounts; + private Set<User> participants; + private Queue<User> waitingParticipants; + + public Builder() { + } + + public Builder(Travel travel) { + this.id = travel.id; + this.name = travel.name; + this.departureAirport = travel.departureAirport; + this.arrivalAirport = travel.arrivalAirport; + this.departureDate = travel.departureDate; + this.arrivalDate = travel.arrivalDate; + this.destination = travel.destination; + this.capacity = travel.capacity; + this.price = travel.price; + this.discounts = travel.discounts; + this.participants = travel.participants; + this.waitingParticipants = travel.waitingParticipants; + } + + public Builder id(Long id) { + this.id = id; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder departureAirport(Airport departureAirport) { + this.departureAirport = departureAirport; + return this; + } + + public Builder arrivalAirport(Airport arrivalAirport) { + this.arrivalAirport = arrivalAirport; + return this; + } + + public Builder departureDate(Instant departureDate) { + this.departureDate = departureDate; + return this; + } + + public Builder arrivalDate(Instant arrivalDate) { + this.arrivalDate = arrivalDate; + return this; + } + + public Builder destination(String destination) { + this.destination = destination; + return this; + } + + public Builder capacity(int capacity) { + this.capacity = capacity; + return this; + } + + public Builder price(double price) { + this.price = price; + return this; + } + + public Builder disocunts(List<Discount> discounts) { + this.discounts = discounts; + return this; + } + + public Builder participants(List<User> participants) { + this.participants = participants; + return this; + } + + public Builder waitingParticipants(List<User> waitingParticipants) { + this.waitingParticipants = waitingParticipants; + return this; + } + + public Travel build() { + return new Travel(id, name, departureAirport, arrivalAirport, departureDate, arrivalDate, + destination, capacity, price, discounts, participants, waitingParticipants); + } + } + } + ``` + +On pourra utiliser ce builder afin de créer des `Travel` comme suit : + +=== "Main.java" + ```java + Travel travel = Travel.builder() + .id(1) + .name("name") + .departureDate(Instant.now()) + .arrivalDate(Instant.now().plusSeconds(86400)) + .departureAirport(Airport.PARIS_CHARLES_DE_GAULLE) + .arrivalAirport(Airport.TOKYO_HANEDA) + .destination("destination") + .capacity(10) + .price(350.00) + .build(); + ``` + +!!! edit "À vous de jouer" + Maintenant, c'est à vous de réaliser le `User.Builder`. + +## Fichiers modifiés + +``` +src/main/java/io/takima/agencymanagement/App.java +src/main/java/io/takima/agencymanagement/model/Travel.java +src/main/java/io/takima/agencymanagement/model/User.java +``` + +!!! warning "N'oubliez pas de `Commit` votre travail !" diff --git a/resources/docs/docs/chapters/tp2/maven.md b/resources/docs/docs/chapters/tp2/maven.md new file mode 100644 index 0000000000000000000000000000000000000000..0b469fbfdfb399191f51595a9126ddbcfbff4002 --- /dev/null +++ b/resources/docs/docs/chapters/tp2/maven.md @@ -0,0 +1,216 @@ +# Maven + +Bienvenue dans le **Jour 2** de votre formation Java. + +Dans le jour précédent, nous avons vu comment créer un projet à partir de zéro. Cette méthode fonctionne pour les petits projets ou les exemples, mais elle présente certaines limites à plus grande échelle. Par exemple, si vous souhaitez utiliser une bibliothèque externe dans votre code, vous devez : + +* Rechercher la bibliothèque sur le web et télécharger le fichier `.jar` correspondant. +* Placer ce fichier `.jar` dans le dossier `lib/`. +* Configurer le `$PATH` ou le `--classpath` pour inclure cette bibliothèque. +* Oublier tout cela et ne plus jamais toucher à ce fichier. + +Inutile de dire que cette méthode est loin d'être pratique et qu'elle n'encourage pas les mises à jour fréquentes des dépendances. + +Dans ce chapitre, nous allons introduire l'utilisation d'un gestionnaire de paquets. Laissez-moi vous présenter +[**Maven**](https://maven.apache.org/guides/getting-started/){:target="_blank"} ! + +!!! info "Gestionnaire de paquets" + + - Il s'agit d'un outil permettant de télécharger et d'installer les dépendances requises (c'est-à-dire les bibliothèques externes). + - Les paquets sont connectés à un dépôt d'artefacts : un serveur externe à partir duquel les paquets sont téléchargés. + - Le dépôt par défaut de Maven est [Maven Repository](https://mvnrepository.com){:target="_blank"}. + - Les organisations configurent généralement leurs propres dépôts hébergés (par exemple : [Jfrog's Artifactory](https://jfrog.com/artifactory/){:target="_blank"}, [Sonatype Nexus](https://www.sonatype.com/products/nexus-repository){:target="_blank"}, [Gitlab Package Registry](https://docs.gitlab.com/ee/user/packages/maven_repository/){:target="_blank"}) afin de pouvoir publier leurs propres paquets. + - Java dispose de deux gestionnaires de paquets que vous pouvez utiliser : + - [_Maven_](https://maven.apache.org/){:target="_blank"} : le standard de facto + - [_Gradle_](https://gradle.org/){:target="_blank"} : un gestionnaire de paquets relativement récent, qui fonctionne avec les mêmes dépôts que Maven. Il est principalement adopté par la communauté Android et Kotlin. + +### Générez un nouveau projet Maven + +Un projet Java qui utilise Maven est appelé **artifact**. En tant que tel, il doit suivre une structure de fichier spécifique : +```bash +├── pom.xml # project descriptor. +├── src # sources root +│ ├── main +│ │ └── java # main sources +│ │ └── com.my-package.my-project +│ │ └── App.java +│ └── test +│ └── java # test sources +│ └── com +│ └── com.my-package.my-project +│ └── AppTest.java # Tests are suffixed with `Test` +└── target # compiled classes & artifacts + ├── classes + └── my-project-1.0-SNAPSHOT.jar +``` + +En plus de la gestion des dépendances, Maven vous aide également à créer une structure de projet générique. + +!!! edit "À vous de jouer" + - Lancez cette commande [`mvn archetype:generate`](https://maven.apache.org/guides/mini/guide-creating-archetypes.html){:target="_blank"} pour générer un projet qui suit les conventions des projets Maven. Nommez le projet `takima-agency` et définissez `io.takima.agencymanagement` pour le group id. + + ```bash + mvn -B archetype:generate \ + -DarchetypeVersion=1.4 \ + -DgroupId=io.takima.agencymanagement \ + -DartifactId=takima-agencymanagement + ``` + +!!! info Gestion des dépendances + + - Un **artifact** est un morceau de code, généralement compilé, qui **peut être utilisé comme dépendance** par d'autres artifacts. Par conséquent, la combinaison du _name_ et du _groupId_ d'un artifact doit être unique. + - La commande `mvn archetype:generate` est utilisée pour créer un nouveau projet, basé sur un _archetype_ (un modèle). Sans spécifier d'_archetype_, il utilisera `maven-archetype-quickstart`, qui est un _Hello World_. + + +Quand on crée la structure du projet, Maven génère aussi un fichier de configuration appelé `pom.xml`, pour **Project Object Model**. +Gardez-le propre, bien structuré et commenté. Référez-vous à [la documentation officielle](https://maven.apache.org/guides/getting-started/){:target="_blank"} pour plus de détails. + +Le fichier `pom.xml` est utilisé pour lister toutes les dépendances dont le projet a besoin. + +Pour l'instant, le projet est livré avec une seule dépendance (très ancienne) [_JUnit_](https://junit.org/junit4/){:target="_blank"}. Il s'agit d'un framework Java populaire permettant d'écrire des tests automatiques. + +??? abstract "pom.xml" + ```xml + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <groupId>io.takima.agencymanagement</groupId> + <artifactId>takima-agency</artifactId> + <packaging>jar</packaging> + <version>1.0-SNAPSHOT</version> + <name>takima-agency</name> + <url>http://maven.apache.org</url> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <maven.compiler.source>1.7</maven.compiler.source> + <maven.compiler.target>1.7</maven.compiler.target> + </properties> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.11</version> + <scope>test</scope> + </dependency> + </dependencies> + + </project> + ``` + +!!! info "Maven scopes" + + - `scope` est utilisé pour définir la visibilité d'une dépendance dans un projet Maven. Il y a **4 scopes importants que vous devez connaître** : [`compile`, `runtime`, `provided` et `test`](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#dependency-scope){:target="_blank"}. + - `<scope>test</scope>` indique que cette dépendance est disponible seulement pendant la phase de test et pas au runtime. + +### Retirez JUnit 4 + +Comme nous n'allons pas faire de test vous pouvez retirer la dépendance JUnit ainsi que le fichier `src/test`. + +## Définissez la version de Java + +Maven gère également la compilation de votre projet. Cela signifie qu'au lieu d'appeler la commande `javac` vous-même, vous pouvez appeler `mvn compile` et Maven appellera la commande `javac` pour vous. + +La version de Java à utiliser est configurée par quelques propriétés : + +* `maven.compiler.source`: Utiliser les fonctionnalités de cette version de Java. + * Cette propriété indique quelle valeur doit être définie pour l'option `javac -source`. Elle spécifie la version de Java que nous utilisons pour développer. + +* `maven.compiler.target`: Compiler un binaire en bytecode compatible avec cette version. + * Cette propriété indique quelle valeur doit être définie pour l'option `javac -target`. Elle spécifie la version minimale de la JVM avec laquelle le bytecode généré doit être compatible. + +!!! edit "À vous de jouer" + + - Définissez une `property` appelée `java.version` et mettez la dernière version de Java _LTS_ (eg: `21`). + - Utilisez `maven.compiler.source` et `maven.compiler.target` pour définir cette version. + + ```xml + <!-- ... --> + <properties> + <java.version>17</java.version> + <maven.compiler.source>${java.version}</maven.compiler.source> + <maven.compiler.target>${java.version}</maven.compiler.target> + </properties> + <!-- ... --> + ``` + +## Compilez un `jar` avec Maven + +Maven definit un [cycle de vie de construction](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html){:target="_blank"} pour accomplir différentes tâches pour le projet : +_compile_, _package_, _test_, _deploy_, ... + +- Lancez la commande `clean package` pour builder et packager le projet : + ```bash + mvn clean package + ``` +Cette commande compile l'artifact dans le répertoire `target/`. Il doit contenir des fichiers `.class` , ainsi qu'un fichier `.jar` qui les packages tous. + +!!! info "jar" + - Un fichier `.jar` est comme un `zip` contenant les fichiers Java compilés. Il contient également un fichier exécutable. + - Rappelez-vous de mettre le `target/` dans votre `.gitignore` car il contient des fichiers binaires générés, qui **ne doivent pas être commit**. + +- Essayez de lancer le `jar` avec la commande suivante : + ```bash + java -jar target/takima-agencymanagement-1.0-SNAPSHOT.jar + ``` + +!!! failure + ==**No main manifest attribute in target/takima-agencymanagement-1.0-SNAPSHOT.jar**== + + Cette erreur montre que Java **ne sait pas quel fichier est exécutable dans le `jar`**. En fait il vous demande de lui indiquer **quelle classe définit la méthode `public void main()`**. + +### Fichier exécutable `.jar` + +- Modifiez le `pom.xml` et ajoutez ces quelques lignes : + + ```xml + <build> + <plugins> + ... + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifest> + <mainClass>io.takima.agencymanagement.App</mainClass> + </manifest> + </archive> + </configuration> + </plugin> + </plugins> + </build> + ``` + +- Packagez le `jar` à nouveau et décompressez l'archive avec un gestionnaire d'archives pour voir les fichiers qu'elle contient. + + ```bash + mvn clean package + cd target/ + unzip takima-agencymanagement-1.0-SNAPSHOT.jar + ``` + + Assurez-vous que l'archive contienne un `MANIFEST.MF` qui pointe vers votre Main.class. + + ```bash + Archive: takima-agencymanagement-1.0-SNAPSHOT.jar + creating: META-INF/ + inflating: META-INF/MANIFEST.MF + ``` +- Lancez le `jar` à nouveau, ça devrait fonctionner ! + + ```bash + java -jar takima-agencymanagement-1.0-SNAPSHOT.jar + ``` + +## Fichiers modifiés + +``` +pom.xml +src/ +.gitignore +``` + +!!! warning "N'oubliez pas de `Commit` votre travail !" diff --git a/resources/docs/docs/chapters/tp2/release.md b/resources/docs/docs/chapters/tp2/release.md new file mode 100644 index 0000000000000000000000000000000000000000..fb065b95623c867520e14e3d30c6de843229b8c6 --- /dev/null +++ b/resources/docs/docs/chapters/tp2/release.md @@ -0,0 +1,59 @@ +# La release + +Maintenant, votre application offre un point d'entrée _CLI_ qui vous permet de lister les voyages disponibles ainsi +que les voyageurs inscrits. Le moment est venu de la publier. + +Lorsque vous publiez un artefact Java, vous devez définir un nouveau numéro de version et lui donner un _Git tag_ spécifique pour identifier facilement cette version. +Vous savez quoi ? Maven dispose d'un outil pour faire cela. + +## [**Maven Release plugin**](https://maven.apache.org/maven-release/maven-release-plugin/){:target="_blank"} + +- Ajoutez le [**Maven Release plugin**](https://maven.apache.org/maven-release/maven-release-plugin/){:target="_blank"} à votre `pom.xml`. + +```xml +<project> +<scm> + <developerConnection>scm:git:git@[YOUR_REPO.git]</developerConnection> +</scm> + +<build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-release-plugin</artifactId> + <version>2.5.3</version> + </plugin> + </plugins> + <!-- ... --> +</build> + <!-- ... --> +<!-- ... --> +</project> +``` + +- Lancez `mvn release:prepare`. + +!!! bug "Cependant, veuillez effectuer un `add`, un `commit` et un `push` de votre code avant cela." + +```sh +mvn release:prepare \ + -Dtag=1.0.0 \ + -DreleaseVersion=1.0.0 \ + -DdevelopmentVersion=2.0-SNAPSHOT +``` + +Avec cette commande, Maven **met à jour la `<version>`** dans le `pom.xml` à la version spécifiée, **crée le tag Git** et **pousse les changements**. +!!! info "Une bonne pratique pour définir le numéro de version est d'utiliser [SEMVER](https://semver.org/){:target="_blank"} (Semantic Versioning)." + +- Lancez la commande `mvn release:clean` pour préparer la prochaine version. + ```sh + mvn release:clean + ``` + +!!! warning "N'oubliez pas de `Commit` votre travail !" + +# Récapitulatif + +Félicitations pour avoir terminé ce TP qui a abordé plusieurs aspects essentiels du développement logiciel, +notamment Maven, JDBC et la gestion des releases avec ce dernier. Cependant, préparez-vous à plonger dans l'univers +de Spring ! diff --git a/resources/docs/docs/chapters/tp2/spring.md b/resources/docs/docs/chapters/tp2/spring.md new file mode 100644 index 0000000000000000000000000000000000000000..272c44450c9821dfcf50a466e60cb1620d62bc11 --- /dev/null +++ b/resources/docs/docs/chapters/tp2/spring.md @@ -0,0 +1,275 @@ +## <span style="color:orange"> **Optionnel** </span> + +!!! info "Spring ?" + - Vous n'avez jamais entendu parler de spring ? En quelques mots, voici ce qu'il faut retenir : + - C'est un framework + - C'est une alternative à JakartaEE (anciennement connu sous le nom de JEE) + - Il offre DI (dependency injection) et IoC (Inversion of control) + - Il exploite l'AOP (Aspect Oriented Programming) pour améliorer vos POJO (objets Java ordinaires). + - Dans cette étape, vous allez ajouter le framework Spring-Boot à une base de code Java existante. + +!!! info "Spring vs Spring Boot" + - Il ne faut pas confondre Spring et Spring Boot. + - Spring est le framework, celui qui offre DI, IoC, AOP etc... Il est puissant et plein de fonctionnalités et de modules, mais nécessite beaucoup de - configuration pour le faire fonctionner. + - Spring Boot peut être considéré comme une extension de Spring. Il est construit au-dessus de Spring et fournit des fonctionnalités très pratiques : autoconfiguration, configuration par défaut, serveur embarqué, cohérence de la version des dépendances, starters, etc. + +## Générer un nouveau projet + +### Le pom + +Allez sur [start.spring.io](https://start.spring.io/#!type=maven-project&language=java&platformVersion=3.2.2&packaging=jar&jvmVersion=21&groupId=io.takima.agencymanagement&artifactId=agencymanagement&name=agencymanagement&description=Takima%20Store&packageName=io.takima.agencymanagement&dependencies=postgresql,jdbc,web), et download le projet. Le projet a été préconfiguré avec les settings suivants, n'hésitez pas à rajouter des dépendances pour tester si vous en avez besoin: + +**group**: + +- `io.takima.agencymanagement` + +**artifact**: + +- `agencymanagement` + +**dependencies**: + +- `postgreSQL` +- `JDBC` +- `Web` + +### Code source + +Copier votre code source dans le nouveau projet (Attention il faut que vous mixiez la classe main et vous allez peut-être avoir quelques problèmes d'imports) + +!!! info "Pom Parent" + - Comme vous avez pu le remarquer, `<artifactId>postgresql</artifactId>` n'a pas de numéro de version spécifié, + c'est parce que `spring-boot-starter-parent` fournit et gère toutes les versions des dépendances. + - Si, pour une raison quelconque, vous souhaitez modifier la version d'une dépendance, vous devez définir la balise `dependencyManagement`, puis à + l'intérieur de celle-ci, vous devez spécifier la dépendance pour laquelle vous souhaitez une version spécifique et la version que vous souhaitez. + - Mais réfléchissez-y à deux fois avant de le faire. Spring-Boot assure (la plupart du temps) la compatibilité entre les versions des dépendances. Il peut être préférable de mettre à jour la version de Spring Boot plutôt que celle d'une dépendance spécifique. + - Cependant, cela peut permettre de corriger rapidement un bug dans une dépendance sans attendre la nouvelle version de Spring Boot. + + +!!! info "HicariCP" + - Si vous regardez attentivement le fichier `pom.xml`, vous verrez que la dépendance `hikariCP` n'est pas spécifiée, + c'est parce qu'elle est incluse par défaut par `spring-boot-starter-jdbc`, puisqu'elle est utilisée pour le **connection pooling** par défaut dans `Spring Boot 2` + + + +!!! info "Starter Spring Boot" + - Un spring boot starter est une dépendance regroupant un ou plusieurs modules Spring, et les dépendances externes nécessaires avec une version définie. - Comme son nom l'indique, il est utilisé pour commencer à utiliser rapidement une fonctionnalité. + - Prenez un moment pour explorer le contenu de `spring-boot-starter-parent` et de certains spring-boot-starters comme `spring-boot-starter-jdbc`. + +- Lancez mvn dependency:tree pour afficher toutes les dépendances incluses dans votre installation Spring. + + +!!! info "Tips Intellij" + - Notez qu'IntelliJ vous permet de démarrer un nouveau projet avec Spring Initializer directement (en évitant de le générer depuis le site web puis de le télécharger) + + +### La db + +Compilez et exécutez votre application : + +!!! failure + =**java.lang.IllegalStateException: Failed to load ApplicationContext**= + Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured. + +Si tu ouvres ton `pom.xml`, tu verras que ton projet vient avec une `spring-boot-starter-jdbc` dépendance. +Celle-ci dit à Spring automatiquement de configurer une connection de db, mais Spring n'arrive pas à le faire car on lui a donné aucune info sur la db. + +Une petite recherche sur internet te dirait que Spring (plus spécifiquement Spring Boot) a besoin d'une configuration (soit dans le fichier `application.properties`, soit dans le fichier `application.yml`) , pour configurer la connexion à la base de données: + + + +```yml +spring: + datasource: + url: jdbc:postgresql://localhost:5432/agencymanagement_db + username: madmin + password: madmin +``` + +```properties +spring.datasource.url=jdbc:postgresql://localhost:5432/agencymanagement_db +spring.datasource.username=madmin +spring.datasource.password=madmin +``` + +!!! info "doc Spring" + -Il y a plusieurs façons de définir les propriétés de Spring. + Les définir dans votre fichier `application.properties`/`application.yml` est la plus standard, mais il en existe d'autres ! + Consultez [la documentation](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config) pour en savoir plus. + +Mettez à jour vos noms de propriétés, exécutez l'application à nouveau, et si votre docker `agencymanagement_db` est en cours d'exécution, vous devriez obtenir ce qui suit : + +```bash +Tomcat started on port(s): 8080 (http) with context path '' +Started MaStoreApplication in 3.248 seconds (JVM running for 4.51) +``` + +### Le logger + +Spring boot est livré avec l'API de logging SLF4j (`spring-boot-starter-web` => `spring-boot-starter` => `spring-boot-starter-logging` ). +L'implémentation par défaut est **Logback** (voir : `spring-boot-starter-logging-X.X.X.RELEASE.pom:42`). +Logback est le successeur de **log4j**. Il a été conçu par le même développeur, avec la performance à l'esprit. +De plus, il offre une **intégration native avec slf4j** et ne nécessite pas de connecteur. + +Profitons de cette occasion pour passer de **log4j** à **logback**. +Si vous le souhaitez, logback dispose d'un [traducteur](https://logback.qos.ch/translator/) pour migrer votre ancien fichier `log4j.properties` vers le fichier `logback.xml` requis. + +Cependant, si vous ne voulez pas écrire du XML, Spring Boot vous permet de configurer le logger à travers le fichier `properties` : + + +```yaml +logging: + level: + root: info + pattern: + console: '%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger.%M - %msg%n' +``` + +```properties +logging.level.root=info +logging.pattern.console=%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger.%M - %msg%n +``` + +!!! info tips + - Spring peut configurer logback pour utiliser les couleurs avec cette option:`spring.output.ansi.enabled=always` + - N'oubliez pas de supprimer l'ancien fichier `log4j.properties`, car il n'est plus utile + - Ecrivez le `application.properties` comme ça `application.yml`, le yaml est plus lisible + + +### L'injection de dépendances + +Que le fun commence ! + +L'ensemble du framework Spring est construit autour du concept d'injection de dépendances (Dependency Injection). +Dans cette étape, vous découvrirez l'injection de dépendance en configurant la manière d'injecter le DataSource. + +DataSource est un composant JPA qui contient les connexions à la base de données. + +Lorsque vous avez introduit le pool de connexion DB dans l'étape Java précédente, vous avez probablement créé une sorte de singleton ConnectionManager pour configurer HikariCP et fournir un objet de connexion. +En fait, HikariCP vous donne accès à la connexion par l'intermédiaire d'un HikariDataSource : + +```bash +DataSourceManager() { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(url); + // ... + this.datasource = new HikariDataSource(config); +} +Connection getConnection() { + return this.datasource.getConnection(); +} +``` +Tous vos DAO ont besoin de cette source de données et, plutôt que de coder en dur le lien entre chaque DAO et la source de données, injectons-le ! + +!!! info + L'instanciation d'objets directement dans la classe qui les requiert n'est pas flexible, car elle engage la classe dans un ensemble particulier d'objets et rend impossible la modification ultérieure de leur instanciation, indépendamment de la classe. + Cela empêche la classe d'être réutilisable si d'autres objets sont nécessaires, et rend difficile le test de la classe, car les objets réels ne peuvent pas + être remplacés par des objets fictifs. C'est là que l'injection de dépendances devient pratique. + +Créez un nouveau fichier ApplicationContext.java, qui utilise les annotations suivantes : + +@Configuration +@Bean +@Primary + +Le fichier ApplicationContext.java est utilisé par Spring pour définir le contexte. +Considérez le contexte comme une classe annotée avec @Configuration, que Spring utilise au démarrage de l'application, pour instancier vos services, +vos singletons, tout configurer et tout relier. + +!!! info + Il peut y avoir autant de classes @Configuration que vous le souhaitez. + Initialement, Spring s'appuyait sur des fichiers XML pour configurer le contexte (i.e. : context.xml). Depuis Spring 4.X, nous préférons utiliser la configuration basée sur JavaConfig, qui s'appuie largement sur des annotations (comme @Bean ou @Configuration) plutôt que sur l'approche XML. + Consultez cette [excellent article on Baeldung](https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring) si vous voulez en savoir plus! + + +Lorsque le fichier ci-dessus est lu par Spring, le DataSource est alors considéré comme un bean et peut être @Autowired à vos DAOs, leur donnant accès à une Connection. Si vous avez besoin de changer votre source de données, vous n'avez qu'à changer votre bean, aucune modification de DAO n'est nécessaire. N'est-ce pas génial ? + +Allez y créer votre class ApplicationContext avec un bean datasource ! + +??? tip + + ```java + @Configuration + public class ApplicationContext { + + @Bean + @Primary + DataSource datasource() { + return DataSourceManager.INSTANCE.getDatasource(); + } + } + ``` + +## Spring Autowire + +> Consultez cet [article de Baeldung sur spring autowire](https://www.baeldung.com/spring-autowire) si vous voulez en savoir plus. + +Comme nous l'avons vu précédemment, le contexte consiste essentiellement à fournir des beans de certains types à l'ensemble de l'application. + +Au cours de cette étape, vous allez transformer tous vos singletons en **composants** gérés par Spring et **Autowired** les uns avec les autres. + +!!! info + Une classe annotée avec `@Component` se transformera en Bean une fois instanciée et pourra alors être injectée. + +- Dans votre DAO, injectez et utilisez la source de données que nous avons configurée plus tôt: + +```bash +class SomeDao { + @Autowired. + DataSource ds; // Spring will inject a DataSource instance HERE. +} +``` + +L'annotation [@Autowired](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Autowired.html) indique à Spring d'injecter le bean `DatasSource` que nous avons configuré plus tôt. + +!!! info + - Il est recommandé d'utiliser `@Autowired` sur les **constructeurs** plutôt que sur les **champs**, car ils vous permettent d'implémenter des composants d'application comme des _objets immuables_ et de vous assurer que les dépendances requises ne sont pas _nulles_. + - Les injections de setter ou de champ ne doivent être utilisées que pour les dépendances optionnelles. + - Vous pouvez en savoir plus à ce sujet [ici](https://docs.spring.io/spring/docs/5.0.x/spring-framework-reference/core.html#beans-constructor-vs-setter-injection) + - Le saviez-vous ? Depuis Spring 4.3, les classes annotées `@Component` qui n'ont qu'un seul constructeur ne doivent pas nécessairement définir l'annotation `@Autowired` pour bénéficier de la DI. + +- Ajoutez l'annotation `@Repository` à vos classes DAO. + Cela les transforme en **Composants** gérés par Spring. + +!!! info + - Les deux `@Component` et `@Repository` marquent un bean comme un composant géré par Spring, mais `@Repository` attrape aussi les exceptions spécifiques à la persistance et les relance comme l'une des exceptions unifiées non contrôlées de Spring. + + - Si une classe n'est pas un composant, elle n'est probablement pas gérée par Spring. N'utilisez jamais `@Autowired` à l'intérieur d'une telle classe, car rien ne sera injecté. + +- Annotez vos classes de service avec l'annotation @Service. + +!!! info + Tous vos composants doivent avoir un constructeur public et trivial. + Par trivial, nous entendons un constructeur sans argument ou avec des arguments qui sont autowired. + +- Modifiez vos services pour `autowire` les composants requis. + +- Vous n'aurez pas besoin de `autowire` quoi que ce soit dans votre main dans le futur, parce que votre application gérera les requêtes web. + Cependant, pour le moment, vous avez toujours besoin d'exécuter votre application CLI, et pour ce faire, vous pouvez écrire un constructeur dans votre main et l'injecter avec vos dépendances. + + +```bash +@SpringBootApplication +public class MaStoreApplication { + private final SellerService sellerService; + private final ArticleService articleService; + @Autowired + public MaStoreApplication(SellerService sellerService, ArticleService articleService) { + this.sellerService = sellerService; + this.articleService = articleService; + // Put your old main code here +} + public static void main(String[] args) { + SpringApplication.run(MaStoreApplication.class, args); + } +} +``` + +!!! info + Spring instanciera la classe `@SpringBootApplication` en appelant le constructeur. + Vous pouvez également externaliser votre code dans une autre classe et l'annoter avec `@Service` pour réduire la quantité de code dans le constructeur. + +- Compilez et testez votre code ; tout devrait bien se passer ! + +Comme vous le voyez, il n'y a rien comme `new SellerDaoImpl()` dans votre code. +C'est à Spring d'appeler les constructeurs du composant et d'instancier l'objet. \ No newline at end of file diff --git a/resources/docs/docs/chapters/tp3/launch_hackaton.md b/resources/docs/docs/chapters/tp3/launch_hackaton.md new file mode 100644 index 0000000000000000000000000000000000000000..72c80853dc138b81e0365a2497605d27ffb50414 --- /dev/null +++ b/resources/docs/docs/chapters/tp3/launch_hackaton.md @@ -0,0 +1,51 @@ +# Hackaton + +## **Au programme** + +Vous possédez maintenant toutes les compétences pour créer votre propre applation java. C’est un bon début ! +Mais quoi de mieux que la pratique pour appliquer tout ça ! + +Le but de ces trois derniers jours et de créer une application de bout en bout Java Spring Angular. +On a confiance en vous mais on sait que c'est un projet ambitieux. + +Pour vous aider on vous fournit [un skelette d'application backend et frontend](https://gitlab.takima.io/formation-dev-web/squeleton-web-app). +N'hésitez surtout pas à le consulter et à vous en inspirez grandement. + +C'est à vous de jouer : Formez des groupes de 3. + +Vous avez deux choix : +- Soit vous partez juste du skelette et vous nous créez votre propre application +- Soit le front vous donne des boutons et vous développez l'api de notre super TakiCine + +Si vous partez sur l'option TakiCine, [voici le lien du repo front](https://gitlab.takima.io/formation-dev-web/allocine-exemple-webb-app-front) et [un open api deployé](https://api.allocine.takima.dev/swagger-ui/index.html#/) pour vous aider un peu ! + +Si vous partez sur l'option libre, [voici des exemples de projet](https://gitlab.takima.io/formation-dev-web/squeleton-web-app/-/tree/main/projects-ideas?ref_type=heads). + +Vous devez aussi préparer une petite présentation de votre projet pour le dernier jour ! + +Que la force soit avec vous + +!!! info "Spring ?" + - Vous n'avez jamais entendu parler de spring ? En quelques mots, voici ce qu'il faut retenir : + - C'est un framework + - C'est une alternative à JakartaEE (anciennement connu sous le nom de JEE) + - Il offre DI (dependency injection) et IoC (Inversion of control) + - Il exploite l'AOP (Aspect Oriented Programming) pour améliorer vos POJO (objets Java ordinaires). + - Dans cette étape, vous allez ajouter le framework Spring-Boot à une base de code Java existante. + +!!! info "Spring vs Spring Boot" + - Il ne faut pas confondre Spring et Spring Boot. + - Spring est le framework, celui qui offre DI, IoC, AOP etc... Il est puissant et plein de fonctionnalités et de modules, mais nécessite beaucoup de - configuration pour le faire fonctionner. + - Spring Boot peut être considéré comme une extension de Spring. Il est construit au-dessus de Spring et fournit des fonctionnalités très pratiques : autoconfiguration, configuration par défaut, serveur embarqué, cohérence de la version des dépendances, starters, etc. + + +!!! info "Hibernate" + - Il y a deux frameworks dans Java EE que tout développeur Java doit connaître : Le premier est Spring boot. Son meilleur ami est la bibliothèque ORM populaire Hibernate, et c'est le sujet de ce jalon. + + - Qu'est-ce qu'un ORM ? + + - Vous savez déjà comment écrire de simples requêtes SQL à partir de votre application Java, mais vous avez certainement constaté que c'était assez fastidieux. + + - Imaginez maintenant que vous devez interroger plusieurs tables, et que de nombreuses tables se réfèrent les unes aux autres. Il faut les récupérer toutes, faire toutes les correspondances encore et encore... C'est beaucoup trop de code si nous écrivons tout cela avec le bon vieux PreparedStatement. + + - Hibernate permet de simplifier tout ça ! \ No newline at end of file diff --git a/resources/docs/docs/cheatsheets/tp1/command_line.md b/resources/docs/docs/cheatsheets/tp1/command_line.md new file mode 100644 index 0000000000000000000000000000000000000000..f26e6c97688c06356c5df6a2fa3ec3757b03f10a --- /dev/null +++ b/resources/docs/docs/cheatsheets/tp1/command_line.md @@ -0,0 +1,17 @@ +## **Compilation et exécution du code** + +!!! info "Compilation avec javac" + `javac` est le compilateur officiel de Java. Il transforme le code source Java en bytecode, nécessaire pour l'exécution par la machine virtuelle Java (JVM). +Utilisez la commande suivante dans votre terminal pour compiler votre projet : +``` +javac src/*.java -d target +``` + +Cette commande compile tous les fichiers Java dans le dossier `src` et place les fichiers de sortie dans le dossier `target`. + +Une fois le code compilé, vous pouvez exécuter votre application avec la commande suivante : + +``` +java -cp target src.Main +``` +Cette commande exécute la classe `Main` en utilisant les fichiers compilés dans le dossier `target`. diff --git a/resources/docs/docs/cheatsheets/tp2/docker_install.md b/resources/docs/docs/cheatsheets/tp2/docker_install.md new file mode 100644 index 0000000000000000000000000000000000000000..bc5138dd049aa7c650b3c21f86dc41174bdd645b --- /dev/null +++ b/resources/docs/docs/cheatsheets/tp2/docker_install.md @@ -0,0 +1,51 @@ +## <i class="fa-solid fa-download"></i> Install _Docker Engine_ + +> You can skip this part if you already have Docker installed. + +:::spoiler Linux installation +### <i class="fab fa-linux"></i> Linux installation + +> **warning** +> +> As of 2022, **Docker Desktop** now requires a paid subscription for commercial usage in large companies. +> +> - **Docker Desktop** is a convenience utility that packages the _Docker Engine_ plus a _Graphical User Interface_ (_GUI_). +> - **Docker Engine** is a free _Command Line Tool_ (_CLI_) to create and manage **Docker objects**, such as **images**, **containers**, **networks** and **volumes**. +> +> - If you use _Mac_ or _Windows_, you have to use _Docker Desktop_ to gain compatibility with _Docker Engine_. + +- [Install _Docker Engine_](https://docs.docker.com/engine/install/){:target="_blank"} for Linux: + ```sh + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh get-docker.sh + ``` +- Add your user to the docker group: + ```sh + sudo usermod -aG docker $USER + ``` + +> **warning** +> +> - As a Linux user, you can use _Docker Engine_ only, even if the official documentation pushed toward the adoption of [Docker Desktop for Linux](https://docs.docker.com/desktop/linux/install/){:target="_blank"}. + +::: + +:::spoiler MacOs installation +### <i class="fab fa-apple"></i> MacOs installation + +> **warning** Docker on _MacOS_ is packaged into _Docker Desktop_, that requires now a paid subscription for commercial usage in large companies. + +[Install Docker Desktop on MacOs](https://docs.docker.com/desktop/mac/install/){:target="_blank"} +::: + +:::spoiler Windows installation +### <i class="fab fa-windows"></i> Windows installation + + +> **warning** Docker on _Windows_ is packaged into _Docker Desktop_, that requires now a paid subscription for commercial usage in large companies. + +> **warning** Docker Engine creates isolation with the help of unix system primitives and kernel features (eg: [CGroups](https://en.wikipedia.org/wiki/Cgroups){:target="_blank"}, [Chroot](https://en.wikipedia.org/wiki/Chroot){:target="_blank"}, ...). As a result, it cannot run natively on any non-unix based system. +> Docker works around this issue using virtualization with [`Hyper-V`](https://en.wikipedia.org/wiki/Hyper-V){:target="_blank"} or [`WSL2`](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux){:target="_blank"}, but some features will not work the same (eg: [_Bind mount volumes_](https://docs.docker.com/desktop/windows/troubleshoot/#path-conversion-on-windows){:target="_blank"}) + +[Install Docker Desktop on Windows](https://docs.docker.com/desktop/windows/install/){:target="_blank"} +::: diff --git a/resources/docs/docs/downloads/dao.zip b/resources/docs/docs/downloads/dao.zip new file mode 100644 index 0000000000000000000000000000000000000000..7c85b3e7d5e26806ba692edc112f74a11604e473 Binary files /dev/null and b/resources/docs/docs/downloads/dao.zip differ diff --git a/resources/docs/docs/downloads/db-init.zip b/resources/docs/docs/downloads/db-init.zip new file mode 100644 index 0000000000000000000000000000000000000000..df1ccc811a690ce6e285de3355a75914e609d513 Binary files /dev/null and b/resources/docs/docs/downloads/db-init.zip differ diff --git a/resources/docs/docs/downloads/docker-compose-db.zip b/resources/docs/docs/downloads/docker-compose-db.zip new file mode 100644 index 0000000000000000000000000000000000000000..d4908786b21701a1bbf2e2a53cc15bb143cb9dd7 Binary files /dev/null and b/resources/docs/docs/downloads/docker-compose-db.zip differ diff --git a/resources/docs/docs/index.md b/resources/docs/docs/index.md new file mode 100644 index 0000000000000000000000000000000000000000..a534ac436489da074ebf9a9bf2e227cda3e6755c --- /dev/null +++ b/resources/docs/docs/index.md @@ -0,0 +1,9 @@ +# **Java One** + +### Sessions pratiques + +[Fondamentaux Java et API ](chapters/tp1/index.md) + +[Maven, Bases de Données, Spring](chapters/tp2/maven.md) + +[Hackaton](chapters/tp3/launch_hackaton.md) \ No newline at end of file diff --git a/resources/docs/mkdocs.yml b/resources/docs/mkdocs.yml new file mode 100644 index 0000000000000000000000000000000000000000..43833068abe8fd7be6c12a04f748f3359126019c --- /dev/null +++ b/resources/docs/mkdocs.yml @@ -0,0 +1,52 @@ +site_name: Java One Formation - Application de gestion des voyages +nav: + - Sessions: index.md + - Jour 1: + - "Briefing": chapters/tp1/index.md + - "Prérequis": chapters/tp1/prerequisites.md + - "Création du projet": chapters/tp1/project_creation.md + - "POO ": chapters/tp1/poo.md + - "Collection ": chapters/tp1/api_collection.md + - "API Map": chapters/tp1/api_map.md + - "API Date": chapters/tp1/api_date.md + - "API Stream": chapters/tp1/api_stream.md + - Jour 2: + - "Maven": chapters/tp2/maven.md + - "Dépendances": chapters/tp2/dependances.md + - "Entité": chapters/tp2/entite.md + - "Database": chapters/tp2/database.md + - "Release": chapters/tp2/release.md + - "Spring": chapters/tp2/spring.md + - Jour 3: + - "Hackaton": chapters/tp3/launch_hackaton.md + - Cheatsheet: + - "Compilation": cheatsheets/tp1/command_line.md + +theme: + name: "material" + logo: assets/logo.png + palette: + primary: pink + features: + - navigation.tabs + - navigation.instant + theme: + icon: + file: fontawesome/solid/file + +markdown_extensions: + - markdown.extensions.admonition + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.details + - pymdownx.mark + - pymdownx.tabbed: + alternate_style: true + - attr_list + - tables + +extra_dirs: + - downloads \ No newline at end of file diff --git a/resources/docs/requirements b/resources/docs/requirements new file mode 100644 index 0000000000000000000000000000000000000000..0b6ff60c03bc3055786235ad0e589a4a7f4022bb --- /dev/null +++ b/resources/docs/requirements @@ -0,0 +1,3 @@ +mkdocs==1.2.4 +mkdocs-material==8.1.1 +pymdown-extensions==9.4 diff --git a/resources/docs/run.sh b/resources/docs/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..a8c72c73d8e73f9fe44a74320b252048a9b539ad --- /dev/null +++ b/resources/docs/run.sh @@ -0,0 +1,5 @@ + +docker build . -t java-one-lab + +echo "Building done" +docker run -p 8000:8000 java-one-lab \ No newline at end of file diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/Main.java b/resources/solution/day-1/src/io/takima/agencymanagement/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..cfdacf88418be5c2404e2ffae74ba5291d8ec6dc --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/Main.java @@ -0,0 +1,166 @@ +package io.takima.agencymanagement; + +import io.takima.agencymanagement.model.Airport; +import io.takima.agencymanagement.model.Discount; +import io.takima.agencymanagement.model.RestaurentOffer; +import io.takima.agencymanagement.model.PercentDiscount; +import io.takima.agencymanagement.model.Travel; +import io.takima.agencymanagement.model.User; +import io.takima.agencymanagement.service.TravelService; +import io.takima.agencymanagement.utils.AirportManager; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +public class Main { + + public static void main(String[] args) { + + TravelService travelService = new TravelService(); + //Update Main + System.out.printf("Agency App \n"); + + User user1 = new User(0, "John", "Doe", "john.doe@gmail.com"); + User user2 = new User(1, "John", "Doe", "john.doe@gmail.com"); + User user3 = new User(2, "John", "Doe", "john.doe@gmail.com"); + User user4 = new User(3, "John", "Doe", "john.doe@gmail.com"); + User user5 = new User(4, "John", "Doe", "john.doe@gmail.com"); + User user6 = new User(5, "Mat", "Doe", "mat.doe@gmail.com"); + + //Add 50% discount + List<Discount> discounts = new ArrayList<>(); + discounts.add(new PercentDiscount(50)); + + //Add participant set + Set<User> users = Set.of(user1, user2, user3, user4, user5); + Set<User> participants = new HashSet<>(users); + + // Bug without overriding equals and hashCode + User sameUser1 = new User(0, "Same", "User", "sameuser@gmail.com"); + User sameUser2 = new User(0, "Same", "User", "sameuser@gmail.com"); + + Set<User> sameUsers = new HashSet<>(); + try { + sameUsers = Set.of(sameUser1, sameUser2); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + Travel travelWithBug = new Travel(0L, null, Instant.now(), Instant.now().plusSeconds(1), + null, null, null, 0, 0, null, + sameUsers, null); + + System.out.println(travelWithBug.getParticipants()); + + //Add waitList + Queue<User> waitList = new ArrayDeque<>(); + waitList.add(user6); + + // Log RestaurentOffer details + RestaurentOffer offer = new RestaurentOffer( + 0L, + 4, + "description", + 2, + Instant.now().plusSeconds(10), + 2, + Instant.now() + ); + System.out.println(offer.getDetails()); + + //Create firstTravel + Travel firstTravel = new Travel(0L, "to Japan", Instant.now(), Instant.now().plusSeconds(86400), Airport.PARIS_CHARLES_DE_GAULLE, Airport.TOKYO_HANEDA, "Tokyo", 5, 2000, discounts, participants, waitList); + + //Apply discount + applyDiscounts(firstTravel); + + //Log user information + System.out.println(user1); + //Log travel information + System.out.println(firstTravel); + + //Subscribe user using TravelService and get an exception + try { + travelService.subscribe(user1, firstTravel); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + + System.out.println("Utilisateurs en file d'attente : " + firstTravel.getWaitList().element()); + + //Unsubscribe a participant then check the first one in the waitList is subscribed + travelService.unsubscribe(user1, firstTravel); + + for (User participant : firstTravel.getParticipants()) { + System.out.println(participant); + } + + //Log CDG airport timeZone + System.out.println(AirportManager.getTimeZone(Airport.PARIS_CHARLES_DE_GAULLE)); + + //Use computeTravelDuration + String dateStr = "26/04/2023, 12:10"; + String dateStr2 = "27/04/2023, 06:36"; + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy, HH:mm"); + LocalDateTime localDateTime1 = LocalDateTime.parse(dateStr, formatter); + LocalDateTime localDateTime2 = LocalDateTime.parse(dateStr2, formatter); + + + Instant instant1 = localDateTime1.atZone(ZoneId.of(AirportManager.getTimeZone(Airport.PARIS_CHARLES_DE_GAULLE))).toInstant(); + Instant instant2 = localDateTime2.atZone(ZoneId.of(AirportManager.getTimeZone(Airport.TOKYO_HANEDA))).toInstant(); + + firstTravel.setDepartureDate(instant1); + firstTravel.setArrivalDate(instant2); + + System.out.println(travelService.computeTravelDuration(firstTravel)); + + //Use adjustDates + travelService.adjustDates(Duration.ofHours(5), firstTravel); + //Log it in the appropriate zoneTime + System.out.println(firstTravel.getDepartureDate().atZone(ZoneId.of(AirportManager.getTimeZone(firstTravel.getDepartureAirport())))); + + //Find available ones + List<Travel> availableTravels = travelService.findAvailableForDestination("Paris"); + + } + + //Before services + public static void applyDiscounts(Travel travel) { + if (travel.getDiscounts().isEmpty()) { + System.out.println("Aucune réduction n'est applicable sur ce voyage !"); + } else { + for (Discount discount : travel.getDiscounts()) { + double discountedPrice = discount.apply(travel.getPrice()); + System.out.println("Prix après réduction : " + discountedPrice); + } + } + } + + public static boolean subscribe(User user, Travel travel) { + + if (travel.getParticipants().size() < travel.getCapacity()) { + travel.getParticipants().add(user); + return true; + } + travel.getWaitList().add(user); + return false; + } + + public void unsubscribe(User user, Travel travel) { + + travel.getParticipants().remove(user); + + if (!travel.getWaitList().isEmpty()) { + travel.getParticipants().add(travel.getWaitList().poll()); + } + } +} \ No newline at end of file diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/dao/TravelDao.java b/resources/solution/day-1/src/io/takima/agencymanagement/dao/TravelDao.java new file mode 100644 index 0000000000000000000000000000000000000000..5937d37b3d75e12d635e8bbe3adc5e4b2b425d36 --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/dao/TravelDao.java @@ -0,0 +1,59 @@ +package io.takima.agencymanagement.dao; + +import io.takima.agencymanagement.model.Airport; +import io.takima.agencymanagement.model.Discount; +import io.takima.agencymanagement.model.Travel; +import io.takima.agencymanagement.model.User; + +import java.time.Instant; +import java.util.*; + +public class TravelDao { + + private final Map<Long, Travel> travels = Collections.synchronizedMap(new HashMap<>()); + + public TravelDao() { + addTravelsToMap(); + } + + public Travel persist(Travel travel) { + travels.put(travel.getId(), travel); + return travel; + } + + public Travel update(Travel travel) { + if (travels.containsKey(travel.getId())) { + travels.put(travel.getId(), travel); + return travel; + } + return null; + } + + public void delete(Long travelId) { + travels.remove(travelId); + } + + public Travel findById(Long travelId) { + return travels.get(travelId); + } + + public List<Travel> findAll() { + return new ArrayList<>(travels.values()); + } + + public void addTravelsToMap() { + List<Discount> emptyDiscountsList = new ArrayList<>(); + Queue<User> emptyWaitList = new LinkedList<>(); + Set<User> emptyParticipantsList = new HashSet<>(); + Set<User> participantsList = new HashSet<>(); + participantsList.add(new User(10, "Jean", "Michel", "j.m@gamil.com")); + + Travel travel1 = new Travel(1, "Trip to Paris", Instant.now(), Instant.now().plusSeconds(3600), Airport.NEW_YORK_NEWARK, Airport.PARIS_ORLY, "Paris",50, 500.0, emptyDiscountsList, emptyParticipantsList, emptyWaitList); + Travel travel2 = new Travel(2, "Holiday in Tokyo", Instant.now(), Instant.now().plusSeconds(7200), Airport.PARIS_CHARLES_DE_GAULLE, Airport.TOKYO_HANEDA,"Tokyo", 40, 800.0, emptyDiscountsList, emptyParticipantsList, emptyWaitList); + Travel travel3 = new Travel(3, "Exploring Rome", Instant.now(), Instant.now().plusSeconds(10800), Airport.TOKYO_NARITA, Airport.NEW_YORK_JFK, "New York", 30, 600.0, emptyDiscountsList, participantsList, emptyWaitList); + + travels.put(travel1.getId(), travel1); + travels.put(travel2.getId(), travel2); + travels.put(travel3.getId(), travel3); + } +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/dao/UserDao.java b/resources/solution/day-1/src/io/takima/agencymanagement/dao/UserDao.java new file mode 100644 index 0000000000000000000000000000000000000000..689edb65a789d6e8bb2b6cde15b36d51d27951cb --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/dao/UserDao.java @@ -0,0 +1,54 @@ +package io.takima.agencymanagement.dao; + +import io.takima.agencymanagement.model.User; + +import java.util.*; + +public class UserDao { + + public UserDao() { + //add data + addUsersToMap(); + } + + private final Map<Long, User> users = Collections.synchronizedMap(new HashMap<>()); + + public User persist(User user) { + users.put(user.getId(), user); + return user; + } + + public User update(User user) { + if (users.containsKey(user.getId())) { + users.put(user.getId(), user); + return user; + } + return null; + } + + public void delete(Long userId) { + users.remove(userId); + } + + public User findById(Long userId) { + return users.get(userId); + } + + public List<User> findAll() { + return new ArrayList<>(users.values()); + } + + public void addUsersToMap() { + User user1 = new User(1, "John", "Doe", "john@example.com"); + User user2 = new User(2, "Jane", "Smith", "jane@example.com"); + User user3 = new User(3, "Alice", "Johnson", "alice@example.com"); + User user4 = new User(4, "Bob", "Williams", "bob@example.com"); + User user5 = new User(5, "Eva", "Brown", "eva@example.com"); + + users.put(user1.getId(), user1); + users.put(user2.getId(), user2); + users.put(user3.getId(), user3); + users.put(user4.getId(), user4); + users.put(user5.getId(), user5); + } +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/mapper/TravelDtoMapper.java b/resources/solution/day-1/src/io/takima/agencymanagement/mapper/TravelDtoMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..4ea0ed96b0e420860885dda78df81801a101023f --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/mapper/TravelDtoMapper.java @@ -0,0 +1,20 @@ +package io.takima.agencymanagement.mapper; + +import io.takima.agencymanagement.model.Travel; +import io.takima.agencymanagement.presentation.responsedto.TravelResponseDto; +import io.takima.agencymanagement.utils.AirportManager; + +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class TravelDtoMapper { + + public static TravelResponseDto fromTravel(Travel travel) { + ZonedDateTime zonedDepartureDate = travel.getDepartureDate() + .atZone(ZoneId.of(AirportManager.timeZone.get(travel.getDepartureAirport()))); + ZonedDateTime zonedArrivalDate = travel.getDepartureDate() + .atZone(ZoneId.of(AirportManager.timeZone.get(travel.getArrivalAirport()))); + + return new TravelResponseDto(travel.getName(), travel.getDestination(), travel.getDepartureAirport(), travel.getArrivalAirport(), zonedDepartureDate, zonedArrivalDate, travel.getPrice()); + } +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/mapper/UserDtoMapper.java b/resources/solution/day-1/src/io/takima/agencymanagement/mapper/UserDtoMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..d3351a9a6ca8a5b3aec35f1f44d80a9de6790e13 --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/mapper/UserDtoMapper.java @@ -0,0 +1,12 @@ +package io.takima.agencymanagement.mapper; + +import io.takima.agencymanagement.model.User; +import io.takima.agencymanagement.presentation.responsedto.UserResponseDto; + +public class UserDtoMapper { + + public static UserResponseDto fromUser(User user) { + + return new UserResponseDto(user.getFirstName(), user.getLastName(), user.getContact()); + } +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/model/Airport.java b/resources/solution/day-1/src/io/takima/agencymanagement/model/Airport.java new file mode 100644 index 0000000000000000000000000000000000000000..e2eabb8bf21b6055a42576ff5cc34ee550eecc1d --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/model/Airport.java @@ -0,0 +1,22 @@ +package io.takima.agencymanagement.model; + +public enum Airport { + + PARIS_CHARLES_DE_GAULLE("CDG"), + PARIS_ORLY("ORY"), + NEW_YORK_JFK("JFK"), + NEW_YORK_NEWARK("EWR"), + NEW_YORK_LAGUARDIA("LGA"), + TOKYO_NARITA("NRT"), + TOKYO_HANEDA("HND"); + + private final String acronym; + + Airport(String acronym){ + this.acronym = acronym; + } + + public String getAcronym() { + return acronym; + } +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/model/Discount.java b/resources/solution/day-1/src/io/takima/agencymanagement/model/Discount.java new file mode 100644 index 0000000000000000000000000000000000000000..4ab3d08a9724e1cae5161b407d8a12fd505a297f --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/model/Discount.java @@ -0,0 +1,5 @@ +package io.takima.agencymanagement.model; + +public interface Discount { + public double apply(double price); +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/model/HotelOffer.java b/resources/solution/day-1/src/io/takima/agencymanagement/model/HotelOffer.java new file mode 100644 index 0000000000000000000000000000000000000000..cea6ddb24b75fa1ee8daf748cec537577af747e3 --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/model/HotelOffer.java @@ -0,0 +1,36 @@ +package io.takima.agencymanagement.model; + +import java.time.Instant; + +public class HotelOffer extends Offer { + private boolean isBreakfastIncluded; + private int nbOfAvailableDays; + + public HotelOffer( + long id, + double score, + String description, + int nbOfReviews, + boolean isBreakfastIncluded, + int nbOfAvailableDays + ) { + super(id, score, description, nbOfReviews); + this.isBreakfastIncluded = isBreakfastIncluded; + this.nbOfAvailableDays = nbOfAvailableDays; + } + + @Override + public boolean isValid() { + return super.isValid() && + nbOfAvailableDays > 0; + } + + public String getDetails() { + return "Le nombre de jours valaibles " + + nbOfAvailableDays + + ". Petit déjeuner est " + + (isBreakfastIncluded ? "" : "non") + + "inclu"; + } + +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/model/Offer.java b/resources/solution/day-1/src/io/takima/agencymanagement/model/Offer.java new file mode 100644 index 0000000000000000000000000000000000000000..3b30b57b43b8971f0488772aeb6a60273d6f6e1f --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/model/Offer.java @@ -0,0 +1,27 @@ +package io.takima.agencymanagement.model; + +import java.time.Instant; + +public abstract class Offer { + protected long id; + protected double score; + protected String description; + protected int nbOfReviews; + protected abstract String getDetails(); + + protected Offer(long id, double score, String description, int nbOfReviews) { + this.id = id; + this.score = score; + this.description = description; + this.nbOfReviews = nbOfReviews; + } + + protected boolean isValid() { + return score >= 0 && + score <= 5 && + description != null && + !description.isBlank() && + nbOfReviews >= 0; + } + +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/model/PercentDiscount.java b/resources/solution/day-1/src/io/takima/agencymanagement/model/PercentDiscount.java new file mode 100644 index 0000000000000000000000000000000000000000..04876ada65f7343d7d6dc777561ffd256984c963 --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/model/PercentDiscount.java @@ -0,0 +1,15 @@ +package io.takima.agencymanagement.model; + +public class PercentDiscount implements Discount{ + + private double percent; + + public PercentDiscount(int percent) { + this.percent = percent; + } + + @Override + public double apply(double price) { + return price * (1 - (percent / 100)); + } +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/model/RestaurentOffer.java b/resources/solution/day-1/src/io/takima/agencymanagement/model/RestaurentOffer.java new file mode 100644 index 0000000000000000000000000000000000000000..f203bae445dd1399635dcf8ec73686ea787a5d37 --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/model/RestaurentOffer.java @@ -0,0 +1,34 @@ +package io.takima.agencymanagement.model; + +import java.time.Instant; + +public class RestaurentOffer extends Offer { + private int nbOfStars; + private int nbOfPerson; + + public RestaurentOffer( + long id, + double score, + String description, + int nbOfReviews, + Instant expiration, + int nbOfStars, + int nbOfPerson + ) { + super(id, score, description, nbOfReviews); + this.nbOfStars = nbOfStars; + this.nbOfPerson = nbOfPerson; + } + + @Override + public boolean isValid() { + return super.isValid() && + nbOfPerson > 0 && + nbOfStars >0; + } + + public String getDetails() { + return "La reservation est dans un restaurant de %d étoiles, pour %s personnes".formatted(nbOfStars, nbOfPerson); + } + +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/model/Travel.java b/resources/solution/day-1/src/io/takima/agencymanagement/model/Travel.java new file mode 100644 index 0000000000000000000000000000000000000000..af92355f057308640bef70ed522017922a2db8e8 --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/model/Travel.java @@ -0,0 +1,158 @@ +package io.takima.agencymanagement.model; + +import java.time.Instant; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +public class Travel { + + private long id; + private String name; + private Instant departureDate; + private Instant arrivalDate; + private Airport departureAirport; + private Airport arrivalAirport; + private String destination; + private int capacity; + private double price; + private List<Discount> discounts; + + private Set<User> participants; + + private Queue<User> waitList; + + public Travel(long id, String name, Instant departureDate, Instant arrivalDate, Airport departureAirport, Airport arrivalAirport, String destination, int capacity, double price, List<Discount> discounts, Set<User> participants, Queue<User> waitList) { + if(departureDate.isAfter(arrivalDate)){ + throw new IllegalArgumentException("Date de départ et après la date d'arrivée"); + } + + this.id = id; + this.name = name; + this.departureDate =departureDate; + this.arrivalDate = arrivalDate; + this.departureAirport = departureAirport; + this.arrivalAirport = arrivalAirport; + this.destination = destination; + this.capacity = capacity; + this.price = price; + this.discounts = discounts; + this.participants = participants; + this.waitList = waitList; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Instant getDepartureDate() { + return departureDate; + } + + public void setDepartureDate(Instant departureDate) { + this.departureDate = departureDate; + } + + public Instant getArrivalDate() { + return arrivalDate; + } + + public void setArrivalDate(Instant arrivalDate) { + this.arrivalDate = arrivalDate; + } + + public Airport getDepartureAirport() { + return departureAirport; + } + + public void setDepartureAirport(Airport departureAirport) { + this.departureAirport = departureAirport; + } + + public Airport getArrivalAirport() { + return arrivalAirport; + } + + public void setArrivalAirport(Airport arrivalAirport) { + this.arrivalAirport = arrivalAirport; + } + + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + + public int getCapacity() { + return capacity; + } + + public void setCapacity(int capacity) { + this.capacity = capacity; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + public List<Discount> getDiscounts() { + return discounts; + } + + public void setDiscounts(List<Discount> discounts) { + this.discounts = discounts; + } + + public Set<User> getParticipants() { + return participants; + } + + public void setParticipants(Set<User> participants) { + this.participants = participants; + } + + public Queue<User> getWaitList() { + return waitList; + } + + public void setWaitList(Queue<User> waitList) { + this.waitList = waitList; + } + + @Override + public String toString() { + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Le voyage '") + .append(name) + .append("' à destination de ") + .append(destination) + .append(" a ") + .append(participants.size()) + .append(" voyageurs inscrits qui sont respectivement:\n"); + + for (User participant : participants) { + stringBuilder.append("- ").append(participant.getFirstName()).append(participant.getLastName()).append("\n"); + } + + return stringBuilder.toString(); + } +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/model/User.java b/resources/solution/day-1/src/io/takima/agencymanagement/model/User.java new file mode 100644 index 0000000000000000000000000000000000000000..b8393a81002ca1b1b7e98998be3a0836b65f269f --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/model/User.java @@ -0,0 +1,72 @@ +package io.takima.agencymanagement.model; + +import java.util.Objects; + +public class User { + + private long id; + private String firstName; + private String lastName; + private String contact; + + public User(long id, String firstName, String lastName, String contact) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.contact = contact; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getContact() { + return contact; + } + + public void setContact(String contact) { + this.contact = contact; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return id == user.id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Utilisateur " + + firstName + " " + + lastName + " " + + ", contact '" + contact + "\n" + ; + } +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/model/ValueDiscount.java b/resources/solution/day-1/src/io/takima/agencymanagement/model/ValueDiscount.java new file mode 100644 index 0000000000000000000000000000000000000000..61ea2fc067cbc3d6c70bba73c62c50d318b14e25 --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/model/ValueDiscount.java @@ -0,0 +1,15 @@ +package io.takima.agencymanagement.model; + +public class ValueDiscount implements Discount{ + + private double value; + + public ValueDiscount(double value) { + this.value = value; + } + + @Override + public double apply(double price) { + return price - value; + } +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/presentation/responsedto/TravelResponseDto.java b/resources/solution/day-1/src/io/takima/agencymanagement/presentation/responsedto/TravelResponseDto.java new file mode 100644 index 0000000000000000000000000000000000000000..38a2048c1bb1b5b6c4d46dade6efedb6667ef64a --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/presentation/responsedto/TravelResponseDto.java @@ -0,0 +1,15 @@ +package io.takima.agencymanagement.presentation.responsedto; + +import io.takima.agencymanagement.model.Airport; + +import java.time.ZonedDateTime; + +public record TravelResponseDto( + String name, + String destination, + Airport departureAirport, + Airport destinationAirport, + ZonedDateTime departureDate, + ZonedDateTime endDate, + double price) { +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/presentation/responsedto/UserResponseDto.java b/resources/solution/day-1/src/io/takima/agencymanagement/presentation/responsedto/UserResponseDto.java new file mode 100644 index 0000000000000000000000000000000000000000..5a41162ba8455950695b32fd2e4983f143c2c75d --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/presentation/responsedto/UserResponseDto.java @@ -0,0 +1,7 @@ +package io.takima.agencymanagement.presentation.responsedto; + +public record UserResponseDto( + String firstName, + String lastName, + String contact) { +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/service/TravelService.java b/resources/solution/day-1/src/io/takima/agencymanagement/service/TravelService.java new file mode 100644 index 0000000000000000000000000000000000000000..37969c2c3a44d86e048ce96449bdf070b49a26cd --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/service/TravelService.java @@ -0,0 +1,158 @@ +package io.takima.agencymanagement.service; + +import io.takima.agencymanagement.dao.TravelDao; +import io.takima.agencymanagement.mapper.TravelDtoMapper; +import io.takima.agencymanagement.model.Travel; +import io.takima.agencymanagement.model.User; +import io.takima.agencymanagement.presentation.responsedto.TravelResponseDto; +import io.takima.agencymanagement.model.Discount; + +import java.time.Duration; +import java.time.Instant; +import java.util.Comparator; +import java.util.List; +import java.util.NoSuchElementException; + +import static io.takima.agencymanagement.mapper.TravelDtoMapper.fromTravel; + +public class TravelService { + + private TravelDao travelDao; + + public TravelService() { + travelDao = new TravelDao(); + } + + public TravelResponseDto create(Travel travel) { + travelDao.persist(travel); + return fromTravel(travel); + } + + public TravelResponseDto update(Travel travel) { + travelDao.persist(travel); + return fromTravel(travel); + } + + public TravelResponseDto delete(Travel travel) { + travelDao.persist(travel); + return fromTravel(travel); + } + + public TravelResponseDto findById(Long id) { + Travel travel = travelDao.findById(id); + return fromTravel(travel); + } + + public List<TravelResponseDto> findAll() { + return travelDao.findAll().stream() + .map(TravelDtoMapper::fromTravel) + .toList(); + + } + public void applyDiscounts(Travel travel) { + if (travel.getDiscounts().isEmpty()) { + System.out.println("Aucune réduction n'est applicable sur ce voyage !"); + } else { + double discountedPrice = travel.getPrice(); + for (Discount discount : travel.getDiscounts()) { + discountedPrice = discount.apply(discountedPrice); + } + System.out.println("Prix après les réductions : " + discountedPrice); + } + } + + public boolean subscribe(User user, Travel travel) throws Exception { + + if (travel.getParticipants().contains(user) || travel.getWaitList().contains(user)) { + //User already subscribed or in waitList + throw new Exception("L'utilisateur est déjà inscrit ou en file d'attente pour ce voyage."); + } + + if (travel.getParticipants().size() < travel.getCapacity()) { + travel.getParticipants().add(user); + return true; + } + travel.getWaitList().add(user); + return false; + } + + public void unsubscribe(User user, Travel travel) { + + travel.getParticipants().remove(user); + + if (!travel.getWaitList().isEmpty()) { + travel.getParticipants().add(travel.getWaitList().poll()); + } + } + + public Duration computeTravelDuration(Travel travel) { + return Duration.between(travel.getDepartureDate(), travel.getArrivalDate()); + } + + public void adjustDates(Duration delay, Travel travel) { + travel.setDepartureDate(travel.getDepartureDate().plus(delay)); + travel.setArrivalDate(travel.getArrivalDate().plus(delay)); + } + + public Travel findClosestToCurrentDate(List<Travel> travels) { + Instant now = Instant.now(); + + return travels.stream() + .min(Comparator.comparing(travel -> Duration.between(now, travel.getDepartureDate()).abs())) + .orElseThrow(NoSuchElementException::new); + } + + public List<Travel> findAvailableForDestination(String destination) { + List<Travel> travels = travelDao.findAll(); + + return travels.stream() + .filter(travel -> travel.getCapacity() > travel.getParticipants().size() && travel.getDestination().equals(destination)) + .toList(); + } + + public List<Travel> findInPriceRange(double minPrice, double maxPrice) { + List<Travel> travels = travelDao.findAll(); + + return travels.stream() + .filter(travel -> travel.getPrice() >= minPrice && travel.getPrice() <= maxPrice) + .toList(); + } + + public List<Travel> findNSortedByPriceAsc(int n) { + List<Travel> travels = travelDao.findAll(); + + return travels.stream() + .sorted(Comparator.comparing(Travel::getPrice)) + .limit(n) + .toList(); + } + + public List<Travel> findWithPromotions() { + List<Travel> travels = travelDao.findAll(); + + return travels.stream() + .filter(travel -> !travel.getDiscounts().isEmpty()) + .toList(); + } + + public List<Travel> findByUser(String firstName, String lastName) { + List<Travel> travels = travelDao.findAll(); + + return travels.stream() + .filter(travel -> + travel.getParticipants().stream() + .anyMatch(user -> + user.getFirstName().equals(firstName) && + user.getLastName().equals(lastName))) + .toList(); + } + + public double getAveragePrice() { + List<Travel> travels = travelDao.findAll(); + + return travels.stream() + .mapToDouble(Travel::getPrice) + .average() + .orElse(0.0); + } +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/service/UserService.java b/resources/solution/day-1/src/io/takima/agencymanagement/service/UserService.java new file mode 100644 index 0000000000000000000000000000000000000000..08903f6c0eee79fc1fc9b8c05b6e17e14caa7d4e --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/service/UserService.java @@ -0,0 +1,42 @@ +package io.takima.agencymanagement.service; + +import io.takima.agencymanagement.dao.UserDao; +import io.takima.agencymanagement.mapper.UserDtoMapper; +import io.takima.agencymanagement.model.User; +import io.takima.agencymanagement.presentation.responsedto.UserResponseDto; + +import java.util.List; + +import static io.takima.agencymanagement.mapper.UserDtoMapper.fromUser; + +public class UserService { + + UserDao userDao = new UserDao(); + + public UserResponseDto create(User user) { + userDao.persist(user); + return fromUser(user); + } + + public UserResponseDto update(User user) { + userDao.persist(user); + return fromUser(user); + } + + public UserResponseDto delete(User user) { + userDao.persist(user); + return fromUser(user); + } + + public UserResponseDto findById(Long id) { + User user = userDao.findById(id); + return fromUser(user); + } + + public List<UserResponseDto> findAll() { + return userDao.findAll().stream() + .map(UserDtoMapper::fromUser) + .toList(); + + } +} diff --git a/resources/solution/day-1/src/io/takima/agencymanagement/utils/AirportManager.java b/resources/solution/day-1/src/io/takima/agencymanagement/utils/AirportManager.java new file mode 100644 index 0000000000000000000000000000000000000000..d48a17b7a424a42a613db4a3d1e8358cc03f31c5 --- /dev/null +++ b/resources/solution/day-1/src/io/takima/agencymanagement/utils/AirportManager.java @@ -0,0 +1,21 @@ +package io.takima.agencymanagement.utils; + +import io.takima.agencymanagement.model.Airport; + +import java.util.HashMap; +import java.util.Map; + +public class AirportManager { + + private static final Map<Airport, String> timeZone = new HashMap<>(); + + static { + timeZone.put(Airport.PARIS_CHARLES_DE_GAULLE, "Europe/Paris"); + timeZone.put(Airport.PARIS_ORLY, "Europe/Paris"); + timeZone.put(Airport.NEW_YORK_JFK, "America/New_York"); + timeZone.put(Airport.NEW_YORK_NEWARK, "America/New_York"); + timeZone.put(Airport.NEW_YORK_LAGUARDIA, "America/New_York"); + timeZone.put(Airport.TOKYO_NARITA, "Asia/Tokyo"); + timeZone.put(Airport.TOKYO_HANEDA, "Asia/Tokyo"); + } +} diff --git a/resources/solution/day-2/.gitkeep b/resources/solution/day-2/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/resources/solution/day-2/day2.zip b/resources/solution/day-2/day2.zip new file mode 100644 index 0000000000000000000000000000000000000000..a7f5535d8e48728a50bdf7d56035e61f38d2449c Binary files /dev/null and b/resources/solution/day-2/day2.zip differ diff --git a/scripts/clean_lab.py b/scripts/clean_lab.py new file mode 100644 index 0000000000000000000000000000000000000000..35ce2b0861ea34ab75419980f833fe180ef22063 --- /dev/null +++ b/scripts/clean_lab.py @@ -0,0 +1,46 @@ +from ast import Return +import subprocess +import shlex +import csv +import json +import os +from pathlib import Path +from dotenv import load_dotenv +from ansible_playbook_runner import Runner +import boto3 +import re + + +# Declare glob var +DOMAIN = os.getenv("DOMAIN") +SCHOOL_NAME = os.getenv("SCHOOL_NAME") +REGION = os.getenv("REGION") +students_usernames = [] +students = {} +client = boto3.client('route53') + +def sanitize_user(username): + return re.sub("[^0-9a-zA-Z]+", "-", username.lower()) + +def get_str_users_json_conf(CSV): + str_students_usernames_json_conf= "" + array_students_usernames=[] + with open(CSV) as csv_file: + csv_reader = csv.reader(csv_file, delimiter=',') + for line in csv_reader: + for email in line: + username = email.split('@')[0].lower() + username_sanitized = sanitize_user(username) + array_students_usernames.append("\"" + username_sanitized + "\"") + str_students_usernames_json_conf="\'{\"usernames\":[" + ",".join(array_students_usernames) + "]}\'" + return str_students_usernames_json_conf + +students_usernames = get_str_users_json_conf(os.getenv("CSV")) +print (students_usernames) +#ansible_cmd="ansible-playbook -i ../ansible/inventories/k8s.ini ../ansible/provision.yml --extra-vars " + "\'{\"USERNAMES\": \"" + students_usernames + "\"}\'" + " --extra-vars argo_website_host=argocd." + DOMAIN + " --extra-vars argo_grpc_api_host=grpc-argocd." + DOMAIN +ansible_cmd="ansible-playbook -i ../ansible/inventories/k8s.ini ../ansible/clean.yml --extra-vars " + students_usernames + " --extra-vars dns_zone=" + DOMAIN +print (ansible_cmd) +subprocess.call(shlex.split(ansible_cmd)) + + + diff --git a/scripts/clean_students_namespaces.py b/scripts/clean_students_namespaces.py new file mode 100644 index 0000000000000000000000000000000000000000..46320d2e9bc54e5f2ea70318641a835c1b915475 --- /dev/null +++ b/scripts/clean_students_namespaces.py @@ -0,0 +1,67 @@ +from ast import Return +import subprocess +import shlex + + +import csv +import json +import os +import re +from pathlib import Path +from dotenv import load_dotenv + +# Load .env variables +load_dotenv() +DOMAIN = os.getenv("DOMAIN") +SCHOOL_NAME = os.getenv("SCHOOL_NAME") +REGION = os.getenv("REGION") +LAB_ENV = os.getenv("LAB_ENV") + +# deploy ingress controller and retrieve Public ip LB +#subprocess.check_call(['./create_ingress_controller.sh']) + + + +# Declare glob var +students_usernames = [] +students = {} + +def sanitize_user(username): + return re.sub("[^0-9a-zA-Z]+", "-", username.lower()) + +def get_str_users_json_conf(CSV): + str_students_usernames_json_conf= "" + array_students_usernames=[] + with open(CSV) as csv_file: + csv_reader = csv.reader(csv_file, delimiter=',') + for line in csv_reader: + for email in line: + username = email.split('@')[0].lower() + username_sanitized = sanitize_user(username) + array_students_usernames.append("\"" + username_sanitized + "\"") + str_students_usernames_json_conf="\'{\"usernames\":[" + ",".join(array_students_usernames) + "]}\'" + return str_students_usernames_json_conf + + +students_usernames = get_str_users_json_conf(os.getenv("CSV")) +print (students_usernames) + +## send mail and construct kubernetes ns + kubconf by user +with open(os.getenv("CSV")) as csv_file: + csv_reader = csv.reader(csv_file, delimiter=',') + # For each trainee + for line in csv_reader: + for email in line: + try: + username = email.split('@')[0].lower() + username_sanitized = sanitize_user(username) + # Create users in cluster, create kubeconfig + subprocess.check_call(['./clean_namespaces.sh', username_sanitized, username_sanitized]) + except RuntimeError: + print('An Error occurred with user ' + email + ': did not fully complete namespace cleanup') + + with open('students.tfvars.json', 'w') as outfile: + students_dict = { + "students": students + } + json.dump(students_dict, outfile) diff --git a/scripts/clean_trainee_repo.py b/scripts/clean_trainee_repo.py new file mode 100755 index 0000000000000000000000000000000000000000..232abb130897d4d3988da08e1ac08c1584dc51c2 --- /dev/null +++ b/scripts/clean_trainee_repo.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +from gitcontent import playerrepo +import os +os.chdir(os.path.dirname(os.path.realpath(__file__))) +playerrepo.clean_trainee_doc() +playerrepo.commit_and_push_force_master() +playerrepo.clear() \ No newline at end of file diff --git a/scripts/constant.py b/scripts/constant.py new file mode 100644 index 0000000000000000000000000000000000000000..6c7fb2cf23eb9f397f284e5f7429ff13e9f4ad95 --- /dev/null +++ b/scripts/constant.py @@ -0,0 +1,8 @@ +import os + +# Public Git Repo for Trainees +GIT_REPOSITORY = os.getenv("GIT_TRAINEE_REPOSITORY") +GIT_USERNAME = os.getenv("GIT_TRAINEE_ACCESS_TOKEN_NAME") +GIT_PASSWORD = os.getenv("GIT_TRAINEE_ACCESS_TOKEN_SECRET") + +GIT_REPO = 'https://' + GIT_USERNAME + ':' + GIT_PASSWORD + '@' + GIT_REPOSITORY diff --git a/scripts/deploy_boilerplate.py b/scripts/deploy_boilerplate.py new file mode 100755 index 0000000000000000000000000000000000000000..4a17b755e0eded91db6a73115c4120c12aa0bab4 --- /dev/null +++ b/scripts/deploy_boilerplate.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +from gitcontent import playerrepo +import os +import sys +os.chdir(os.path.dirname(os.path.realpath(__file__))) +playerrepo.clone_from_remote() + +if (len(sys.argv) != 3): + print("Usage: python3 deploy_boilerplate.py [chapter] [step]") + print("Example: python3 deploy_boilerplate.py chapter1 step-2") + exit(1) +playerrepo.deploy_boilerplate(sys.argv[1], sys.argv[2]) diff --git a/scripts/deploy_solution.py b/scripts/deploy_solution.py new file mode 100755 index 0000000000000000000000000000000000000000..7feb73b4e71bdded3b8012a9d564f1d64398cf25 --- /dev/null +++ b/scripts/deploy_solution.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +from gitcontent import playerrepo +import os +import sys +os.chdir(os.path.dirname(os.path.realpath(__file__))) +playerrepo.clone_from_remote() + +if (len(sys.argv) != 3): + print("Usage: python3 deploy_solution.py [chapter] [step]") + print("Example: python3 deploy_solution.py chapter1 step-2") + exit(1) +playerrepo.deploy_solution(sys.argv[1], sys.argv[2]) diff --git a/scripts/deploy_trainee_repo.py b/scripts/deploy_trainee_repo.py new file mode 100755 index 0000000000000000000000000000000000000000..e12110f06613df189118dd36d4ab14fc200219ba --- /dev/null +++ b/scripts/deploy_trainee_repo.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +from gitcontent import playerrepo +import os +os.chdir(os.path.dirname(os.path.realpath(__file__))) +playerrepo.init_with_resources() +playerrepo.commit_and_push_force_master() +playerrepo.clear() \ No newline at end of file diff --git a/scripts/gitcontent/playerrepo.py b/scripts/gitcontent/playerrepo.py new file mode 100644 index 0000000000000000000000000000000000000000..acfedd3a58385d6b48e244acd235578c4becbaf1 --- /dev/null +++ b/scripts/gitcontent/playerrepo.py @@ -0,0 +1,62 @@ +import constant +import tempfile +import subprocess +from git import Repo +import shutil + +tmpdir = tempfile.mkdtemp(suffix='trainee') +repodir = tmpdir + "/repo" +public_docs_dir = repodir + "/public" + +def init_with_resources(): + print("Copying resources to " + repodir) + shutil.copytree("../resources/boilerplate/java-one-trainees", repodir) + print("Building docs") + return_code = subprocess.call("cd ../resources/docs && mkdocs build", shell=True) + if (return_code != 0): + exit(1) + print("Copying resources to " + public_docs_dir) + shutil.copytree("../resources/docs/site", public_docs_dir) + +def clean_trainee_doc(): + print("Building empty doc to replace older one") + return_code = subprocess.call("cd ../resources/boilerplate/emptydocs/ && mkdocs build", shell=True) + if (return_code != 0): + exit(1) + print("Copying resources to " + public_docs_dir) + shutil.copytree("../resources/boilerplate/emptydocs/site", public_docs_dir) + shutil.copy("../resources/boilerplate/java-one-trainees/.gitlab-ci.yml", repodir) + +def commit_and_push_force_master(): + repo = Repo.init(repodir) + origin = repo.create_remote('origin', constant.GIT_REPO) + assert origin.exists() + repo.git.add("--all") + repo.git.commit(message="Initial Commit") + repo.git.push("-f", "origin", "master:main") + +def clone_from_remote(): + repo = Repo.clone_from(constant.GIT_REPO, repodir) + assert repo.__class__ is Repo + +def deploy_boilerplate(chapter, step): + print(f'Copying boilerplate {chapter}/{step}') + shutil.copytree(f'../resources/boilerplate/{chapter}/{step}', f'{repodir}/boilerplate/{chapter}/{step}') + repo = Repo(repodir) + repo.git.add("--all") + repo.git.commit(message="Added boilerplate for " + chapter + " - " + step) + repo.git.push("origin", "main:main") + +def deploy_solution(chapter, step): + print("Copying solution from " + chapter + " / " + step) + shutil.copytree("../resources/solution/" + chapter + "/" + step, repodir + "/solution/" + chapter + "/" + step) + repo = Repo(repodir) + repo.git.add("--all") + repo.git.commit(message="Added solution for " + chapter + " - " + step) + repo.git.push("origin", "main:main") + +def clear(): + global tmpdir + global repodir + tmpdir = tempfile.mkdtemp(suffix='trainee') + repodir = tmpdir + "/repo" diff --git a/scripts/utils.py b/scripts/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..e3f6503ed92b58c2f01d01a0afdf49e00c1b2ffe --- /dev/null +++ b/scripts/utils.py @@ -0,0 +1,24 @@ +def replace(file, begintoken, endtoken, replacementlines): + destfile = open(file, "r") + lines = destfile.readlines() + destfile.close() + destfile = open(file, "w") + begintokenline = -1 + endtokenline = -1 + for i, line in enumerate(lines): + if begintoken in line: + destfile.write(line) + begintokenline = i + if endtoken in line: + endtokenline = i + if begintokenline == -1: + destfile.write(line) + continue; + if endtokenline == -1: + continue; + for replacementline in replacementlines: + destfile.write(replacementline + '\r\n') + destfile.write(line) + begintokenline = -1 + endtokenline = -1 + destfile.close()