<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ed Legaspi</title>
    <description>The latest articles on DEV Community by Ed Legaspi (@czetsuya).</description>
    <link>https://dev.to/czetsuya</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F918356%2F3bd969db-a70a-492b-bad3-72081965f313.jpg</url>
      <title>DEV Community: Ed Legaspi</title>
      <link>https://dev.to/czetsuya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/czetsuya"/>
    <language>en</language>
    <item>
      <title>I Got Tired of Rewriting Audit Logs in Spring Boot — So I Built nerv-audit</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Fri, 17 Apr 2026 07:18:23 +0000</pubDate>
      <link>https://dev.to/czetsuya/i-got-tired-of-rewriting-audit-logs-in-spring-boot-so-i-built-nerv-audit-3cg4</link>
      <guid>https://dev.to/czetsuya/i-got-tired-of-rewriting-audit-logs-in-spring-boot-so-i-built-nerv-audit-3cg4</guid>
      <description>&lt;p&gt;Every backend system eventually hits this moment:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Who changed this record?”&lt;br&gt;
“What was the previous value?”&lt;br&gt;
“When did it happen?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Simple questions… until you actually need answers in production.&lt;/p&gt;




&lt;p&gt;⚠️ Note: The core module is commercial, but you can explore the public modules and examples here:&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-audit" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-audit&lt;/a&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-examples" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-examples&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;In most of my Spring Boot projects, I relied on Hibernate Envers.&lt;/p&gt;

&lt;p&gt;It works—but in real systems, it starts to hurt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You repeat the same setup across services&lt;/li&gt;
&lt;li&gt;Audit queries are hard to read and maintain&lt;/li&gt;
&lt;li&gt;Business-level audit logic gets scattered&lt;/li&gt;
&lt;li&gt;Small mistakes become painful in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a few projects, I realized:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I wasn’t building features anymore—I was rebuilding audit infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  What I Actually Wanted
&lt;/h2&gt;

&lt;p&gt;Not a replacement for Envers.&lt;/p&gt;

&lt;p&gt;Just something that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standardizes audit handling&lt;/li&gt;
&lt;li&gt;Reduces boilerplate&lt;/li&gt;
&lt;li&gt;Makes queries readable&lt;/li&gt;
&lt;li&gt;Works consistently across projects&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  So I Built nerv-audit
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/czetsuyatech/nerv-audit" rel="noopener noreferrer"&gt;nerv-audit&lt;/a&gt; is a lightweight layer on top of Envers that focuses on &lt;strong&gt;developer experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of wiring everything manually, you get a cleaner way to work with audit data.&lt;/p&gt;


&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Without nerv-audit
&lt;/h3&gt;

&lt;p&gt;You end up dealing with low-level Envers APIs and custom query logic.&lt;/p&gt;
&lt;h3&gt;
  
  
  With nerv-audit
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVerticalAudits&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That’s it.&lt;/p&gt;



&lt;p&gt;Want to see how it's implemented in a real project?&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-audit" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-audit&lt;/a&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-examples" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-examples&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  What It Improves
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Consistent Audit Handling
&lt;/h3&gt;

&lt;p&gt;Define audit behavior once, reuse everywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@AuditedEntity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. Cleaner Queries
&lt;/h3&gt;

&lt;p&gt;No more complex Envers query construction.&lt;/p&gt;

&lt;p&gt;You focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What changed&lt;/li&gt;
&lt;li&gt;When it changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not &lt;em&gt;how&lt;/em&gt; to retrieve it.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Less Repetition
&lt;/h3&gt;

&lt;p&gt;Across projects, the pattern is always the same.&lt;/p&gt;

&lt;p&gt;nerv-audit abstracts that pattern so you don’t rewrite it every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;This came from real production work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple systems&lt;/li&gt;
&lt;li&gt;Repeated audit requirements&lt;/li&gt;
&lt;li&gt;Real incidents where audit data mattered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At some point, it made more sense to &lt;strong&gt;abstract the solution&lt;/strong&gt; than keep rebuilding it.&lt;/p&gt;




&lt;h2&gt;
  
  
  About the Model (Transparency)
&lt;/h2&gt;

&lt;p&gt;nerv-audit is &lt;strong&gt;not fully open-source&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is a &lt;strong&gt;free tier&lt;/strong&gt; you can use&lt;/li&gt;
&lt;li&gt;Some advanced capabilities are part of a &lt;strong&gt;paid core&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ Note: The core module is commercial, but you can explore the public modules and examples here:&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-audit" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-audit&lt;/a&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/czetsuyatech/nerv-examples" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-examples&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I chose this approach to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the project sustainable&lt;/li&gt;
&lt;li&gt;Continue improving it over time&lt;/li&gt;
&lt;li&gt;Focus on real-world use cases&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Who This Is For
&lt;/h2&gt;

&lt;p&gt;This might be useful if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build Spring Boot applications&lt;/li&gt;
&lt;li&gt;Use Hibernate Envers (or plan to)&lt;/li&gt;
&lt;li&gt;Want a cleaner way to handle audit logs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  I’d Like Your Input
&lt;/h2&gt;

&lt;p&gt;How are you handling audit logs today?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pure Envers?&lt;/li&gt;
&lt;li&gt;Custom implementation?&lt;/li&gt;
&lt;li&gt;Something else?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m especially interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pain points&lt;/li&gt;
&lt;li&gt;Query challenges&lt;/li&gt;
&lt;li&gt;Scaling issues&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you're working with Envers, try it out and let me know what breaks—or what works better.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/czetsuyatech/nerv-audit" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-audit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/czetsuyatech/nerv-examples" rel="noopener noreferrer"&gt;https://github.com/czetsuyatech/nerv-examples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>hibernate</category>
      <category>backend</category>
    </item>
    <item>
      <title>Hands-On Coding: Exploring Hyperparameters for Programmers</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Thu, 07 Mar 2024 04:54:31 +0000</pubDate>
      <link>https://dev.to/czetsuya/hands-on-coding-exploring-hyperparameters-for-programmers-5c9c</link>
      <guid>https://dev.to/czetsuya/hands-on-coding-exploring-hyperparameters-for-programmers-5c9c</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In this article, we will explore different techniques for finding the optimal hyperparameter values from a given set of parameters in a grid. Particularly we will look at RandomizedSearchCV, GridSearchCV, and BayesSearchCV.&lt;/p&gt;

&lt;p&gt;In this blog you will learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to initialize the parameter grid.&lt;/li&gt;
&lt;li&gt;How to find the optimal hyperparameters based on a given technique.&lt;/li&gt;
&lt;li&gt;How to build a model (XGBClassifier) to use the hyperparameters.&lt;/li&gt;
&lt;li&gt;How to score the performance of the model.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  RandomizedSearchCV
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;param_grid = {
    "gamma": [0, 0.1, 0.2, 0.5, 1, 1.5, 2, 3, 6, 12, 20],
    "learning_rate": [0.01, 0.02, 0.03, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8],
    "max_depth": [1, 2, 3, 4, 5, 6, 8, 12],
    "n_estimators": [25, 50, 65, 80, 100, 115, 200]
}

grid_search = RandomizedSearchCV(estimator=classifier_0, param_distributions=param_grid, scoring=scoring)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GridSearchCV
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;param_grid = {
    "gamma": [0, 0.1, 0.2, 0.5, 1, 1.5, 2, 3, 6, 12, 20],
    "learning_rate": [0.01, 0.02, 0.03, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8],
    "max_depth": [2, 3, 4, 5, 6, 8, 12],
    "n_estimators": [25, 50, 65, 80, 100, 115, 200]
}

grid_search = GridSearchCV(estimator=classifier_0, param_grid=param_grid, scoring=scoring)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  BayesSearchCV
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;param_bayes = {
    'gamma': Categorical(param_grid['gamma']),
    'learning_rate': Categorical(param_grid['learning_rate']),
    'max_depth': Categorical(param_grid['max_depth']),
    'n_estimators': Categorical(param_grid['n_estimators'])
}

grid_search = BayesSearchCV(estimator=classifier_0, search_spaces=param_bayes, scoring=scoring, n_jobs=-1, cv=10)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Finding the Best HyperParameters
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;best_model = grid_search.fit(X_train, y_train)
hyperparams = best_model.best_params_
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Building and Scoring the Classifier using the HyperParameters
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Fitting the Model
ne = hyperparams['n_estimators']
lr = hyperparams['learning_rate']
md = hyperparams['max_depth']
gm = hyperparams['gamma']
print("Recommended Params &amp;gt;&amp;gt;", f"ne: {ne},", f"lr: {lr}", f"md: {md}", f"gm: {gm}")

# Build Classification Model
classifier_1 = XGBClassifier(
    base_score=0.5,
    colsample_bylevel=1,
    colsample_bynode=1,
    objective=objective,
    booster="gbtree",
    eval_metric=eval_metric_list,
    n_estimators=ne,
    learning_rate=lr,
    max_depth=md,
    gamma=gm,
    subsample=0.8,
    colsample_bytree=1,
    random_state=1
)

# Fit Model
eval_set = [(X_train, y_train)]
classifier_1.fit(
    X_train,
    y_train,
    eval_set=eval_set,
    verbose=False
)

# Get predictions for training data
train_yhat = classifier_1.predict(X_train)
print("Training Preds: \n", train_yhat[:5])

# Set K-Fold Cross Validation Levels
cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=3, random_state=1)

# Training Results
train_results = cross_val_score(classifier_1, X_train, y_train, scoring=scoring, cv=cv, n_jobs=1)

# Brief Review of Training Results
print("Average Accuracy K-Fold: ", round(train_results.mean(), 2))
print("Std Deviation K-Fold: ", round(train_results.std(), 2))
print("Precision Score 0: ", round(precision_score(y_train, train_yhat, average=None)[0], 3))
print("Precision Score 1: ", round(precision_score(y_train, train_yhat, average=None)[1], 3))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Machine: Laptop &lt;br&gt;
Processor: AMD Ryzen 7 &lt;br&gt;
OS: Windows &lt;br&gt;
DataFrame Shape: (7282, 17)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkkxfai73dfdz9tjcqz69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkkxfai73dfdz9tjcqz69.png" alt="Image description" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>programming</category>
      <category>learning</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Understanding How Scope Affects Values in Your Spring REST Controller</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sat, 02 Mar 2024 03:33:59 +0000</pubDate>
      <link>https://dev.to/czetsuya/understanding-how-scope-affects-values-in-your-spring-rest-controller-4c97</link>
      <guid>https://dev.to/czetsuya/understanding-how-scope-affects-values-in-your-spring-rest-controller-4c97</guid>
      <description>&lt;p&gt;Below we explore how a scope annotation affects an instance value in a Spring REST controller.&lt;/p&gt;

&lt;p&gt;Each controller is annotated with scope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@RestController
@Scope([SCOPE_VALUE])
public class XXXScopeController {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwos5ouwyufdrudx13r9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwos5ouwyufdrudx13r9.png" alt="Image description" width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Convert Vertically Stored Asset Data into Columnar Format for Cointegration Analysis</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sat, 02 Mar 2024 01:12:16 +0000</pubDate>
      <link>https://dev.to/czetsuya/how-to-convert-vertically-stored-asset-data-into-columnar-format-for-cointegration-analysis-54f9</link>
      <guid>https://dev.to/czetsuya/how-to-convert-vertically-stored-asset-data-into-columnar-format-for-cointegration-analysis-54f9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This piece of code fetches asset information from a table stored vertically. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08t3ue8ulmjcwbr4wi4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08t3ue8ulmjcwbr4wi4u.png" alt="Image description" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependencies
&lt;/h2&gt;

&lt;p&gt;Install the following package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conda install pandas
conda install numpy as np
conda install mysql-connector-python
conda install sqlalchemy
conda install pymysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hands-on Coding
&lt;/h2&gt;

&lt;p&gt;Connect to the database&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def query_df(query):
    try: 
        engine_uri = f"mysql+pymysql://db_user:db_pass_123@localhost:3306/tradewise_pse"
        db_conn = create_engine(engine_uri)        
        df_result = pd.read_sql(query, db_conn)    
        return df_result

    except Exception as e:    
        print(str(e))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fetching the Dataset
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if not load_existing:
    sql_distinct_tickers = "select ticker from candlestick where event_time='2023-12-29' and ticker not like '^%%'"
    df_tickers = query_df(sql_distinct_tickers)

    df = pd.DataFrame(index=['event_time'])

    ### Get the candlesticks
    for ticker in df_tickers['ticker']:
        sql_ticker_col = "select event_time, close from candlestick where ticker='{0}'"
        df_temp = query_df(sql_ticker_col.format(ticker))
        df_temp.set_index('event_time', inplace=True)
        df_temp.rename(columns={'close': ticker}, inplace=True)        
        df = df.add(df_temp, fill_value=0)

    df.to_csv(file_name)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Load the dataset from file
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df = pd.read_csv(file_name, index_col=0)
df.drop(index=df.index[-1],axis=0, inplace=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Drop NA
&lt;/h2&gt;

&lt;p&gt;df.dropna(axis=1, inplace=True)&lt;/p&gt;

&lt;h2&gt;
  
  
  Print the Dataset
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(f"Shape: {df.shape}")
print(f"Null values: {df.isnull().values.any()}")
df

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ypd0yfpe8ihrfvx58cl.png)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>programming</category>
    </item>
    <item>
      <title>Learn to Incorporate Rolling Hurst Values into Your DataFrame</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sat, 02 Mar 2024 01:08:30 +0000</pubDate>
      <link>https://dev.to/czetsuya/learn-to-incorporate-rolling-hurst-values-into-your-dataframe-40jk</link>
      <guid>https://dev.to/czetsuya/learn-to-incorporate-rolling-hurst-values-into-your-dataframe-40jk</guid>
      <description>&lt;h2&gt;
  
  
  The hurst function.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def hurst(ts, min_lag=1, max_lag=7):
    lags = range(min_lag, max_lag)
    tau = [np.sqrt(np.std(np.subtract(ts[lag:], ts[:-lag]))) for lag in lags]
    poly = np.polyfit(np.log(lags), np.log(tau), 1)
    return poly[0]*2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding to our DataFrame
&lt;/h2&gt;

&lt;p&gt;The hurst value is computed with the last 14 close values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df['Hurst'] = df['close'].rolling(14).apply(hurst, raw=True)
df[10:20]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5huizfpyrqhicnwiekmy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5huizfpyrqhicnwiekmy.png" alt="Image description" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>programming</category>
    </item>
    <item>
      <title>Implementing Glowroot: A Hands-On Tech Review for Application Monitoring Mastery</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Mon, 05 Feb 2024 02:16:41 +0000</pubDate>
      <link>https://dev.to/czetsuya/implementing-glowroot-a-hands-on-tech-review-for-application-monitoring-mastery-31c0</link>
      <guid>https://dev.to/czetsuya/implementing-glowroot-a-hands-on-tech-review-for-application-monitoring-mastery-31c0</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In the realms of information technology and systems management, Application Performance Management (APM) involves monitoring and overseeing the performance and availability of software applications. APM aims to identify and diagnose intricate application performance issues to uphold a predefined level of service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Glowroot&lt;/strong&gt; is an open-source APM that facilitates a quicker resolution of application performance issues by helping us pinpoint the root causes. It supports applications running from Java 6 onwards.&lt;/p&gt;

&lt;h1&gt;
  
  
  Key Features
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Response Time Breakdown Charts: Visualizes the breakdown of response times for better analysis.&lt;/li&gt;
&lt;li&gt;Response Time Percentile Charts: Offers percentile charts to understand response time distribution.&lt;/li&gt;
&lt;li&gt;SQL Capture and Aggregation: Captures and aggregates SQL queries for in-depth analysis.&lt;/li&gt;
&lt;li&gt;Service Call Capture and Aggregation: Gathers and aggregates data on service calls for comprehensive insights.&lt;/li&gt;
&lt;li&gt;MBean Attribute Capture and Charts: Monitors and charts MBean attributes for performance evaluation.&lt;/li&gt;
&lt;li&gt;Configurable Alerting: Allows users to configure alerts based on specific criteria.&lt;/li&gt;
&lt;li&gt;Historical Rollup: Provides historical data rollup at different intervals (1m, 5m, 30m, 4h) with configurable retention settings.&lt;/li&gt;
&lt;li&gt;Full Support for Async Requests: Supports asynchronous requests that span multiple threads.&lt;/li&gt;
&lt;li&gt;Responsive UI with Mobile Support: User-friendly and responsive interface with mobile support for accessibility.&lt;/li&gt;
&lt;li&gt;Optional Central Collector: Offers the flexibility of an optional central collector for centralized data management.&lt;/li&gt;
&lt;li&gt;Supports Multiple Application Servers: Wildfly, JBoss EAP, Tomcat, TomEE, Jetty, Glassfish, Payara, WebLogic, WebSphere&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Central Collector
&lt;/h1&gt;

&lt;p&gt;The central collector collects runtime information from the registered services and offers a GUI for easy viewing. &lt;a href="https://github.com/glowroot/glowroot/wiki/Central-Collector-Installation" rel="noopener noreferrer"&gt;https://github.com/glowroot/glowroot/wiki/Central-Collector-Installation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;You can follow the steps above for running the GlowRoot central collector either as a standalone or as a docker image. For this section, I'll share how it can be run locally.&lt;br&gt;
Here's my docker-compose file for running glowroot-central with cassandra. Override username, password, and contactPoints in glowroot-central.properties.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;version: '3.8'&lt;/p&gt;

&lt;p&gt;networks:&lt;br&gt;
  tradewise-network:&lt;/p&gt;

&lt;p&gt;services:&lt;br&gt;
  cassandra:&lt;br&gt;
    image: cassandra:latest&lt;br&gt;
    container_name: cassandra&lt;br&gt;
    restart: unless-stopped&lt;br&gt;
    ports:&lt;br&gt;
      - "9042:9042"&lt;br&gt;
    networks:&lt;br&gt;
      - tradewise-network&lt;/p&gt;

&lt;p&gt;glowroot-central:&lt;br&gt;
    image: glowroot/glowroot-central:0.14.1&lt;br&gt;
    container_name: glowroot-central&lt;br&gt;
    restart: unless-stopped&lt;br&gt;
    volumes:&lt;br&gt;
      - ./glowroot-central.properties:/usr/share/glowroot-central/glowroot-central.properties&lt;br&gt;
    depends_on:&lt;br&gt;
      - cassandra&lt;br&gt;
    ports:&lt;br&gt;
      - "4000:4000"&lt;br&gt;
      - "8181:8181"&lt;br&gt;
    networks:&lt;br&gt;
      - tradewise-network&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Instrumentation&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;There are 4 ways in which we can integrate GlowRoot into our services  &lt;a href="https://glowroot.org/instrumentation.html" rel="noopener noreferrer"&gt;https://glowroot.org/instrumentation.html&lt;/a&gt;. For this exercise, we will focus on using the agent API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editing the pom.xml File
&lt;/h2&gt;

&lt;p&gt;In our Spring Boot project's pom.xml file, add the GlowRoot agent configuration.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;&amp;lt;plugin&amp;gt;&lt;br&gt;
    &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br&gt;
    &amp;lt;artifactId&amp;gt;maven-resources-plugin&amp;lt;/artifactId&amp;gt;&lt;br&gt;
    &amp;lt;executions&amp;gt;&lt;br&gt;
        &amp;lt;execution&amp;gt;&lt;br&gt;
            &amp;lt;id&amp;gt;glowroot-plugins&amp;lt;/id&amp;gt;&lt;br&gt;
            &amp;lt;phase&amp;gt;validate&amp;lt;/phase&amp;gt;&lt;br&gt;
            &amp;lt;goals&amp;gt;&lt;br&gt;
                &amp;lt;goal&amp;gt;copy-resources&amp;lt;/goal&amp;gt;&lt;br&gt;
            &amp;lt;/goals&amp;gt;&lt;br&gt;
            &amp;lt;configuration&amp;gt;&lt;br&gt;
                &amp;lt;outputDirectory&amp;gt;target/glowroot-plugins/&amp;lt;/outputDirectory&amp;gt;&lt;br&gt;
                &amp;lt;resources&amp;gt;&lt;br&gt;
                    &amp;lt;resource&amp;gt;&lt;br&gt;
                        &amp;lt;directory&amp;gt;glowroot-plugins&amp;lt;/directory&amp;gt;&lt;br&gt;
                        &amp;lt;filtering&amp;gt;false&amp;lt;/filtering&amp;gt;&lt;br&gt;
                    &amp;lt;/resource&amp;gt;&lt;br&gt;
                &amp;lt;/resources&amp;gt;&lt;br&gt;
            &amp;lt;/configuration&amp;gt;&lt;br&gt;
        &amp;lt;/execution&amp;gt;&lt;br&gt;
    &amp;lt;/executions&amp;gt;&lt;br&gt;
&amp;lt;/plugin&amp;gt;&lt;/p&gt;

&lt;p&gt;&amp;lt;plugin&amp;gt;&lt;br&gt;
    &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br&gt;
    &amp;lt;artifactId&amp;gt;maven-dependency-plugin&amp;lt;/artifactId&amp;gt;&lt;br&gt;
    &amp;lt;version&amp;gt;${maven-dependency-plugin.version}&amp;lt;/version&amp;gt;&lt;br&gt;
    &amp;lt;executions&amp;gt;&lt;br&gt;
        &amp;lt;execution&amp;gt;&lt;br&gt;
            &amp;lt;id&amp;gt;copy-glowroot-jar&amp;lt;/id&amp;gt;&lt;br&gt;
            &amp;lt;phase&amp;gt;prepare-package&amp;lt;/phase&amp;gt;&lt;br&gt;
            &amp;lt;goals&amp;gt;&lt;br&gt;
                &amp;lt;goal&amp;gt;copy&amp;lt;/goal&amp;gt;&lt;br&gt;
            &amp;lt;/goals&amp;gt;&lt;br&gt;
        &amp;lt;/execution&amp;gt;&lt;br&gt;
    &amp;lt;/executions&amp;gt;&lt;br&gt;
    &amp;lt;configuration&amp;gt;&lt;br&gt;
        &amp;lt;artifactItems&amp;gt;&lt;br&gt;
            &amp;lt;artifactItem&amp;gt;&lt;br&gt;
                &amp;lt;groupId&amp;gt;org.glowroot&amp;lt;/groupId&amp;gt;&lt;br&gt;
                &amp;lt;artifactId&amp;gt;glowroot-agent&amp;lt;/artifactId&amp;gt;&lt;br&gt;
                &amp;lt;version&amp;gt;${glowroot-agent.version}&amp;lt;/version&amp;gt;&lt;br&gt;
                &amp;lt;type&amp;gt;jar&amp;lt;/type&amp;gt;&lt;br&gt;
                &amp;lt;overWrite&amp;gt;false&amp;lt;/overWrite&amp;gt;&lt;br&gt;
                &amp;lt;outputDirectory&amp;gt;${project.build.directory}&amp;lt;/outputDirectory&amp;gt;&lt;br&gt;
                &amp;lt;destFileName&amp;gt;glowroot.jar&amp;lt;/destFileName&amp;gt;&lt;br&gt;
            &amp;lt;/artifactItem&amp;gt;&lt;br&gt;
        &amp;lt;/artifactItems&amp;gt;&lt;br&gt;
    &amp;lt;/configuration&amp;gt;&lt;br&gt;
&amp;lt;/plugin&amp;gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Preparing the Dockerfile&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;We need to add the GlowRoot files in the docker image.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;h1&gt;
  
  
  Base Image
&lt;/h1&gt;

&lt;p&gt;FROM eclipse-temurin:17-jdk-alpine&lt;br&gt;
LABEL author=CzetsuyaTech&lt;br&gt;
LABEL maintainer=CzetsuyaTech&lt;/p&gt;
&lt;h1&gt;
  
  
  Configuration
&lt;/h1&gt;

&lt;p&gt;WORKDIR /&lt;/p&gt;

&lt;p&gt;RUN addgroup --system czetsuyatech &amp;amp;&amp;amp; \&lt;br&gt;
    adduser --system czetsuyatech --ingroup czetsuyatech &amp;amp;&amp;amp; \&lt;br&gt;
    mkdir -p /glowroot /glowroot/tmp /glowroot/logs /glowroot/plugins &amp;amp;&amp;amp; \&lt;br&gt;
    echo '{ "web": { "bindAddress": "0.0.0.0" } }' &amp;gt; /glowroot/admin.json &amp;amp;&amp;amp; \&lt;br&gt;
    chown czetsuyatech:czetsuyatech -R /glowroot &amp;amp;&amp;amp; \&lt;br&gt;
    chmod -R 777 /glowroot&lt;/p&gt;

&lt;p&gt;USER czetsuyatech&lt;br&gt;
ADD --chown=czetsuyatech:czetsuyatech target/glowroot.jar /glowroot&lt;br&gt;
ADD --chown=czetsuyatech:czetsuyatech target/glowroot-plugins /glowroot/plugins&lt;/p&gt;
&lt;h1&gt;
  
  
  Service
&lt;/h1&gt;

&lt;p&gt;ADD --chown=czetsuyatech:czetsuyatech target/*.jar app.jar&lt;/p&gt;
&lt;h1&gt;
  
  
  Start
&lt;/h1&gt;

&lt;p&gt;ENV JAVA_JAR "/app.jar"&lt;br&gt;
ENV JAVA_OTHERS "-Xshare:off"&lt;br&gt;
ENV JAVA_OPTS ${JAVA_OPTS}&lt;br&gt;
ENV JAVA_MEM ${JAVA_MEM}&lt;/p&gt;

&lt;p&gt;RUN echo "exec java $JAVA_MEM $JAVA_OPTS $JAVA_OTHERS -jar $JAVA_JAR"&lt;br&gt;
ENTRYPOINT exec java $JAVA_MEM $JAVA_OPTS $JAVA_OTHERS -jar $JAVA_JAR&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Running the Spring Boot Service&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;To enable instrumentation in our instance, we need to specify the javaagent property. And to send information to the central collector we need to specify the central collector and give our instance an agent id.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;JAVA_OPTS=-javaagent:glowroot/glowroot.jar -Dglowroot.collector.address=localhost:8181 -server -Dglowroot.agent.id=TradewiseAI&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  GUI
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Usage
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7aki3psigfk8syzkk3d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7aki3psigfk8syzkk3d.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Transactions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Web
&lt;/h3&gt;

&lt;p&gt;To assess our REST Endpoints' performance, we access the "Web" section. In the provided example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;By choosing "Response Time," we can identify which HttpRequests and JDBC Queries are taking longer in the REST Endpoints.&lt;/li&gt;
&lt;li&gt;Opting for "Slow Traces" allows us to pinpoint the specific endpoint that consumes more time.&lt;/li&gt;
&lt;li&gt;Selecting "Queries" reveals insights into the queries made, indicating that using the count query is more resource-intensive compared to the select query. This information aids in optimizing and refining the performance of REST Endpoints.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;To analyze our background performance, we navigate to the "Background" section. In the given example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choosing "Response Time" allows us to identify which Job and Hibernate Queries are consuming more time in the background processes.&lt;/li&gt;
&lt;li&gt;Opting for "Slow Traces" reveals that some calls take more time, providing insights into areas that may require attention.&lt;/li&gt;
&lt;li&gt;Selecting "Queries" and filtering by the select query, we observe that it is called more frequently and takes longer, particularly when filtered by status. This information helps in understanding and addressing potential bottlenecks in the background processes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tu9jlngltybia6nfbbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tu9jlngltybia6nfbbz.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Startup
&lt;/h3&gt;

&lt;p&gt;To assess our startup performance, we can utilize the "Startup" section. In the provided example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;By choosing "Response Time," we can identify which startup and filter init processes consume more time.&lt;/li&gt;
&lt;li&gt;Opting for "Slow Traces" reveals the duration it takes for the context to initialize fully.&lt;/li&gt;
&lt;li&gt;Selecting "Queries" provides insights into the database queries. Some queries will be more resource intensive, with fewer calls but longer duration. This information aids in pinpointing specific areas for optimization within the startup processes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Errors
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Web
&lt;/h3&gt;

&lt;p&gt;To visualize errors in our REST endpoints, we can navigate to the "Web" section. In the following example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choosing "Error Messages" reveals errors of type XXXException.&lt;/li&gt;
&lt;li&gt;Opting for "Error Traces" provides details on the errors.&lt;/li&gt;
&lt;li&gt;Clicking on a specific error allows us to view the detailed trace, aiding in the understanding and resolution of the issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  JVM
&lt;/h2&gt;

&lt;p&gt;In our services' JVM, we can monitor and analyze memory status. This allows us to gain insights into the memory usage patterns, allocations, and overall health of the Java Virtual Machine, aiding in the effective management and optimization of our services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdo3q22y5422r8y15at5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdo3q22y5422r8y15at5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  MBeanTree
&lt;/h2&gt;

&lt;p&gt;The MBeanTree functionality in Glowroot proves invaluable in monitoring instance creation. For instance, to track thread-related metrics and identify potential Thread Leaks, users can navigate to the java.lang section, specifically under Threading. This allows for a detailed examination of thread-related information, aiding in the identification and resolution of potential thread-related issues, such as Thread Leaks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo68n8lnr4ex1wid1n5gb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo68n8lnr4ex1wid1n5gb.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Recommendation
&lt;/h1&gt;

&lt;p&gt;In a typical product infrastructure, Glowroot serves as our APM tool in each microservice. All microservices are instrumented to gather and transmit data to Glowroot, enhancing our understanding of system behavior. We've chosen Glowroot based on various criteria, including its license-free nature, alignment with the Java ecosystem, and straightforward instrumentation for microservices, ensuring a simple ramp-up and configuration process.&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>productivity</category>
      <category>java</category>
      <category>learning</category>
    </item>
    <item>
      <title>Navigating the Code: A Guide to Environment Variables, Configuration, and Feature Flags</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Thu, 14 Dec 2023 00:10:28 +0000</pubDate>
      <link>https://dev.to/czetsuya/navigating-the-code-a-guide-to-environment-variables-configuration-and-feature-flags-3c07</link>
      <guid>https://dev.to/czetsuya/navigating-the-code-a-guide-to-environment-variables-configuration-and-feature-flags-3c07</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93g7xodr20h2fvcvl07h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93g7xodr20h2fvcvl07h.jpg" alt="App Configuration" width="800" height="532"&gt;&lt;/a&gt;&lt;br&gt;
Determining the optimal location for storing essential information crucial for an application's functionality necessitates thoughtful planning. This guide aims to assist you in making informed decisions about where to store crucial data. Here, the term "application" encompasses any software created with code and deployed on various platforms and runtimes, irrespective of the underlying software architecture.&lt;/p&gt;

&lt;p&gt;A fundamental principle is to avoid hard-coding any values within your application. Instead, adopt the practice of retrieving all configuration settings from a distinct storage system, separate from the deployment environment. This approach allows for seamless modifications to configuration settings even after the application has been deployed, eliminating the need for redeployment. This separation ensures greater flexibility and facilitates the adjustment of critical parameters without disrupting the operational state of your deployed application.&lt;/p&gt;

&lt;p&gt;When considering the storage location for key/value pairs, it's crucial to evaluate the volatility of each pair—how frequently and by whom it might change. Reflect on the dynamic nature of the data and the responsibilities associated with its modification. This thoughtful approach will guide you in choosing an appropriate storage solution that aligns with the specific needs and characteristics of each key/value pair. By understanding the frequency and ownership of potential changes, you can make informed decisions that optimize the efficiency and maintainability of your data storage strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables
&lt;/h2&gt;

&lt;p&gt;Environment variables, characterized by their infrequent changes, typically occur only once per deployment. Common examples include sensitive information like usernames and passwords for databases, which are often stored securely in a Vault. Additionally, environment variables encompass essential details required for the system's initialization, such as environment specifics and the connection details for the application configuration store, which hosts additional settings. By utilizing environment variables for these foundational aspects, you ensure a secure and stable system setup while centralizing critical configuration information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application Configuration
&lt;/h2&gt;

&lt;p&gt;Application configuration, in contrast to environment variables, operates independently of deployments. It encompasses supplementary settings for the system that have the potential to change between deployments at runtime. This flexibility allows for real-time adjustments to the application's behavior and features, accommodating evolving requirements without the need for redeployment. By leveraging application configuration for these dynamic settings, you establish a modular and adaptable structure, enhancing the responsiveness of your system to changing runtime conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime User Settings
&lt;/h2&gt;

&lt;p&gt;Runtime user settings represent the most volatile category, as users can alter any key/value pair at any given moment. Therefore, the system must be designed to accommodate this high level of dynamism. Options for handling such volatility include implementing robust real-time validation mechanisms and ensuring the integrity and security of user-initiated changes. Additionally, the system should provide an intuitive user interface for managing these settings, enabling seamless customization while maintaining overall system stability and security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here are three options for handling configuration updates:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Re-read Configuration All the Time:&lt;/strong&gt; Continuously monitor and re-read the configuration, ensuring that the system remains up-to-date with any changes. This approach provides real-time responsiveness to modifications but may impose a continuous processing overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Re-read Configuration at Set Intervals (e.g., Every 15 Minutes):&lt;/strong&gt; Implement a periodic schedule to re-read the configuration at predefined intervals, such as every 15 minutes. This approach balances responsiveness with reduced processing overhead, making it suitable for scenarios where near-real-time updates are acceptable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Receive Push Notifications When New Application Configuration Is Available:&lt;/strong&gt; Set up a push notification mechanism to alert the system when new application configuration becomes available. This approach minimizes the need for continuous or scheduled re-reading, optimizing efficiency by updating the system only when changes occur. However, it requires a reliable notification infrastructure. Choosing among these options depends on the specific requirements of your system, considering factors such as the criticality of real-time updates, resource constraints, and the overall responsiveness desired for configuration changes.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>appconfig</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>Java XML Validation: How to Validate an XML Document against an XSD</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sat, 01 Jul 2023 11:58:22 +0000</pubDate>
      <link>https://dev.to/czetsuya/java-xml-validation-how-to-validate-an-xml-document-against-an-xsd-nn7</link>
      <guid>https://dev.to/czetsuya/java-xml-validation-how-to-validate-an-xml-document-against-an-xsd-nn7</guid>
      <description>&lt;p&gt;Introducing our Java XML Validation Project! Master the art of validating XML effortlessly against XSD in a Java environment. Gain hands-on experience, ensuring the integrity and conformity of your data.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Introduction
&lt;/h1&gt;

&lt;p&gt;Introducing our Java XML Validation Project! Gain hands-on experience in validating XML documents against XSD with ease. This project serves as a practical guide, equipping you with the necessary knowledge and tools to ensure the integrity and conformity of your XML data in a Java environment. Discover the simplicity of our step-by-step approach, which walks you through the entire validation process. Master the art of validating XML effortlessly and unlock a new level of confidence in your data. Join us on this journey as we empower you to harness the power of XML validation in your Java projects. &lt;/p&gt;

&lt;h1&gt;
  
  
  2. Generating Java Classes from XSD
&lt;/h1&gt;

&lt;p&gt;For this exercise, we will be using an XSD from w3schools  &lt;a href="https://www.w3schools.com/xml/schema_example.asp"&gt;https://www.w3schools.com/xml/schema_example.asp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To be able to generate the Java classes from XSD, we will be using Eclipse Enterprise Edition. IntelliJ can also do it with the Jakarta plugin, but Eclipse performs better, especially when dealing with XJB files.&lt;/p&gt;

&lt;p&gt;IntelliJ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fclhm3821xl2sfg2b33lj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fclhm3821xl2sfg2b33lj.png" alt="Image description" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Eclipse JAXB Classes Generator&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft98dbv71fkwtl60jak2r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft98dbv71fkwtl60jak2r.png" alt="Image description" width="800" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Generated Classes&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fceuz1p5bxrwxqkepueka.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fceuz1p5bxrwxqkepueka.png" alt="Image description" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Dependencies
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.fasterxml.jackson.dataformat&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jackson-dataformat-xml&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;jakarta.activation&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jakarta.activation-api&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.1.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;jakarta.xml.bind&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jakarta.xml.bind-api&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;${jakarta.version}&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.glassfish.jaxb&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jaxb-runtime&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;${jakarta.version}&amp;lt;/version&amp;gt;
    &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  4. Core Classes
&lt;/h1&gt;

&lt;p&gt;To run the validations and throw the appropriate exception or name of the tag where a constraint is violated, we will need the following classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  4.1 ResourceResolver
&lt;/h2&gt;

&lt;p&gt;To import an external XSD, for example, if we will be using the XML digital signature.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;xs:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="xmldsig-core-schema.xsd"/&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class ResourceResolver implements LSResourceResolver {

  private static final Logger LOGGER = LoggerFactory.getLogger(ResourceResolver.class);

  public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {

    // note: in this sample, the XSD's are expected to be in the root of the classpath
    InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("xsd/" + systemId);
    return new Input(publicId, systemId, resourceAsStream);
  }

  static class Input implements LSInput {

    private String publicId;

    private String systemId;

    public String getStringData() {

      String textDataFromFile;

      try {
        BufferedReader bufferReader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder stringBuilder = new StringBuilder();
        String eachStringLine;

        while ((eachStringLine = bufferReader.readLine()) != null) {
          stringBuilder.append(eachStringLine).append("\n");
        }
        textDataFromFile = stringBuilder.toString();

        return textDataFromFile;

      } catch (IOException e) {
        LOGGER.error("Fail reading from inputStream={}", e.getMessage());
        return null;
      }
    }
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4.2 SaxErrorHandler
&lt;/h2&gt;

&lt;p&gt;For us to be able to know the tag where a constraint exception is thrown, we need to define a custom exception that accepts an XMLStreamReader object where we can get the localName property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class SaxErrorHandler implements ErrorHandler {

  private XMLStreamReader reader;

  public SaxErrorHandler(XMLStreamReader reader) {
    this.reader = reader;
  }

  @Override
  public void error(SAXParseException e) {
    warning(e);
  }

  @Override
  public void fatalError(SAXParseException e) {
    warning(e);
  }

  @Override
  public void warning(SAXParseException e) {

    throw new XmlValidationException(reader.getLocalName());
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4.3 Miscellaneous Classes
&lt;/h2&gt;

&lt;p&gt;We will also be needing utility classes for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a JAXBElement&lt;/li&gt;
&lt;li&gt;Convert JAXBElement to XMLStreamReader&lt;/li&gt;
&lt;li&gt;Defining the correct marshaller object&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These classes are all available in the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  4.4 XmlSchemaValidator Interface
&lt;/h2&gt;

&lt;p&gt;And finally, the interface that pieces together all components to do the validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default void validateXMLSchema(String xsdPath, JAXBElement&amp;lt;?&amp;gt; xml) {

  Validator validator = null;
  try {
    SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
    factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
    factory.setResourceResolver(new ResourceResolver());

    Schema schema = factory.newSchema(ResourceUtils.getFile(xsdPath));
    validator = schema.newValidator();
    XMLStreamReader xmlStreamReader = asStreamReader(xml);

    ErrorHandler errorHandler = new SaxErrorHandler(xmlStreamReader);
    validator.setErrorHandler(errorHandler);

    validator.validate(new StAXSource(xmlStreamReader));

  } catch (IOException | SAXException | JAXBException | XMLStreamException e) {

    var xmlEx = getXmlValidationExceptionIfPresent(e);
    throw new InvalidXmlException(xmlEx.getLocalName(), e);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  5. Source Code / Git Repository
&lt;/h1&gt;

&lt;p&gt;The complete source code for this project is available for my sponsors at &lt;a href="https://github.com/czetsuyatech/java-xml-validation-by-xsd"&gt;https://github.com/czetsuyatech/java-xml-validation-by-xsd&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sponsorship link: &lt;a href="https://github.com/sponsors/czetsuya"&gt;https://github.com/sponsors/czetsuya&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Project Screenshot&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxybdgpwcqcu0pnhv9mkf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxybdgpwcqcu0pnhv9mkf.png" alt="Image description" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>xml</category>
      <category>xsd</category>
    </item>
    <item>
      <title>Unlocking Google Calendar API with Keycloak: A Developer's Guide</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sat, 20 May 2023 07:52:14 +0000</pubDate>
      <link>https://dev.to/czetsuya/unlocking-google-calendar-api-with-keycloak-a-developers-guide-nkn</link>
      <guid>https://dev.to/czetsuya/unlocking-google-calendar-api-with-keycloak-a-developers-guide-nkn</guid>
      <description>&lt;h1&gt;
  
  
  1. Overview
&lt;/h1&gt;

&lt;p&gt;Unlock the power of Keycloak for seamless authentication and authorization in your applications. This comprehensive guide is designed for software developers who want to integrate Keycloak into their systems to enable secure user login and token exchange with Google. &lt;/p&gt;

&lt;p&gt;By following step-by-step instructions and best practices, you'll learn how to leverage Keycloak's robust features to facilitate seamless authentication and obtain a Google token. With this token, you can effortlessly access the Google Calendar API and unlock a wealth of functionality for your application. Enhance user experience and streamline calendar integration - start implementing Keycloak and Google integration today.&lt;/p&gt;

&lt;p&gt;You need prior knowledge with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google&lt;/li&gt;
&lt;li&gt;Keycloak&lt;/li&gt;
&lt;li&gt;Spring Boot&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  2. Google
&lt;/h1&gt;

&lt;p&gt;Before we can do the integration with Keycloak we need to create a project in Google Console, create a credential and obtain OAuth client id and secret.&lt;/p&gt;

&lt;p&gt;If you haven't done so, you can create a Google console account at  &lt;a href="https://cloud.google.com/cloud-console"&gt;https://cloud.google.com/cloud-console&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2.1 Creating the Google Cloud Project
&lt;/h2&gt;

&lt;p&gt;At the top left of your Google Cloud Console, click the project and click New Project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f7pwciuh7wp4n5t38jt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f7pwciuh7wp4n5t38jt.png" alt="Image description" width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I call this project "Lab".&lt;/p&gt;

&lt;h2&gt;
  
  
  2.2 Create OAuth2 Client
&lt;/h2&gt;

&lt;p&gt;The next step is to create the OAuth2 client credential that will give us the client id and secret. This combination will be used when we set up the Google Identity provider in Keycloak.&lt;/p&gt;

&lt;p&gt;With the Lab project selected, find API &amp;amp; Services / Credentials in the menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00auccu017eizp2gmmyr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00auccu017eizp2gmmyr.png" alt="Image description" width="800" height="807"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Create Credentials with the following inputs:&lt;/p&gt;

&lt;p&gt;Type: OAuth client ID&lt;br&gt;
Application type: Web application&lt;br&gt;
Name: Lab&lt;/p&gt;

&lt;p&gt;There are two important entries in this section:&lt;/p&gt;

&lt;p&gt;Authorized Javascript origins&lt;/p&gt;

&lt;p&gt;This should be the URL where the request for login will come from.&lt;/p&gt;

&lt;p&gt;Authorized redirect URIs&lt;/p&gt;

&lt;p&gt;This entry is a common cause of problems if not properly set up. This is where Google will redirect the user after a successful login.&lt;/p&gt;

&lt;p&gt;Some common redirect URIs.&lt;/p&gt;

&lt;p&gt;Amazon Cognito: &lt;a href="https://lab.auth.ap-southeast-1.amazoncognito.com/oauth2/idpresponse"&gt;https://lab.auth.ap-southeast-1.amazoncognito.com/oauth2/idpresponse&lt;/a&gt;&lt;br&gt;
Postman: &lt;a href="https://www.getpostman.com/oauth2/callback"&gt;https://www.getpostman.com/oauth2/callback&lt;/a&gt;&lt;br&gt;
Keycloak broker: &lt;a href="http://localhost:8080/realms/czetsuyatech/broker/google/endpoint"&gt;http://localhost:8080/realms/czetsuyatech/broker/google/endpoint&lt;/a&gt;&lt;br&gt;
Keycloak token: &lt;a href="http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token"&gt;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you have a redirect issue, this is one of the first things you may want to check.&lt;/p&gt;

&lt;p&gt;This is how my Lab client looks like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdbhmhk1u8gw5631b383.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdbhmhk1u8gw5631b383.png" alt="Image description" width="800" height="1179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the same page, you should be able to see the client id and secret. Take note because we will need them later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhx392nxc34knlq2h98hd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhx392nxc34knlq2h98hd.png" alt="Image description" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2.3 Setup OAuth Consent Screen
&lt;/h2&gt;

&lt;p&gt;This section will ask for several application-related settings. You can enter values as you see fit, but normally I set the following.&lt;/p&gt;

&lt;p&gt;Non-sensitive scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;auth-userinfo-email&lt;/li&gt;
&lt;li&gt;auth-userinfo-profile&lt;/li&gt;
&lt;li&gt;openid
Don't forget to add a test user. We will use it to login later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61xi2r6zkimt7tx2hnwp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61xi2r6zkimt7tx2hnwp.png" alt="Image description" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  3. Keycloak
&lt;/h1&gt;

&lt;p&gt;For this exercise, we need to create a new Keycloak realm and client. We must enable authentication in the client.&lt;/p&gt;
&lt;h2&gt;
  
  
  3.1 Client
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4hqne0pzftaxnv2ioxsr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4hqne0pzftaxnv2ioxsr.png" alt="Image description" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enabling authentication will give us access to Keycloak's client and secret, which we will need later for testing. Take note of it.&lt;/p&gt;

&lt;p&gt;We also need to download the Adapter config of our newly created client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdlo3u7i4tgunx9zur6cs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdlo3u7i4tgunx9zur6cs.png" alt="Image description" width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The downloaded config should look like this. Save it as we will use it in our Spring Boot application later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "realm": "czetsuyatech",
  "auth-server-url": "http://localhost:8080/",
  "ssl-required": "external",
  "resource": "web-front",
  "credentials": {
    "secret": "BxYWD11qCAW6ZybPTy6b9M8ej0thTxxx"
  },
  "confidential-port": 0
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3.2 Identity Provider
&lt;/h2&gt;

&lt;p&gt;Identity providers are third-party services that allow users to log in to Keycloak. In our example, we will do an authorization as well by exchanging the Keycloak token to Google to allow us to call Google API services such as Calendar and Gmail.&lt;/p&gt;

&lt;p&gt;So login to Keycloak as admin, click Identity Providers on the left side, Click Add Provider, and select Google.&lt;/p&gt;

&lt;p&gt;In the Client ID and secret fields, enter the values that we saved when we were configuring the Google client earlier.&lt;/p&gt;

&lt;p&gt;Under Advance settings, change the value of Scopes to "&lt;em&gt;email profile openid &lt;a href="https://www.googleapis.com/auth/calendar"&gt;https://www.googleapis.com/auth/calendar&lt;/a&gt; &lt;a href="https://www.googleapis.com/auth/calendar.events"&gt;https://www.googleapis.com/auth/calendar.events&lt;/a&gt; &lt;a href="https://www.googleapis.com/auth/gmail.readonly"&gt;https://www.googleapis.com/auth/gmail.readonly&lt;/a&gt;&lt;/em&gt;", without double quotes. This will give us permission to call list endpoints from Calendar and Gmail APIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr79unbkhq7322ou8tx11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr79unbkhq7322ou8tx11.png" alt="Image description" width="800" height="632"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3.3 Permissions
&lt;/h2&gt;

&lt;p&gt;In this section, we will give our client permission to exchange tokens with the newly created Google identity provider.&lt;/p&gt;

&lt;p&gt;On the identity provider page, click the Permissions tab. Toggle Permissions enabled to ON. Under the Permission list, click token-exchange.&lt;/p&gt;

&lt;p&gt;Set the Decision strategy to Unanimous. Click Save.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypp6qqvktgxmvcoraj7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypp6qqvktgxmvcoraj7d.png" alt="Image description" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to create the permission scope. At the top-left of the page, click Client details. It should redirect you to the Authorization tab. Open the Policies sub-tab. Create a new policy with the following values:&lt;/p&gt;

&lt;p&gt;Type: Client&lt;br&gt;
Name: google-token-exchange&lt;br&gt;
Clients: web-front (the Keycloak client we created earlier)&lt;br&gt;
Logic: Positive&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ocn5te2c92kzfszr1ut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ocn5te2c92kzfszr1ut.png" alt="Image description" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go back to your Google identity provider’s permission page and set the Policies to the one we just created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flf69pcrjffxo8prgiwfa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flf69pcrjffxo8prgiwfa.png" alt="Image description" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The setup should now be ready to exchange tokens.&lt;/p&gt;
&lt;h1&gt;
  
  
  4. Testing
&lt;/h1&gt;

&lt;p&gt;To test the exchange of tokens between Keycloak and Google, I have created a Postman collection where we can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate a user&lt;/li&gt;
&lt;li&gt;Exchange Keycloak to Google token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we proceed with the testing, we need to run a Keycloak instance. I have prepared a docker-compose to do that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxti4bl4amtfthj44pwc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxti4bl4amtfthj44pwc.png" alt="Image description" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to authenticate our user, so create a new POST request in Postman and use OAuth2 authorization. Set the URL to &lt;a href="http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token"&gt;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7w6egwndvph1aiingsh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7w6egwndvph1aiingsh.png" alt="Image description" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Be sure to set the client id and secret to the ones we saved earlier when we created the Keycloak client.&lt;/p&gt;

&lt;p&gt;For the truncated values:&lt;/p&gt;

&lt;p&gt;Callback URL: &lt;a href="https://www.getpostman.com/oauth2/callback"&gt;https://www.getpostman.com/oauth2/callback&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Auth URL: &lt;a href="http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/auth"&gt;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/auth&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Access Token URL: &lt;a href="http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token"&gt;http://localhost:8080/realms/czetsuyatech/protocol/openid-connect/token&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Get New Access Token. It should redirect us to a Google login page, use the test email address that you added earlier when creating the Google client.&lt;/p&gt;

&lt;p&gt;The next page will show you the scopes that will be permitted for your user. We defined this when we created the Google identity provider earlier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6x4q39v0evkx1xniduf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6x4q39v0evkx1xniduf.png" alt="Image description" width="800" height="889"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Allow.&lt;/p&gt;

&lt;p&gt;Copy the access token and paste it on a bearerToken variable. The succeeding requests will have Authorization Type = Bearer and will use this value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce5fkzuuyjxdw9jlks8q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce5fkzuuyjxdw9jlks8q.png" alt="Image description" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Body of the request must have the following URL encoded parameters:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11pv1gu93k9p5opk1yvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11pv1gu93k9p5opk1yvj.png" alt="Image description" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Send. It should return with the following response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lsentnlwlf38yea2df6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lsentnlwlf38yea2df6.png" alt="Image description" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the token that we can use to access Google APIs.&lt;/p&gt;

&lt;p&gt;Obviously, we need to automate the process if we wanted this feature in our application. Thus, I have created a Spring project to do it.&lt;/p&gt;
&lt;h1&gt;
  
  
  5. Spring Project
&lt;/h1&gt;

&lt;p&gt;This project demonstrates the token-exchange feature available on Keycloak. With this, we can exchange Keycloak for Google’s token, which we can use to call Google APIs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: This project is only available for my sponsors &lt;a href="https://github.com/sponsors/czetsuya"&gt;https://github.com/sponsors/czetsuya&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  5.1 Available Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Get the bearer token from the request and store it in a Bean that can be autowired.&lt;/li&gt;
&lt;li&gt;Exchange Keycloak token to Google, this will allow us to call Google APIs (Calendar, Gmail).&lt;/li&gt;
&lt;li&gt;Use Spring exchange to provide abstraction in calling Google APIs.&lt;/li&gt;
&lt;li&gt;Endpoints to list Calendar events and Gmail messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The heart of the application exchanges tokens between Keycloak and Google.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Component
public class GoogleTokenExchange implements TokenExchange {

  @Override
  public String exchangeToken(String accessToken) {

    AuthzClient authzClient = AuthzClient.create();
    Configuration keycloakConfig = authzClient.getConfiguration();
    String baseUrl = authzClient.getServerConfiguration().getTokenEndpoint();

    RestTemplate restTemplate = new RestTemplate();

    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-Type", MediaType.APPLICATION_FORM_URLENCODED.toString());
    headers.add("Accept", MediaType.APPLICATION_JSON.toString());

    MultiValueMap&amp;lt;String, String&amp;gt; requestBody = new LinkedMultiValueMap&amp;lt;&amp;gt;();
    requestBody.add("client_id", keycloakConfig.getResource());
    requestBody.add("client_secret", keycloakConfig.getCredentials().get("secret").toString());
    requestBody.add("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange");
    requestBody.add("subject_token_type", "urn:ietf:params:oauth:token-type:access_token");
    requestBody.add("subject_token", accessToken);
    requestBody.add("requested_issuer", "google");

    HttpEntity formEntity = new HttpEntity&amp;lt;&amp;gt;(requestBody, headers);

    ResponseEntity&amp;lt;AccessTokenResponse&amp;gt; response = restTemplate.exchange(baseUrl, HttpMethod.POST, formEntity,
        AccessTokenResponse.class);

    return response.getBody().getToken();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5.2 Testing
&lt;/h2&gt;

&lt;p&gt;If you have subscribed as my sponsor, you will be given access to my repository and proceed with this testing.&lt;/p&gt;

&lt;p&gt;The project provides two endpoints, to test calling list calendar events and Gmail messages from Google. Matching Postman requests are also available to make testing easier.&lt;/p&gt;

&lt;p&gt;As we have done earlier, we need to get an access token from Keycloak and set it as bearerToken environment variable inside Postman.&lt;/p&gt;

&lt;p&gt;Get Calendar Events Response&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihegcw8glo30slgohqr1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihegcw8glo30slgohqr1.png" alt="Image description" width="800" height="652"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get Gmail Messages&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzk1skgdifekuyt62ybri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzk1skgdifekuyt62ybri.png" alt="Image description" width="800" height="729"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  6. Source Code
&lt;/h1&gt;

&lt;p&gt;All the resources used in this article and demo are available on the GitHub repository &lt;a href="https://github.com/czetsuyatech/auth-exchange"&gt;https://github.com/czetsuyatech/auth-exchange&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring project source code&lt;/li&gt;
&lt;li&gt;Docker/compose file&lt;/li&gt;
&lt;li&gt;Keycloak realm&lt;/li&gt;
&lt;li&gt;Postman collection for testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  7. References
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange"&gt;https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.keycloak.org/docs/latest/server_development/index.html#retrieving-external-idp-tokens"&gt;https://www.keycloak.org/docs/latest/server_development/index.html#retrieving-external-idp-tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/calendar"&gt;https://developers.google.com/calendar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/gmail/api/guides"&gt;https://developers.google.com/gmail/api/guides&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>keycloak</category>
      <category>googleapi</category>
      <category>tokenexchange</category>
      <category>spring</category>
    </item>
    <item>
      <title>Implementing X509 Certificate Validation in Java: A Step-by-Step Guide</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sun, 07 May 2023 05:09:15 +0000</pubDate>
      <link>https://dev.to/czetsuya/implementing-x509-certificate-validation-in-java-a-step-by-step-guide-1963</link>
      <guid>https://dev.to/czetsuya/implementing-x509-certificate-validation-in-java-a-step-by-step-guide-1963</guid>
      <description>&lt;p&gt;Unlock the power of secure communication with Java! Learn how to implement X509 certificate validation simply and straightforwardly, step-by-step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;An X.509 certificate is a digital certificate used to verify a particular entity's identity, such as a website or an individual. X.509 certificates are widely used in various applications, including secure communication protocols like HTTPS, SSL, and TLS.&lt;/p&gt;

&lt;p&gt;An X.509 certificate contains several pieces of information, including the identity of the entity being verified, the public key that is associated with that entity, and various other pieces of metadata that provide additional information about the certificate itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Importance
&lt;/h2&gt;

&lt;p&gt;One of the key benefits of X.509 certificates is that they allow entities to authenticate themselves to other parties securely and reliably. This can help to prevent various types of attacks, including man-in-the-middle attacks, where an attacker intercepts and modifies communication between two parties to steal sensitive information.&lt;/p&gt;

&lt;p&gt;Overall, X.509 certificates play a critical role in modern security infrastructure and are essential to many different types of secure communication protocols.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;You must have the following installed on your computer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Maven&lt;/li&gt;
&lt;li&gt;IDE (IntellIJ / Visual Studio)&lt;/li&gt;
&lt;li&gt;Java 17+&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dependencies
&lt;/h2&gt;

&lt;p&gt;This project uses the following dependencies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bcpkix-jdk15on - certificate revocation check&lt;/li&gt;
&lt;li&gt;spring-boot-starter-test (for testing)&lt;/li&gt;
&lt;li&gt;lombok (for logging)&lt;/li&gt;
&lt;li&gt;Certificates from &lt;a href="https://www.digicert.com/kb/digicert-root-certificates.htm"&gt;https://www.digicert.com/kb/digicert-root-certificates.htm&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Code
&lt;/h2&gt;

&lt;p&gt;The certificate validation process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjo5re8jpbffczc99kfcm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjo5re8jpbffczc99kfcm.png" alt="Image description" width="441" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To implement the validation, we will introduce a bean where we can define the:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Certificate that we are validating&lt;/li&gt;
&lt;li&gt;The pem file that we can cross-check #1&lt;/li&gt;
&lt;li&gt;Root certificate to check if the certificate is already revoked&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's examine the code.&lt;br&gt;
The CertificateBundle class is just a simple Java bean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Getter
@Setter
@Builder
public class CertificateBundle {

  // Certificate extracted from the message
  private X509Certificate certificate;

  // Certificate loaded from the application must match the certificate
  private X509Certificate confirmationCertificate;

  // RootCertificate of the certificate
  private X509Certificate rootCertificate;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To validate the &lt;strong&gt;certificate expiration&lt;/strong&gt;, we need to extract the X509Certificate object and perform a time comparison against the value of getNotAfter(). Here, we're making sure that the current date is greater than the certificate's expiry date.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Date expiresOn = cert.getNotAfter();
Date now = new Date();

if (now.getTime() &amp;gt; expiresOn.getTime()) {
  throw new ExpiredCertificateException();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Comparing digital signature against a PEM file&lt;/strong&gt;. In most fintech applications, there is a need to sign the message before sending it to another service. Upon receiving the message, the server needs to extract the public certificate embedded in it and compare it to a PEM file. This PEM file is a public key of the private key certificate used to sign the message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhuzd6ogloenj1giz01uz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhuzd6ogloenj1giz01uz.png" alt="Image description" width="437" height="301"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;boolean match = Arrays.equals(cert1.getPublicKey().getEncoded(),
    cert2.getPublicKey().getEncoded());

if (!match) {
  throw new MismatchCertificateException("Embedded certificate does not match the given PEM");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, we do the &lt;strong&gt;revocation check&lt;/strong&gt;. This process allows us to know whether a website's certificate is trustworthy or not. It uses the root certificate that we used for signing as the trust anchor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {
  List&amp;lt;X509Certificate&amp;gt; certs = Collections.singletonList(cb.getCertificate());
  CertificateFactory cf = CertificateFactory.getInstance("X509");
  CertPath cp = cf.generateCertPath(certs);

  // load the root CA cb
  X509Certificate rootCACert = cb.getRootCertificate();

  // init trusted certs
  TrustAnchor ta = new TrustAnchor(rootCACert, null);
  Set&amp;lt;TrustAnchor&amp;gt; trustedCerts = new HashSet&amp;lt;&amp;gt;();
  trustedCerts.add(ta);

  // init PKIX parameters
  PKIXParameters params = new PKIXParameters(trustedCerts);

  // load the CRL
  params.addCertStore(CertStore.getInstance("Collection",
      new CollectionCertStoreParameters(getX509Crls(cf, getCrl(cb.getCertificate())))));

  // perform validation
  CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
  PKIXCertPathValidatorResult cpvResult = (PKIXCertPathValidatorResult) cpv.validate(cp,
      params);
  X509Certificate trustedCert = cpvResult.getTrustAnchor().getTrustedCert();

  if (trustedCert == null) {
    log.error("Trusted certificate not found");
    throw new CertificateException("Trusted certificate not found");

  } else {
    log.debug("Trusted CA DN = " + trustedCert.getSubjectX500Principal());
  }

} catch (InvalidAlgorithmParameterException | java.security.cert.CertificateException |
         CRLException |
         NoSuchAlgorithmException |
         CertPathValidatorException |
         IOException e) {
  throw new RevokedCertificateException(e);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>java</category>
      <category>digitalsigning</category>
      <category>digitalcertificate</category>
    </item>
    <item>
      <title>Java-Based Implementation of Digital Signatures and X509 Certificates for Secure XML Document Processing</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Wed, 03 May 2023 03:34:57 +0000</pubDate>
      <link>https://dev.to/czetsuya/java-based-implementation-of-digital-signatures-and-x509-certificates-for-secure-xml-document-processing-25j1</link>
      <guid>https://dev.to/czetsuya/java-based-implementation-of-digital-signatures-and-x509-certificates-for-secure-xml-document-processing-25j1</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;XML Signature is a standard in the digital signature that allows the authentication and validation of data being sent as XML objects in web service communication. Java 6 introduces Java XML Digital Signature API, which allows the generation and validation of signatures in an XML message.&lt;/p&gt;

&lt;p&gt;According to RFC 2828, a digital signature is a cryptographic value added to a data object to enable the recipient to verify its origin and integrity. The cryptographic digital signature API in JDK 6 is described in detail in a security lesson in the Java Tutorial.&lt;/p&gt;

&lt;p&gt;An XML signature is a type of digital signature that has unique properties. It outlines a process and format for generating digital signatures in the XML format and offers various advanced features. For example, it allows for the signing of multiple pieces of data, either in binary or XML format, and it enables the use of various cryptographic signature algorithms.&lt;/p&gt;

&lt;p&gt;An XML signature is capable of signing arbitrary data, whether it is in XML or binary format. Furthermore, it can sign only a portion or a subset of an XML document instead of the entire document. Uniform Resource Identifiers (URIs) identify the data to be signed. XML signatures can be categorized as one or more of three types based on their properties.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A detached signature is a signature that covers data that exists outside of the Signature element. This data may be located in a different document or file or present within the same document as the signature, such as a related element.&lt;/li&gt;
&lt;li&gt;An enveloping signature is a signature that covers data that is contained within the Signature element itself. In other words, the data to be signed is enclosed within the signature element and the signature itself.&lt;/li&gt;
&lt;li&gt;An enveloped signature is a type of signature that covers data containing the Signature element. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Java API Architecture
&lt;/h2&gt;

&lt;p&gt;The JSR 105 Java Community Process program established the Java XML Digital Signature API to encompass all necessary and suggested features of the W3C's XML-Signature Syntax and Processing Recommendation. The API employs the Java Cryptography Service Provider Architecture, which enables the development of service provider implementations. Service providers create an XML mechanism that identifies the implementation's XML-parsing mechanism. In Java SE 6's implementation, Sun's service provider supports the Document Object Model (DOM) mechanism. For additional details on service providers, refer to the XML Digital Signature API overview.&lt;/p&gt;

&lt;h2&gt;
  
  
  XML Signature
&lt;/h2&gt;

&lt;p&gt;This article will utilize an example of an enveloped XML signature, which means that it is generated over the contents of an XML document, specifically a sample &lt;strong&gt;puchase-order.xml.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0"?&amp;gt;
&amp;lt;PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20"&amp;gt;
  &amp;lt;Address Type="Shipping"&amp;gt;
    &amp;lt;Name&amp;gt;Ellen Adams&amp;lt;/Name&amp;gt;
    &amp;lt;Street&amp;gt;123 Maple Street&amp;lt;/Street&amp;gt;
    &amp;lt;City&amp;gt;Mill Valley&amp;lt;/City&amp;gt;
    &amp;lt;State&amp;gt;CA&amp;lt;/State&amp;gt;
    &amp;lt;Zip&amp;gt;10999&amp;lt;/Zip&amp;gt;
    &amp;lt;Country&amp;gt;USA&amp;lt;/Country&amp;gt;
  &amp;lt;/Address&amp;gt;
  &amp;lt;Address Type="Billing"&amp;gt;
    &amp;lt;Name&amp;gt;Tai Yee&amp;lt;/Name&amp;gt;
    &amp;lt;Street&amp;gt;8 Oak Avenue&amp;lt;/Street&amp;gt;
    &amp;lt;City&amp;gt;Old Town&amp;lt;/City&amp;gt;
    &amp;lt;State&amp;gt;PA&amp;lt;/State&amp;gt;
    &amp;lt;Zip&amp;gt;95819&amp;lt;/Zip&amp;gt;
    &amp;lt;Country&amp;gt;USA&amp;lt;/Country&amp;gt;
  &amp;lt;/Address&amp;gt;
  &amp;lt;DeliveryNotes&amp;gt;Please leave packages in shed by driveway.&amp;lt;/DeliveryNotes&amp;gt;
  &amp;lt;Items&amp;gt;
    &amp;lt;Item PartNumber="872-AA"&amp;gt;
      &amp;lt;ProductName&amp;gt;Lawnmower&amp;lt;/ProductName&amp;gt;
      &amp;lt;Quantity&amp;gt;1&amp;lt;/Quantity&amp;gt;
      &amp;lt;USPrice&amp;gt;148.95&amp;lt;/USPrice&amp;gt;
      &amp;lt;Comment&amp;gt;Confirm this is electric&amp;lt;/Comment&amp;gt;
    &amp;lt;/Item&amp;gt;
    &amp;lt;Item PartNumber="926-AA"&amp;gt;
      &amp;lt;ProductName&amp;gt;Baby Monitor&amp;lt;/ProductName&amp;gt;
      &amp;lt;Quantity&amp;gt;2&amp;lt;/Quantity&amp;gt;
      &amp;lt;USPrice&amp;gt;39.98&amp;lt;/USPrice&amp;gt;
      &amp;lt;ShipDate&amp;gt;1999-05-21&amp;lt;/ShipDate&amp;gt;
    &amp;lt;/Item&amp;gt;
  &amp;lt;/Items&amp;gt;
&amp;lt;/PurchaseOrder&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;XML Example 1.&lt;/p&gt;

&lt;p&gt;After applying the digital signature, our message should look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&amp;gt;
&amp;lt;PurchaseOrder OrderDate="1999-10-20" PurchaseOrderNumber="99503"&amp;gt;
  &amp;lt;Address Type="Shipping"&amp;gt;
    &amp;lt;Name&amp;gt;Ellen Adams&amp;lt;/Name&amp;gt;
    &amp;lt;Street&amp;gt;123 Maple Street&amp;lt;/Street&amp;gt;
    &amp;lt;City&amp;gt;Mill Valley&amp;lt;/City&amp;gt;
    &amp;lt;State&amp;gt;CA&amp;lt;/State&amp;gt;
    &amp;lt;Zip&amp;gt;10999&amp;lt;/Zip&amp;gt;
    &amp;lt;Country&amp;gt;USA&amp;lt;/Country&amp;gt;
  &amp;lt;/Address&amp;gt;
  &amp;lt;Address Type="Billing"&amp;gt;
    &amp;lt;Name&amp;gt;Tai Yee&amp;lt;/Name&amp;gt;
    &amp;lt;Street&amp;gt;8 Oak Avenue&amp;lt;/Street&amp;gt;
    &amp;lt;City&amp;gt;Old Town&amp;lt;/City&amp;gt;
    &amp;lt;State&amp;gt;PA&amp;lt;/State&amp;gt;
    &amp;lt;Zip&amp;gt;95819&amp;lt;/Zip&amp;gt;
    &amp;lt;Country&amp;gt;USA&amp;lt;/Country&amp;gt;
  &amp;lt;/Address&amp;gt;
  &amp;lt;DeliveryNotes&amp;gt;Please leave packages in shed by driveway.&amp;lt;/DeliveryNotes&amp;gt;
  &amp;lt;Items&amp;gt;
    &amp;lt;Item PartNumber="872-AA"&amp;gt;
      &amp;lt;ProductName&amp;gt;Lawnmower&amp;lt;/ProductName&amp;gt;
      &amp;lt;Quantity&amp;gt;1&amp;lt;/Quantity&amp;gt;
      &amp;lt;USPrice&amp;gt;148.95&amp;lt;/USPrice&amp;gt;
      &amp;lt;Comment&amp;gt;Confirm this is electric&amp;lt;/Comment&amp;gt;
    &amp;lt;/Item&amp;gt;
    &amp;lt;Item PartNumber="926-AA"&amp;gt;
      &amp;lt;ProductName&amp;gt;Baby Monitor&amp;lt;/ProductName&amp;gt;
      &amp;lt;Quantity&amp;gt;2&amp;lt;/Quantity&amp;gt;
      &amp;lt;USPrice&amp;gt;39.98&amp;lt;/USPrice&amp;gt;
      &amp;lt;ShipDate&amp;gt;1999-05-21&amp;lt;/ShipDate&amp;gt;
    &amp;lt;/Item&amp;gt;
  &amp;lt;/Items&amp;gt;
  &amp;lt;Signature
    xmlns="http://www.w3.org/2000/09/xmldsig#"&amp;gt;
    &amp;lt;SignedInfo&amp;gt;
      &amp;lt;CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/&amp;gt;
      &amp;lt;SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/&amp;gt;
      &amp;lt;Reference URI=""&amp;gt;
        &amp;lt;Transforms&amp;gt;
          &amp;lt;Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/&amp;gt;
        &amp;lt;/Transforms&amp;gt;
        &amp;lt;DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/&amp;gt;
        &amp;lt;DigestValue&amp;gt;rKK35PJzAva2B92cG/cJp6J2BF5JUbKPS5ogYNJkgJo=&amp;lt;/DigestValue&amp;gt;
      &amp;lt;/Reference&amp;gt;
    &amp;lt;/SignedInfo&amp;gt;
    &amp;lt;SignatureValue&amp;gt;dJ2yTNLTjE41e/q4GRAAHBjIJY+Ax3Qn00CyDRTtfdF1WzCUkxj9V7hpp/alwA3/NWeifHrJMr0F
      ak8g41u90RcKV/9rX3lRxNyOKApAKpTw6CrAM6PTcHzHF/NWiL/At8v51AZYm55UgjjrB3xxHvUn
      BidyBUtRuETIrikUD3hSMGD2u9prNhgAxWZ+QmRx2ga171/tnGo1B0JF+aBnup8kgq0I9Po3BSW3
      a7gwAbVlV3R/pFIrht8wfOVjyzQgFnB+SCJ9UwdSwPJqfyrWxSGE5em3hjbX4VGTWeyhS56s0lX5
      9vkCk6dw9cxIvyUsAVSTxYX5SRib2ekpIe7Oyg==
    &amp;lt;/SignatureValue&amp;gt;
    &amp;lt;KeyInfo&amp;gt;
      &amp;lt;X509Data&amp;gt;
        &amp;lt;X509SubjectName&amp;gt;
          1.2.840.113549.1.9.1=#161e6564776172642e6c6567617370695f65787440612d746f2d62652e636f6d,CN=localhost,OU=EETS,O=Atobe,ST=Lisboa,C=PT
        &amp;lt;/X509SubjectName&amp;gt;
        &amp;lt;X509Certificate&amp;gt;MIIDiTCCAnECFAtZBLeU3vRAzm0tXHf7pdP07psNMA0GCSqGSIb3DQEBCwUAMIGAMQswCQYDVQQG
          EwJQVDEPMA0GA1UECAwGTGlzYm9hMQ4wDAYDVQQKDAVBdG9iZTENMAsGA1UECwwERUVUUzESMBAG
          A1UEAwwJbG9jYWxob3N0MS0wKwYJKoZIhvcNAQkBFh5lZHdhcmQubGVnYXNwaV9leHRAYS10by1i
          ZS5jb20wHhcNMjMwMjIxMTAzNzM0WhcNMjQwMjIxMTAzNzM0WjCBgDELMAkGA1UEBhMCUFQxDzAN
          BgNVBAgMBkxpc2JvYTEOMAwGA1UECgwFQXRvYmUxDTALBgNVBAsMBEVFVFMxEjAQBgNVBAMMCWxv
          Y2FsaG9zdDEtMCsGCSqGSIb3DQEJARYeZWR3YXJkLmxlZ2FzcGlfZXh0QGEtdG8tYmUuY29tMIIB
          IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxpnILKjFkUsbaVhsI366Od6tfCFnZafsk0Vi
          KW+k2z2bckig2+OhDr88KItst7LWORFRmo6JltHx1TfWtkYhboVEWOhw0/kVPCBqNdtJiiAyrj5l
          pElgHW6Js8ECUgiiupoJeqwrDPCUbCF0MsOQX/swCbZMqQsAQrPRiNCiDviZ9V/aIlQEaDb7fDJT
          wfM4pmPSPzMXPRghv58EKNiJQIpmQw9H0LYA5iu/kvV6nYC8ROF/4giTGhPr3qYMyR1WCa5EuFOO
          NGUE8Hn8l43DMFa/hFxVECyYqw/wQQngvM0QDlli1K2FRyZYT7k/v+mqBiNYZC50hjkVtvO5Yd8m
          sQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAnLELZRFMZ8FbLNuSWOqmrizOo7BkiT7gyNx1t6fMb
          gadx8qnCpytbghr8O61QuzpYp+65hi9yDEDBfNDtvmT0olIhJK8tsVOu+857MEL0tNcNmavWI4qH
          N0lJ5HZRjTm0VLz86UZdWFJfyk7H+sZGOLucDVc4+BzcpsCiW1Dyy+rdEeLNjwmsLvPP2ol16Mzc
          Ij48aO2DLOut/+OaUl/4TNOrBKZq4Ve2F55RQUwvMDX0BLZlmDy3pI3XSho7KKDdNhl1/0AFJWVh
          skZwR+fAK/RbU1fB9Obc9/IoSm6KANsJRCqFbfkS2hIhT3cvQs/cmR5ZRjgcFgl5J3sMCUFX
        &amp;lt;/X509Certificate&amp;gt;
      &amp;lt;/X509Data&amp;gt;
    &amp;lt;/KeyInfo&amp;gt;
  &amp;lt;/Signature&amp;gt;
&amp;lt;/PurchaseOrder&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the code that digitally signs the XML document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package com.czetsuyatech.dsig.xml;

import com.czetsuyatech.dsig.SignXml;
import com.czetsuyatech.dsig.exceptions.SignatureNotFoundException;
import com.czetsuyatech.dsig.exceptions.XmlSigningException;
import jakarta.xml.bind.JAXBException;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class DigitalSigneeImpl implements XmlTransformer {

  private final String keystoreFile;
  private final String keystoreFormat;
  private final String keystorePassword;
  private final String privateKeyPassword;
  private final String keyEntryAlias;
  private final XMLSignatureFactory signatureFactory;

  public DigitalSigneeImpl(String keystoreFile, String keystoreFormat, String keystorePassword,
      String privateKeyPassword, String keyEntryAlias) {

    this.keystoreFile = keystoreFile;
    this.keystoreFormat = keystoreFormat;
    this.keystorePassword = keystorePassword;
    this.privateKeyPassword = privateKeyPassword;
    this.keyEntryAlias = keyEntryAlias;

    // Instantiate a DOM XMLSignatureFactory object to produce the enveloped signature
    signatureFactory = XMLSignatureFactory.getInstance("DOM");
  }

  public Document sign(SignXml signXml) throws XmlSigningException {

    // STEP 1

    // Create a Reference to the document being signed by specifying an empty URI value to represent the entire
    // document. Additionally, specify the SHA256 digest algorithm and use the ENVELOPED Transform
    Reference ref;
    try {
      ref = signatureFactory.newReference("", signatureFactory.newDigestMethod(DigestMethod.SHA256, null),
          Collections.singletonList(signatureFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)),
          null, null);

      // Create the SignedInfo
      SignedInfo signedInfo =
          signatureFactory.newSignedInfo(signatureFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE,
                  (C14NMethodParameterSpec) null),
              signatureFactory.newSignatureMethod(SignatureMethod.RSA_SHA256, null), Collections.singletonList(ref));

      // STEP 2

      // Load the KeyStore and retrieve the signing key and certificate
      KeyStore ks = KeyStore.getInstance(keystoreFormat);

      ks.load(getFileFromResourceAsStream(keystoreFile), keystorePassword.toCharArray());
      KeyStore.PrivateKeyEntry keyEntry =
          (KeyStore.PrivateKeyEntry) ks.getEntry(keyEntryAlias,
              new KeyStore.PasswordProtection(privateKeyPassword.toCharArray()));
      X509Certificate cert = (X509Certificate) keyEntry.getCertificate();

      // Create the KeyInfo containing the X509Data
      KeyInfoFactory kif = signatureFactory.getKeyInfoFactory();
      List&amp;lt;Object&amp;gt; x509Content = new ArrayList&amp;lt;&amp;gt;();
      x509Content.add(cert.getSubjectX500Principal().getName());
      x509Content.add(cert);
      X509Data xd = kif.newX509Data(x509Content);
      KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));

      // STEP 3

      // Create an instance of the document that will be signed
      DocumentBuilderFactory dbf = getDocumentBuilderFactory();
      dbf.setNamespaceAware(true);
      //TO IMPL , temp change to be compilable
      Document doc = dbf.newDocumentBuilder().parse(getInputStream(signXml));

      // Initialize a DOMSignContext by providing the RSA PrivateKey and identifying the parent element of the resulting
      // XMLSignature
      DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), doc.getDocumentElement());

      // Instantiate the XMLSignature object without signing it yet
      XMLSignature signature = signatureFactory.newXMLSignature(signedInfo, ki);

      // Marshal, generate, and sign the enveloped signature
      signature.sign(dsc);

      return doc;

    } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | ParserConfigurationException |
             MarshalException | XMLSignatureException | UnrecoverableEntryException | KeyStoreException |
             IOException | CertificateException | SAXException | JAXBException e) {
      throw new XmlSigningException(e);
    }
  }

  public boolean validate(Document doc) throws MarshalException, XMLSignatureException {

    // Find Signature element
    NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
    if (nl.getLength() == 0) {
      throw new SignatureNotFoundException("Signature element not found");
    }

    // Create a DOMValidateContext and specify a KeySelector and document context
    DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(), nl.item(0));
    valContext.setProperty("org.jcp.xml.dsig.secureValidation", Boolean.TRUE);

    // Unmarshal the signature
    XMLSignature signature = signatureFactory.unmarshalXMLSignature(valContext);

    // Validate the signature
    boolean result = signature.validate(valContext);

    if (!result) {
      System.out.println("Signature failed core validation");
      boolean sv = signature.getSignatureValue().validate(valContext);
      System.out.println("signature validation status: {}" + sv);
      if (!sv) {
        // Check the validation status of each Reference.
        for (Reference reference : signature.getSignedInfo().getReferences()) {
          boolean refValid = reference.validate(valContext);
          System.out.println("ref[{}] validity status: " + refValid);
        }
      } else {
        System.out.println("Signature passed core validation");
      }
    } else {
      System.out.println("Signature validation OK");
    }

    return result;
  }

  /**
   * Write an XML document root node to a file
   *
   * @param doc     XML Document
   * @param outFile output filename
   * @throws FileNotFoundException when input file is not found
   * @throws TransformerException  when an error occurred in the transformation
   */
  public void writeToFile(Document doc, String outFile) throws IOException, TransformerException {

    try (OutputStream os = new FileOutputStream(outFile)) {
      TransformerFactory tf = getTransformerFactory();
      Transformer trans = tf.newTransformer();
      trans.transform(new DOMSource(doc), new StreamResult(os));
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we need the following dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;jakarta.activation&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;jakarta.activation-api&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;2.1.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;jakarta.xml.bind&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;jakarta.xml.bind-api&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;${jaxb.version}&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;com.sun.xml.bind&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;jaxb-impl&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;${jaxb.version}&amp;lt;/version&amp;gt;
  &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>java</category>
      <category>digitasignature</category>
      <category>javacryptography</category>
      <category>xmlsignature</category>
    </item>
    <item>
      <title>How to Insert Records in Batch with Spring Data</title>
      <dc:creator>Ed Legaspi</dc:creator>
      <pubDate>Sun, 12 Feb 2023 09:03:34 +0000</pubDate>
      <link>https://dev.to/czetsuya/how-to-insert-records-in-batch-with-spring-data-7al</link>
      <guid>https://dev.to/czetsuya/how-to-insert-records-in-batch-with-spring-data-7al</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;When working with a REST API, we often deal with a single entity to create, update, delete, list, and get. But there are also times when we need to manage entities in batches, and this mostly happens in the back office. For example, in an ecommerce platform, promotions are governed by uploading a CSV file in the backend. And this CSV file contains a list of records that needs to be processed. &lt;/p&gt;

&lt;p&gt;Let's see how we can process a list of promotions.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Solutions for Saving a List of Promotions
&lt;/h2&gt;

&lt;p&gt;For the succeeding examples, let's assume that we have a BaseEntity class that is being extended by PromotionEntity.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Reading, Processing, and Saving One Promotion at a Time
&lt;/h3&gt;

&lt;p&gt;Not the ideal solution, but I still see this approach in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BaseEntity&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Getter
@Setter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
@JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class BaseEntity implements Serializable {

  @Serial
  private static final long serialVersionUID = 3986494663579679129L;

  public static final int NB_PRECISION = 23;
  public static final int NB_SCALE = 12;

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private Long id;

  @Version
  @Column(name = "version")
  private Integer version;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the service layer, we process the record one by one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Transactional(rollbackOn = {SQLException.class})
public void createPromotions(List&amp;lt;Promotion&amp;gt; promotions) {
promotionRepository.saveAll(promotions.stream()
        .map(service2EntityMapper::asPromotionEntity)
        .toList());
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Line 1 - it's always a good idea to wrap these kinds of operations (create/delete/update) in a transaction&lt;/li&gt;
&lt;li&gt;Line 3 - we stream the list of promotions&lt;/li&gt;
&lt;li&gt;Line 4 - we convert each promotion POJO to an entity&lt;/li&gt;
&lt;li&gt;Line 5 - we collect the promotion entities&lt;/li&gt;
&lt;li&gt;Line 3 - we save a list of promotion entities&lt;/li&gt;
&lt;li&gt;Note that the saveAll(List) method does not necessarily save records in batch, as it only calls save(E) underneath.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We need the bare minimum configuration for this setup for the data source.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spring:
  application:
    name: czetsuyatech
  datasource:
    url: jdbc:h2:mem:testdb
    driverClassName: org.h2.Driver
    username: sa
    password:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Summary&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's always a good idea to wrap the create/update/delete operations in a transaction&lt;/li&gt;
&lt;li&gt;Use stream when dealing with lists of entities&lt;/li&gt;
&lt;li&gt;Use DTOs and POJOs when passing data from one layer to another&lt;/li&gt;
&lt;li&gt;For table id, use GenerationType.IDENTITY, when dealing with simple requirements&lt;/li&gt;
&lt;li&gt;I would use this approach when I need to process each record in a different transaction. In that case, I need to annotate this method with Transaction.NEVER and call another service that would actually do the data insertion. This means it's possible to only save a fraction of the list due to errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.2 Reading, Processing, and Saving Promotions in Batch
&lt;/h3&gt;

&lt;p&gt;This is the ideal solution, as it efficiently saves a list of records. This is what I will do when dealing with batch inserts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BaseEntity&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Getter
@Setter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
@JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class BaseEntity implements Serializable {

  @Serial
  private static final long serialVersionUID = 3986494663579679129L;

  @Id
  @GeneratedValue(generator = "ID_GENERATOR", strategy = GenerationType.AUTO)
  @Column(name = "id")
  private Long id;

  @Version
  @Column(name = "version")
  private Integer version;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Line 14 - we are using an id generator that we will define in the promotion entity&lt;/li&gt;
&lt;li&gt;Line 20 - we added a version field annotated with &lt;a class="mentioned-user" href="https://dev.to/version"&gt;@version&lt;/a&gt; for optimistic locking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;PromotionEntity&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Getter
@Setter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "promotion")
@GenericGenerator(name = "ID_GENERATOR",
    strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
    parameters = {@Parameter(name = "sequence_name", value = "promotion_seq")}
)
public class PromotionEntity extends BaseEntity {

  @Column(name = "code")
  private String code;

  // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Line 12 - we extend the BaseEntity&lt;/li&gt;
&lt;li&gt;Line 8 - we create a generic sequence generator that will map to the ID_GENERATOR defined in the BaseEntity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's check our revised service layer. I would use a CompletableFuture that I can chain if needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Transactional
public CompletableFuture&amp;lt;Void&amp;gt; createPromotions(Promotions promotions) {

log.debug("Creating promotions");

return CompletableFuture.runAsync(() -&amp;gt; promotionRepository.saveAll(
    promotions.getValues().stream()
        .map(service2EntityMapper::asPromotionEntity)
        .toList())
);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Line 1 - annotate the method with Transaction so that it can revert when an error occurred&lt;/li&gt;
&lt;li&gt;Line 2 - our service method that returns a CompletableFuture&lt;/li&gt;
&lt;li&gt;Line 6 - here, we wrap our saveAll method inside an async block&lt;/li&gt;
&lt;li&gt;Line 7 - we stream to the list of promotions&lt;/li&gt;
&lt;li&gt;Line 8 - we convert each of the POJO to an entity&lt;/li&gt;
&lt;li&gt;Line 9 - we collect the list of entities&lt;/li&gt;
&lt;li&gt;Line 6 - we save all the entities, again it doesn't necessarily mean that we're saving in batch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To enable batch saving, we must make changes to our configuration file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;application.xml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spring:
  application:
    name: czetsuyatech.com
  datasource:
    url: jdbc:h2:mem:testdb
    driverClassName: org.h2.Driver
    username: sa
    password:
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    properties:
      hibernate:
        jdbc:
          batch_size: 5
        generate_statistics: true
    show-sql: false
  flyway:
    enabled: true
    locations: classpath:db/migrations
    baseline-on-migrate: true
    user: sa
    password: sa
  h2:
    console:
      enabled: true
      path: /db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Line 4 - our h2 data source&lt;/li&gt;
&lt;li&gt;Line 14 - you need to define the batch size. Along with sequenced id, it enables batch saving&lt;/li&gt;
&lt;li&gt;Line 15 - useful when checking that you have successfully enabled batch&lt;/li&gt;
&lt;li&gt;Line 17 - is all about the flyway configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's always a good idea to wrap the create/update/delete operations in a transaction&lt;/li&gt;
&lt;li&gt;Use stream when dealing with lists of entities&lt;/li&gt;
&lt;li&gt;Use DTOs and POJOs when passing data from one layer to another&lt;/li&gt;
&lt;li&gt;Use a custom sequence generator&lt;/li&gt;
&lt;li&gt;Define batch_size hibernate property in application XML&lt;/li&gt;
&lt;li&gt;I would use this approach to speed up the processing of records. The downside is that each batch is managed in one transaction, which means failing one record in that batch means the whole set will not be saved.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Challenge
&lt;/h2&gt;

&lt;p&gt;Using the above approach, try to save records in different sizes: 100, 1000, 10000, and 1000000. You should be able to see a noticeable improvement in inserts.&lt;/p&gt;

</description>
      <category>codequality</category>
      <category>softwareengineering</category>
      <category>career</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
