<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[SELECT BLOG;]]></title><description><![CDATA[#DATA #SQL #PLSQL #ORCLAPEX #KAFKA]]></description><link>https://dsavenko.me/</link><image><url>https://dsavenko.me/favicon.png</url><title>SELECT BLOG;</title><link>https://dsavenko.me/</link></image><generator>Ghost 4.2</generator><lastBuildDate>Mon, 20 Apr 2026 00:35:45 GMT</lastBuildDate><atom:link href="https://dsavenko.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part four]]></title><description><![CDATA[The fourth and the final part of my step-by-step all-in-one guide on how to install a full production-like APEX environment on CentOS Linux. This time coverting SSL, redundancy and backups.]]></description><link>https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-four/</link><guid isPermaLink="false">5c002b6cc083c85821a45211</guid><category><![CDATA[apex]]></category><category><![CDATA[oracle]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Sat, 08 Dec 2018 12:15:00 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/12/cover-square-part-three.png" medium="image"/><content:encoded><![CDATA[<img src="https://dsavenko.me/content/images/2018/12/cover-square-part-three.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part four"><p>As many readers asked for this, I decided to write this continuation of the guide on how to configure a production-like <strong>Oracle Application Express</strong> environment. In this long awaited chapter we are going to cover configuration of <em>SSL</em>,<em> redundancy</em> and <em>backups</em> in the environment you already have if followed the previous chapters. I should tell you that all these steps are totally optional and are not required for you to start with <strong>APEX</strong>. But following them you will dramatically increase security, availabilty and reliability of your environment, which is definitely a must for any production environment. Your users will not tolerate it if somebody steals their passwords, or if they cannot access your website because of some media failure on your server. So, if you want to prevent these events, this chapter is definitely for you.</p><!--kg-card-begin: markdown--><h1 id="configuringssl">Configuring SSL</h1>
<p>In previous chapters we already got rid of unneeded Tomcat applications and tweaked Apache configuration a little bit. But we didn&apos;t touch a huge topic of secure connections between users and your web server. I should warn you that configuring this is going to be more advanced than anything we already did before. But it&apos;s worth it, because we are talking about security and you shouldn&apos;t underestimate it.</p>
<p>So, the first thing to say here - you will need an SSL certificate to set up a secure access to your <em>APEX</em> applications. Such a certificate is issued for a domain name, which you have to possess. Keeping all this in mind we are ready to start.</p>
<p>I should also notice that we are not going to use <em>Oracle Wallet</em>, because our security layer will only take place between our users and the web server, which is <em>Apache httpd</em> in our case.</p>
<p>SSL certificates are generally issued by trusted companies and in most cases this is a paid service. But thanks to <em>Linux Foundation</em>, today we have a totally free option of <strong>Let&apos;s Encrypt</strong> certificates. They are issued for 60 days and are free to renew. There is also very useful software which automates generation and renewal processes of such certificates, it is called <code>certbot</code> and it is available for a variety of operating systems, including <em>CentOS Linux</em>. I will not stop on how to install <code>certbot</code>, because <em>Digital Ocean</em> already composed a <a href="https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-centos-7">detailed tutorial</a> on the topic. And you will need to install <code>certbot</code> using this tutorial. Additionally, you are going to find there information about how to configure automatic renewal of your certificates.</p>
<blockquote>
<p><mark>Note</mark> that during installation, <code>certbot</code> adds <code>ssl.conf</code> file to the <code>/etc/httpd/conf.d</code> directory, which already contains needed HTTPS configuration for <em>Apache</em> (enabling listening to port 443, proper cipher suite and so on).</p>
</blockquote>
<p>Assuming that you succeeded with installation of <code>certbot</code> and generated your certificates following the steps in the manual, the only thing which left is to rewrite our <em>APEX</em> virtual host configuration and restart <em>Apache</em> service.</p>
<p>So, open your <code>10-apex.conf</code> file in the <code>/etc/httpd/conf.d/</code> directory (we created it in the second chapter of this guide) and rewrite its contents as following:</p>
<pre><code class="language-bash,line-numbers"># force HTTPS
&lt;VirtualHost *:80&gt;
    ServerName yourdomain.tld
    ServerAlias www.yourdomain.tld

    RewriteEngine on
    RewriteCond %{SERVER_NAME} =www.yourdomain.tld [OR]
    RewriteCond %{SERVER_NAME} =yourdomain.tld
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
&lt;/VirtualHost&gt;

# forward ORDS requests to tomcat
&lt;VirtualHost *:443&gt;
    ServerName yourdomain.tld
    ServerAlias www.yourdomain.tld

    # SSL certificates settings
    Include /etc/letsencrypt/options-ssl-apache.conf
    SSLCertificateFile /etc/letsencrypt/live/yourdomain.tld/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.tld/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/yourdomain.tld/chain.pem

    # alias for apex static files
    Alias &quot;/i/&quot; &quot;/var/www/apex/images/&quot;

    # uncomment the line below if you want 
    # to redirect traffic to ORDS from root path
    # RedirectMatch permanent &quot;^/$&quot; &quot;/ords&quot;

    # proxy ORDS requests to tomcat
    ProxyRequests off
    ProxyPreserveHost On
    &lt;Location &quot;/ords&quot;&gt;
        ProxyPass &quot;ajp://localhost:8009/ords&quot;
        ProxyPassReverse &quot;ajp://localhost:8009/ords&quot;
    &lt;/Location&gt;
&lt;/VirtualHost&gt;
</code></pre>
<p>Where <code>yourdomain.tld</code> is your domain name. This time this is mandatory to specify one, because your SSL certificates are issued for a particular domain.</p>
<p>Then, as you see, we now have two virtual hosts in the configuration. The first is for the HTTP endpoint, and it only forces usage of HTTPS by leveraging of power of <code>mod_rewrite</code>. The other is for the HTTPS endpoint which looks pretty similar to the configuration we used to have before, the only difference is that it now uses our <em>Let&apos;s Encrypt</em> certificates (mind the paths, they should be changed).</p>
<p>Now save the changes and restart the <em>Apache</em> service:</p>
<pre><code class="language-bash">systemctl restart httpd
</code></pre>
<p>That&apos;s it! By now you should be able to access your <em>APEX</em> applications using HTTPS protocol. No SSL configuration needed for <em>Tomcat</em>, <em>ORDS</em> or Oracle Database itself.</p>
<p><img src="https://dsavenko.me/content/images/2018/12/apex-https-1.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part four" loading="lazy"></p>
<h1 id="makingitindestructible">Making it indestructible</h1>
<p>As you already know, <em>APEX</em> engine and applications metadata reside in the database. So, making your <em>APEX</em> environment reliable and fault tolerant basically means securing the database underneath.</p>
<p>And <em>Oracle Database</em> has quite many options here. However, not everything depends only on the RDBMS, because it is meaningless to try to achieve high degree of reliability if everything is you have is a single physical drive.</p>
<p>To make our environment more fault tolerant, we are going to configure two main things - redundancy and backups. Redundancy basically means having more than one copies of files, critical for database functionality, which would save our nerves if one of the copies gets corrupted. Backups, in their turn, would make it possible to restore and recover lost data in case of more severe media failure.</p>
<p>It is important to say that different copies of critical database files should reside on different physical drives. But these drives should be local to the database, since database constantly write there and performance of your instance depend on this.</p>
<p>Archived REDO logs and backups should also be placed on a physical drive different from the one where your datafiles are, but it is even better to place them on a different host. You can sacrifice performance here, because archiving and backing up processed do not affect your applications response time. For instance, you could attach an <strong>NFS</strong> or <strong>iSCSI</strong> volume to your system and use it in a transparent way like a local hard drive, whereas it would physically reside somewhere else.</p>
<p>There are other things, which you can have a look at in the official <a href="https://docs.oracle.com/en/database/oracle/oracle-database/18/admin/index.html">Oracle Administration</a> guide. But even these two main steps done properly would make your database almost indestructible.</p>
<h2 id="settingupredundancy">Setting up redundancy</h2>
<p>By default <em>Oracle Database</em> <strong>18c</strong> has this configuration in <em>CentOS Linux</em>:</p>
<ul>
<li>Two copies of control files under <code>oradata</code> directory.</li>
<li>Three groups of one redo log file each.</li>
<li><code>NOARCHIVELOG</code> mode.</li>
</ul>
<p>We are going to multiplex the control files to a different drive, add there a member to each redo log group and then enable <code>ARCHIVELOG</code> mode.</p>
<h3 id="preparingdirectories">Preparing directories</h3>
<p>Our datafiles, redo log and control files reside in <code>/opt/oracle/oradata</code> directory by default. Assuming the fact that our second physical drive is mounted to <code>/var/media</code>, let&apos;s prepare a directory for copies of our critical database files:</p>
<pre><code class="language-bash">mkdir -p /var/media/oracle/oradata/XE
chown -R oracle:oinstall /var/media/oracle
</code></pre>
<p>Notice the fact that I followed the default directory structure, which we have for the main location, but this is totally optional. I also gave ownership of the new directory to user <code>oracle</code>, because it needs full permissions on it.</p>
<h3 id="multiplexingcontrolfiles">Multiplexing control files</h3>
<p>Official documentation recommends to have at least two copies of database control files, each stored on a different physical disk. Location of control files is stored in the server parameter file (or <code>SPFILE</code>), but this file must not be edited manually.</p>
<p>First, set the new paths by changing the corresponding instance parameter. For this, run <code>sqlplus /nolog</code> and connect to the database as <code>sys</code>.</p>
<pre><code class="language-sql">-- connect as user SYS using bequeath method and enter your password
connect sys as sysdba
-- set the new value of the &apos;control_files&apos; parameter 
alter system set control_files=&apos;/opt/oracle/oradata/XE/control01.ctl&apos;,&apos;/opt/oracle/oradata/XE/control02.ctl&apos;,&apos;/var/media/oracle/oradata/XE/control01.ctl&apos;,&apos;/var/media/oracle/oradata/XE/control02.ctl&apos; scope=spfile;
-- shut the instance down and do not start it yet
shutdown immediate
</code></pre>
<p>Now copy the control files to the new location:</p>
<pre><code class="language-bash">cp /opt/oracle/oradata/XE/*.ctl /var/media/oracle/oradata/XE/
</code></pre>
<p>And then start the database instance. Execute <code>startup</code> command for this as <code>sys</code>:</p>
<pre><code class="language-sql">-- start the instance
startup
</code></pre>
<p>Done. Now the database uses four control files at a time - two on one hard drive and two on the other.</p>
<h3 id="multiplexingredologfiles">Multiplexing redo log files</h3>
<p>The next step is to set up the second redo log files group on a different HDD. You can check the current status of your redo logs by querying the corresponding dynamic performance views:</p>
<pre><code class="language-sql">select * from v$log;
select * from v$logfile;
</code></pre>
<p>As you see, you have three redo log groups of one file each of 200 MBytes by default. Let us add one more member into each group. For this, run the following commands in <em>SQL*Plus</em> as <code>sys</code>:</p>
<pre><code class="language-sql">-- add one additional member on a separate disk to each redo log group
alter database add logfile member &apos;/var/media/oracle/oradata/XE/redo01.log&apos; to group 1;
alter database add logfile member &apos;/var/media/oracle/oradata/XE/redo02.log&apos; to group 2;
alter database add logfile member &apos;/var/media/oracle/oradata/XE/redo03.log&apos; to group 3;
</code></pre>
<p>You can notice that after adding the files they have <code>INVALID</code> status. This is normal, they become active (with blank status) on the first use. You can force it by invoking this command three times in a row in <em>SQL*Plus</em> as <code>sys</code>:</p>
<pre><code class="language-sql">alter system switch logfile;
</code></pre>
<p>Ok, now the databases writes redo log records into two locations simultaneously. And this is what we wanted.</p>
<h3 id="archivelogmodeon">ARCHIVELOG mode on</h3>
<p>By default, <em>Oracle Database XE</em> works in the <code>NOARCHIVELOG</code> mode. It means it is not protected from media failure and it would be impossible to fully recover database from a failure. Furthermore, even if we set up backups, it will be possible to restore the database only to the point of time when the backups were created.</p>
<p>But if we enable <code>ARCHIVELOG</code> mode, we will be able to recover our database to the most recent point of time, because in this case after restoring from a backup, it will be possible to recover the database to the current state using archived and current redo log files. Isn&apos;t this awesome?</p>
<p>To check the current status of database log mode run the following command in <em>SQL*Plus</em> as <code>sys</code>:</p>
<pre><code class="language-sql">select log_mode from v$database;
</code></pre>
<p>As you see, by default the database works in <code>NOARCHIVELOG</code> mode. Now prepare a directory for archive log files. Since I don&apos;t have a different host for this, I will be using my second hard drive:</p>
<pre><code class="language-bash">mkdir -p /var/media/oracle/oradata/XE/arch
chown -R oracle:oinstall /var/media/oracle
</code></pre>
<p>And then specify it as a destination directory for archive logs in <em>SQL*Plus</em> as <code>sys</code>:</p>
<pre><code class="language-sql">alter system set log_archive_dest=&apos;/var/media/oracle/oradata/XE/arch&apos; scope=both;
</code></pre>
<p>And check if it worked:</p>
<pre><code class="language-sql">select * from v$archive_dest order by dest_id;
</code></pre>
<p>The first destination should be shown as VALID.<br>
Now it&apos;s high time to enable the <code>ARCHIVELOG</code> mode. For this start <em>SQL*Plus</em> as user <code>sys</code> and run the commands as following:</p>
<pre><code class="language-sql">shut immediate
startup mount
alter database archivelog;
alter database open;
</code></pre>
<blockquote>
<p><mark>Note</mark> that if this is not a fresh installation of yours, you will need to perform a full backup after this operation, because all the previously created backups are unusable after changing the log mode.</p>
</blockquote>
<p>Let&apos;s check:</p>
<pre><code class="language-sql">select log_mode from v$database;
</code></pre>
<p>Providing you see <code>ARCHIVELOG</code>, everything is ok. Now your database will archive redo log files to the specified destination.</p>
<p>Our database already looks quite reliable. But what&apos;s next?</p>
<h2 id="backups">Backups</h2>
<p>To be even more confident, we need a backing up strategy for our datafiles. There are several options when it comes to backup an <em>Oracle Database</em>. They are divided into two general classes - physical backups (<code>RMAN</code>, user-managed tools) and logical backups (<code>expdp</code>/<code>impdb</code>, <code>exp</code>/<code>imp</code>, user-defined DDL/DML SQL scripts).</p>
<p>Here we are going to consider managing backups using <em>Recovery Manager</em> (<code>RMAN</code>), because this is the most powerful and officially recommended option by <em>Oracle</em>. RMAN is a perfect addition to the steps we already made in the previous chapters when started to archive redo log files.</p>
<p>RMAN allows you to back up literally everything related to your database, including data, parameter, control and archived redo log files. There is also a huge number of options of how to manage backups with RMAN - they could be written to different media, they could be <em>backup sets</em> or <em>datafile images</em>, the backups could be full or incremental, and if such, then differential or cumulative, and so on.</p>
<p>In this particular article we are going to consider an easy, but still quite a reliable case, and we are going to do so deliberately - our backing up strategy will be to have <em>up to two current copies of full, not compressed backup sets for our data and control files</em>. We are going for the <em>not compressed</em> option since it is more performant and less CPU loading. We are also not going to use recovery catalog, since it is a little bit of a overkill for us. We chose <em>full backups</em>, because incremental backups do not give many benefits for <em>Oracle Database XE</em> (a full backup takes not that much time). But you are welcome to change this configuration for your particular case as you wish.</p>
<p>To start with all this, prepare a directory for the backups (again, I will be using my second drive mount point, for you it could be different):</p>
<pre><code class="language-bash">mkdir -p /var/media/oracle/oradata/XE/backup
chown -R oracle:oinstall /var/media/oracle
</code></pre>
<p>Then, create a file where we will write RMAN commands to perform the backing up operation:</p>
<pre><code class="language-bash">cd /var/media/oracle
touch make_backup.rman
mcedit make_backup.rman
</code></pre>
<p>And put these lines to the file in the editor:</p>
<pre><code class="language-bash"># This is an Oracle Recovery Manager script to create a full backup of all data and control files
# The script expects an established connection to a target database
# For local target database, run `connect target /` before running the script if you use it manually

set echo on
run
{
  # set device configuration to default (disk with parallelism of 1)
  configure device type disk clear;

  # set channel configuration to default and then specify the destination file format for them
  configure channel device type disk clear;
  configure channel device type disk format &apos;/var/media/oracle/oradata/XE/backup/data_%U_%I_%T.bk&apos;;

  # enable backing up of control files and specify the destination file format for them
  configure controlfile autobackup on;
  configure controlfile autobackup format for device type disk to &apos;/var/media/oracle/oradata/XE/backup/cf_%F.bk&apos;;

  # set retention policy to 2 backups at a time
  configure retention policy to redundancy 2;
  
  # delete archive logs after 2 backups
  configure archivelog deletion policy to backed up 2 times to device type disk;

  # show the configuration before start
  show all;

  # perform backing up
  backup check logical database;

  # perform crosscheck to sync metadata with backup media data
  crosscheck backup;

  # delete obsolete and lost (not on disk anymore) archived redo log and backup files
  delete noprompt obsolete;
  delete noprompt expired backup;
  delete noprompt archivelog all;
}
</code></pre>
<p>Then save changes. As you see, the script is quite self explanatory. First, we perform some preliminary configuration, then run the backup command, and finally erase not needed anymore files.</p>
<p>To run the script we are going to create a shell script:</p>
<pre><code class="language-bash">cd /var/media/oracle
touch make_backup.sh
mcedit make_backup.sh
</code></pre>
<p>And put these lines into it:</p>
<pre><code class="language-bash"># Shell script to perform RMAN backup using prepared commands file
# The script expects that environment is set for user `oracle`
# The scripts runs RMAN client as user `oracle`, connects to local database and executes the specified commands file
# The output is written to the specified log file

su - oracle -c &quot;rman target / cmdfile /var/media/oracle/make_backup.rman &gt; /var/media/oracle/make_backup_last.log&quot;
</code></pre>
<p>As you see, our shell script runs the preliminary written RMAN commands file as user <code>oracle</code>. This is important, because only this user is allowed to use bequeath connection method to the database instance (by default).</p>
<p>Now we need to tell the system to run the script periodically. To do so, add this line to <code>/etc/crontab</code> file:</p>
<pre><code class="language-bash">0  3  *  *  1  root  /bin/sh /var/media/oracle/make_backup.sh
</code></pre>
<p>Mind the fact that there must be a new line character at the end of a line in <code>crontab</code>. Otherwise, your task will not be executed.</p>
<p>This enables weekly backups at 3:00 AM every Monday.</p>
<p>That is it! To create a backup immediately, just run the script as user <code>root</code> as following:</p>
<pre><code class="language-bash">. /var/media/oracle/make_backup.sh
</code></pre>
<p>Now, if a catastrophe occurs, the only two RMAN commands which you need in order to recover your database are <code>restore database</code> and <code>recover database</code>. You also have options to restore a particular datafile. Or even a particular corrupted block, however for this you will need <em>Block Media Recovery</em>, which is a paid option, unfortunately <em>still not included</em> with the <em>XE</em>.</p>
<p>Steps which we completed here are generally enough for most cases. But if you like to know more about other options, refer to the official <a href="https://docs.oracle.com/en/database/oracle/oracle-database/18/bradv/index.html">Backup and Recovery</a> documentation.</p>
<h2 id="finalwords">Final words</h2>
<p>In this chapter of the guide I tried to cover security and reliability aspects of an <em>Oracle APEX</em> environment. It happened that <em>APEX</em> is a part of <em>Oracle Database</em>, which has many different options of how to make your applications almost immortal. Here we did it step-by-step, and this is like entering <strong>IDDQD</strong> to get the GOD mode in Doom.</p>
<p>Do not forget to share your ideas about in what ways this article could be improved. This was the final chapter of my detailed all-in-one guide on how to set up, configure and secure a production-like APEX, Oracle Database, ORDS, Tomcat and Apache  environment on CentOS Linux. I hope you enjoyed the write-up and found it useful for you. Stay tuned and don&apos;t forget to check out other articles in my blog!</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://dsavenko.me/content/images/2018/12/doom-god-mode.jpg" class="kg-image" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part four" loading="lazy"></figure><!--kg-card-begin: markdown--><hr>
<h2 id="previouschapters">Previous Chapters</h2>
<p>In case you missed some previous steps, please, use the following links to catch up:</p>
<ul>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-introduction">Introduction Part. APEX environment architecture and its components</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-one">Part 1. CentOS installation and its configuration for APEX</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-two">Part 2. Installation of Oracle Database, ORDS and APEX itself</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-three">Part 3. Additional configuration (optional)</a></li>
</ul>
<p>Hope this post was be helpful for you. Be free to leave your comments if you have some advice how to improve the guide or if you found a mistake!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part three]]></title><description><![CDATA[The third part of the step-by-step all-in-one guide on how to install a full APEX environment on CentOS. It covers some additional steps to improve your experience and security when use APEX.]]></description><link>https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-three/</link><guid isPermaLink="false">5ab9e5ce5821510f9c801b5f</guid><category><![CDATA[apex]]></category><category><![CDATA[oracle]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Fri, 16 Nov 2018 05:30:00 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/03/cover-square-part-three.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/03/cover-square-part-three.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part three"><p>This is the third chapter of this all-in-one guide, covering some additional configuration of the environment. By now, you should already have a working installation of <strong>Oracle Database XE</strong>, <strong>APEX</strong>, <strong>ORDS</strong>, <strong>Tomcat</strong> and <strong>Apache httpd</strong> on your <strong>CentOS</strong> system without proceeding to these steps at all. But when it comes to a production environment, there are always security and performance issues which arise. And if you want to improve these aspects of your setup, keep reading.</p>
<h2 id="sshdtweaks">SSHd tweaks</h2>
<p>It is a good idea to disable direct remote <code>root</code> login. Users which need super user rights, should be able to escalate their rights instead using <code>su</code> command. To do so, edit the SSHd config and reload it:</p>
<pre><code class="language-bash">mcedit /etc/ssh/sshd_config
</code></pre>
<p>Add a line as below to the config and save the file</p>
<pre><code class="language-bash">PermitRootLogin no
</code></pre>
<p>Then, restart the service:</p>
<pre><code class="language-bash">systemctl restart sshd
</code></pre>
<h2 id="apachehttpdtweaks">Apache httpd tweaks</h2>
<p>First, disable the default welcome page by removing the corresponding config file:</p>
<pre><code class="language-bash">rm -rf /etc/httpd/conf.d/welcome.conf
</code></pre>
<p>Then, add an additional configuration file <code>0-extra.conf</code> in the <code>etc/httpd/conf.d/</code> directory with the contents as below:</p>
<pre><code class="language-bash,line-numbers"># additional apache httpd configuration
# add this to the end of /etc/httpd/conf/httpd.conf
# or put it in a separate file such as /etc/httpd/conf.d/0-extra.conf

# disable sensitive version info
ServerSignature Off
ServerTokens Prod

# enable compression of static content
&lt;IfModule deflate_module&gt;
     SetOutputFilter DEFLATE
     AddOutputFilterByType DEFLATE text/plain text/html text/xml text/css text/javascript
&lt;/IfModule&gt;

# enable client caching of static content
&lt;IfModule expires_module&gt;
    ExpiresActive On
    ExpiresByType image/gif &quot;access plus 7 days&quot;
    ExpiresByType image/jpeg &quot;access plus 7 days&quot;
    ExpiresByType image/png &quot;access plus 7 days&quot;
    ExpiresByType text/css &quot;access plus 7 days&quot;
    ExpiresByType text/javascript &quot;access plus 7 days&quot;
    ExpiresByType application/javascript &quot;access plus 7 days&quot;
    ExpiresByType application/x-javascript &quot;access plus 7 days&quot;
&lt;/IfModule&gt;
</code></pre>
<p>This will enable traffic compression and client-side static files caching. On the other hand, it will disable displaying of the sensitive version data.</p>
<h2 id="tomcattweaks">Tomcat tweaks</h2>
<p>If you followed the steps from this guide, there&apos;s nothing to clean up in <em>Tomcat</em>. But, just to be sure, you can execute these command which are intended to remove all default <em>Tomcat</em> applications:</p>
<pre><code class="language-bash">cd /usr/share/tomcat/webapps
rm -rf examples/*
rmdir examples
rm -rf sample/*
rmdir sample
</code></pre>
<p>Then, I noticed that despite the fact both <code>tomcat</code> and <code>oracle-xe-18c</code> start on the system startup, <em>APEX</em> does not properly work without restarting of <code>tomcat</code>. This happens because the <code>tomcat</code> service starts before the <code>oracle-xe-18c</code>. To fix this, we need to edit the Tomcat service <code>systemd</code> unit file:</p>
<pre><code class="language-bash">mcedit /usr/lib/systemd/system/tomcat.service
</code></pre>
<p>There the <code>Unit</code> section should look like this:</p>
<pre><code class="language-bash,line-numbers">[Unit]
Description=Apache Tomcat Web Application Container
After=syslog.target network.target oracle-xe-18c.service
Wants=oracle-xe-18c.service
</code></pre>
<p>Save the file after making changes and invoke this command to reload the config:</p>
<pre><code class="language-bash">systemctl daemon-reload
</code></pre>
<h2 id="oraclexetweaks">Oracle XE tweaks</h2>
<p>Now let&apos;s connect to the database using <code>sqlplus /nolog</code> command, clean it up and make some additional configuration. Notice the fact that we are going to use <em>bequeath</em> connection here, because we will need to restart the database instance once:</p>
<pre><code class="language-sql,line-numbers">-- connect to the CDB database to perform system-wide configuration
connect sys as sysdba

-- memory parameters for the instance
-- notice the fact that we are using the maximum allowed memory size for XE
-- so, please, tweak these values in case you do not have such amounts of memory for the RDBMS (otherwise, your instance won&apos;t start)
-- also notice that we are using AMM (Automatic Memory Management)
alter system set memory_target=2G scope=spfile;
alter system set memory_max_target=2G scope=spfile;
alter system set sga_target=0 scope=spfile;
alter system set pga_aggregate_target=0 scope=spfile;

-- some recommended values for the maximum number of sessions, processes and job_queues
alter system set sessions=250 scope=spfile;
alter system set processes=500 scope=spfile;
alter system set job_queue_processes=100 scope=spfile;

-- restart database
shutdown immediate
startup

-- now change session to use PDB to configure other things
alter session set container = xepdb1;

-- anonymous user is not needed when we don&apos;t use XDB
alter user anonymous account lock;
    
-- dropping the demo schema
drop user hr cascade;

-- altering the default password policy (by default passwords will expire in 180 days)
alter profile default limit password_life_time unlimited;

-- creating a tablespace for our APEX workspaces
create tablespace apex datafile &apos;/opt/oracle/oradata/XE/XEPDB1/apex.dbf&apos; size 128M reuse autoextend on next 8M maxsize unlimited;
    
-- creating a schema for our APEX workspaces
create user apex identified by &quot;YourPasswordHere&quot; default tablespace apex temporary tablespace temp;
alter user apex quota unlimited on apex;
grant unlimited tablespace to apex;
grant create session to apex;
grant create cluster to apex;
grant create dimension to apex;
grant create indextype to apex;
grant create job to apex;
grant create materialized view to apex;
grant create operator to apex;
grant create procedure to apex;
grant create sequence to apex;
grant create snapshot to apex;
grant create synonym to apex;
grant create table to apex;
grant create trigger to apex;
grant create type to apex;
grant create view to apex;

exit
</code></pre>
<p>As you can see, I created a new schema <code>apex</code> in a new tablespace <code>apex</code>. I would recommend to use it for your <em>APEX</em> applications.</p>
<h2 id="ordstweaks">ORDS tweaks</h2>
<p>The default connection pool settings in the <em>ORDS</em> configuration are too small. You&apos;ll have to experiment to see what settings are the best for your workload, but the following seem to work well:</p>
<pre><code class="language-bash">mcedit /opt/oracle/ords/config/ords/conf/apex.xml
</code></pre>
<p>Find these parameters and set their values (or add these lines if they do not exist):</p>
<pre><code class="language-xml,line-numbers">&lt;entry key=&quot;jdbc.InitialLimit&quot;&gt;10&lt;/entry&gt;
&lt;entry key=&quot;jdbc.MinLimit&quot;&gt;10&lt;/entry&gt;
&lt;entry key=&quot;jdbc.MaxLimit&quot;&gt;60&lt;/entry&gt;
</code></pre>
<p>Restart <em>Tomcat</em> to take effect:</p>
<pre><code class="language-bash">systemctl restart tomcat
</code></pre>
<h2 id="checkingifeverythingworks">Checking if everything works</h2>
<p>I believe you are dying to open your browser and check how it works. Just do it!<br>
<img src="https://dsavenko.me/content/images/2018/03/just-do-it.jpg" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part three" loading="lazy"></p>
<p><em>APEX</em> main page must be available on <code>yourdomain.tld/ords</code> address, <em>APEX</em> administration services on <code>yourdomain.tld/ords/apex_admin</code> respectively (where <code>yourdomain.tld</code> is your domain name or the server IP address):<br>
<img src="https://dsavenko.me/content/images/2018/11/apex-in-browser.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part three" loading="lazy"></p>
<p>Let&apos;s check the headers to be sure that caching and traffic compression work:<br>
<img src="https://dsavenko.me/content/images/2018/11/apex-headers-in-browser.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part three" loading="lazy"></p>
<p>Everything looks wonderful!</p>
<p><img src="https://dsavenko.me/content/images/2018/11/apex-version.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part three" loading="lazy"></p>
<p>And as you can see, we use the latest current version of <em>APEX</em>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Surely, these are not all the steps which could be done. Consider also setting up SSL connection to your server, configuring backups, installing some monitoring tools and systems like <em>influxdb+telegraf+grafana</em>. If you like to know about these things, step further to the final part of the guide</p>
<p>But I am already can say that we successfully installed and secured a reliable, nicely working <em>APEX</em> environment on a <em>CentOS Linux</em> server. Please, provide me with your feedback in the comments and do not hesitate to add anything, which would be great to add to this guide. I would really appreciate it if you corrected my English if I misused some words or structures - I should excuse myself for it in advance.</p>
<p>Thank you very much for reading and I wonder if this series of blog posts would do any better to the great <em>APEX</em> community. Stay tuned for other guides and stories!</p>
<hr>
<h2 id="nextchapters">Next chapters</h2>
<p>Here we started with some additional configuration, but if you are ready for SSL, redundancy and backups, you are welcome to check out the final part of the guide/</p>
<p>Here is the link for your convenience:</p>
<ul>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-four">Part 4. Setting up SSL, redundancy and backups (optional)</a></li>
</ul>
<h2 id="previouschapters">Previous chapters</h2>
<p>In case you missed some previous steps, please, use the following links to catch up:</p>
<ul>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-introduction">Introduction Part. APEX environment architecture and its components</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-one">Part 1. CentOS installation and its configuration for APEX</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-two">Part 2. Installation of Oracle Database, ORDS and APEX itself</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part two]]></title><description><![CDATA[The second part of the step-by-step all-in-one guide on how to install a full APEX environment on CentOS. It covers installation and configuration of the Oracle Database, ORDS and APEX itself.]]></description><link>https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-two/</link><guid isPermaLink="false">5ab9e5545821510f9c801b5c</guid><category><![CDATA[apex]]></category><category><![CDATA[oracle]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Fri, 16 Nov 2018 05:20:00 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/03/cover-square-part-two.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/03/cover-square-part-two.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part two"><p>This is the second part of the series of blog posts dedicated to tell you in detail how to organize a usual production-like environment for <strong>APEX</strong>. In this part we are going to sort out the installation and configuration process of <strong>Oracle Database XE</strong>, <strong>ORDS</strong> and <strong>APEX</strong> itself. This instruction assumes you prepared your system following the instructions from the <a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-one">part one</a> of this guide.</p>
<p>Let&apos;s go straight to the point starting with the downloading the software to our server.</p>
<h2 id="downloadingthesoftware">Downloading the software</h2>
<p>The first thing to do here is to download the software from <strong>Oracle Technology Network</strong>:</p>
<ul>
<li><a href="http://www.oracle.com/technetwork/database/database-technologies/express-edition/downloads/index.html">Database/Database Technology Index/Database Express Edition/Downloads</a> - you will need <em>the package for Linux x64</em> and the <em>preinstall RPM package</em> (for release 7 of RHEL or CentOS) from there.</li>
<li><a href="http://www.oracle.com/technetwork/developer-tools/rest-data-services/downloads/index.html">Developer Tools/Oracle REST Data Services/Downloads</a></li>
<li><a href="http://www.oracle.com/technetwork/developer-tools/apex/downloads/index.html">Developer Tools/Application Express/Downloads</a></li>
</ul>
<p>You have to have an <em>ODC</em> account (you can register right there, it is free) and accept the license agreement before you can start to download the stuff.</p>
<p>In case you have downloaded all the the software on your desktop computer, you will need to upload it to your server. It could be done by leveraging of an SCP or SFTP client, for instance <a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">pscp</a> or <a href="https://winscp.net/eng/download.php">WinSCP</a> if you&apos;re a <strong>Windows</strong> user.</p>
<blockquote>
<p>After everything is done I am assuming all the downloaded Oracle software is on your server in the <code>root</code> user home directory, which is usually <code>/root</code>.</p>
</blockquote>
<h2 id="installationoforacledatabase18cxe">Installation of Oracle Database 18c XE</h2>
<p>Before we start, I just want you to be reminded about the minimum system requirements for the installation:</p>
<ul>
<li>1GB RAM (2GB is strongly recommended)</li>
<li>2GB swap</li>
<li>10GB of disk space</li>
</ul>
<h3 id="installationofrdbms">Installation of RDBMS</h3>
<p>After you checked them, to install the RDBMS, you need to install the <em>preinstall RPM package</em> first and then install the database software as following:</p>
<pre><code class="language-bash">cd /root
# uncomment the next line to download the preinstall package in case you have not downloaded it from the OTN
# wget https://yum.oracle.com/repo/OracleLinux/OL7/latest/x86_64/getPackage/oracle-database-preinstall-18c-1.0-1.el7.x86_64.rpm
yum localinstall oracle-database-preinstall-18c* -y
yum localinstall oracle-database-xe-18c* -y
</code></pre>
<p>Notice that I used <code>yum</code> to install the local packages instead of <code>rpm</code>. This will enable us to use all the power of <code>yum</code> in the future if needed (for example, to remove a package with its dependencies).</p>
<p>The user <code>oracle</code> and the group <code>oinstall</code> (not <code>dba</code> as <em>it was previously</em>) are created during the package installation, so we do not need to create them explicitly. Also, the default user environment is created during the set up process (so we do not have to do it explicitly as <em>it was previously</em>). If you like, you can set a password for this user by invoking <code>passwd oracle</code> command. This user is the owner of the <code>/opt/oracle</code> directory where the <strong>Oracle Database</strong> is located and this <a href="https://dsavenko.me/tns-lost-contact-on-local-connect">must stay unchanged</a>.</p>
<p>Now, when the packages are installed and the user is set up, you need to run the <strong>initial database configuration script</strong>:</p>
<pre><code class="language-bash">/etc/init.d/oracle-xe-18c configure
</code></pre>
<p>And answer the questions prompted. After answering all the questions, it is going to take several minutes to initialize the database.</p>
<h3 id="settingupenvironment">Setting up environment</h3>
<p>Now it is a good idea to set up Oracle Database <em>environment variables</em> in order to make users be able to use <code>sqlplus</code> from anywhere. Along with this, we are setting up some useful aliases:</p>
<pre><code class="language-bash">echo &apos;# setting oracle database environment variables and aliases&apos; &gt;&gt; /etc/profile.d/oraenv.sh
echo &apos;ORACLE_SID=XE&apos; &gt;&gt; /etc/profile.d/oraenv.sh
echo &apos;ORAENV_ASK=NO&apos; &gt;&gt; /etc/profile.d/oraenv.sh
echo &apos;. /usr/local/bin/oraenv -s&apos; &gt;&gt; /etc/profile.d/oraenv.sh
echo &apos;alias sqlplus=&quot;rlwrap sqlplus&quot;&apos; &gt;&gt; /etc/profile.d/oraenv.sh
echo &apos;alias rman=&quot;rlwrap rman&quot;&apos; &gt;&gt; /etc/profile.d/oraenv.sh
. /etc/profile.d/oraenv.sh
</code></pre>
<p>Then enable Oracle Database 18c XE service for automatic startup:</p>
<pre><code class="language-bash">systemctl enable oracle-xe-18c
</code></pre>
<blockquote>
<p><mark>Note</mark> that <strong>CentOS 7</strong> uses <code>systemd</code> instead of <code>sysconfig</code> to run system services, hence you should also stop and start the <code>oracle-xe-18c</code> service using <code>systemctl</code> instead of using <code>lsnrctl</code> command. Otherwise, you are risking to find yourself with the listener not working properly.</p>
</blockquote>
<h3 id="connectingtodatabase">Connecting to database</h3>
<p>And we are ready to log into the database and check if everything is good:</p>
<pre><code class="language-sql,line-numbers">sqlplus /nolog
-- connect to the database
SQL&gt; connect sys as sysdba

-- basic query to check if everything works
SQL&gt; select * from dual;
    
-- check components and their versions
SQL&gt; select comp_id, version, status from dba_registry;
    
SQL&gt; exit
</code></pre>
<p>That is it! By now we have successfully installed the XE instance and it is up and running.</p>
<blockquote>
<p><mark>Note</mark> that since 12c Oracle Database has multitenant architecture, which means there could be several <em>pluggable</em> databases and one <em>multitenant container database</em>. By default, the <code>XEPDB1</code> pluggable database is created during the installation of XE.</p>
</blockquote>
<p>To make it easier to connect to the pluggable database, I recommend editing of <code>tnsnames.ora</code> file and add there a new connection descriptor that we are going to use:</p>
<pre><code class="language-bash">mcedit /opt/oracle/product/18c/dbhomeXE/network/admin/tnsnames.ora
</code></pre>
<p>Add this record there below the standard <code>XE</code> record:</p>
<pre><code class="language-bash"> PDB1 =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = XEPDB1)
    )
  )
</code></pre>
<p>And save the changes.</p>
<h2 id="installationofthelatestversionofapex">Installation of the latest version of APEX</h2>
<p>Previously installation process of the latest version of <em>Oracle Application Express</em> (also known as <em>APEX</em>) consisted of deinstalling of the previous version and then installing of a new version. In 18c Oracle stopped to ship the Express Edition of their RDBMS with APEX preinstalled. So the step to deinstall the preinstalled version od <em>APEX</em> is not needed anymore.</p>
<p>Another difference will be in the fact that we are going to utilize the multitenant architecture of the <em>18c XE</em> and will be installing our environment into the <em>pluggable database</em>. This enables us to potentially have different versions of <em>APEX</em> installed into different PDBs.</p>
<p>So now the installation process roughly consists of unzipping of the downloaded archive with the freshest version of <em>APEX</em>, connecting to the PDB, running a few installation scripts and then copying static files to your web server directory.</p>
<h3 id="installationofcurrentversionofapex">Installation of current version of APEX</h3>
<p>So let&apos;s get it started. Change your directory back to <code>/root</code>, unzip the <em>APEX</em> archive and make the user <code>oracle</code> the owner of the directory. Considering we are installing the 18.2 version of <em>APEX</em>, it would look like this:</p>
<pre><code class="language-bash">cd /root
mkdir -p /opt/oracle/apex
unzip apex_18.*.zip -d /opt/oracle
chown -R oracle:oinstall /opt/oracle/apex
</code></pre>
<blockquote>
<p><mark>Note</mark> that we will be installing the <em>full development environment</em> of <em>APEX</em>. This could be converted to a <em>runtime only environment</em> if needed. Refer to the <a href="https://docs.oracle.com/database/apex-18.2/HTMIG/converting-between-runtime-and-full-development-environments.htm#HTMIG29186">official documentation</a> for this. The <em>full development environment</em> should be used only when the <em>Application Builder</em> is needed. On a production environment it is strongly recommended to choose the <em>runtime only</em> setup.</p>
</blockquote>
<p>From the <em>APEX</em> new home directory connect to our pluggable database as <code>sysdba</code> and run the installation scripts (we will be using <code>SYSAUX</code> schema for <em>APEX</em> metadata):</p>
<pre><code class="language-sql,line-numbers">cd /opt/oracle/apex
-- connect to the database
sqlplus sys@pdb1 as sysdba

-- run the script to install a full development environment
SQL&gt; @apexins.sql SYSAUX SYSAUX TEMP /i/
   
 -- create an instance administrator user and set their password
SQL&gt; @apxchpwd.sql
    
-- configure REST Data Services (needed for ORDS to serve workspaces and applications static files)
SQL&gt; @apex_rest_config.sql

-- disable embedded PL/SQL gateways
SQL&gt; exec dbms_xdb.sethttpport(0);
SQL&gt; exec dbms_xdb.setftpport(0);

-- unlock and set up APEX public user, this is needed for ORDS to be able to connect to APEX engine
SQL&gt; alter user apex_public_user account unlock;
SQL&gt; alter user apex_public_user identified by &quot;APEX_PUBLIC_USER&quot;;

-- add ACL to enable outgoing connections for APEX internal user
-- this is needed for the APEX_EXEC and APEX_WEB_SERVICE APIs to function properly
-- change it for a more strict policy if needed
SQL&gt; begin
    dbms_network_acl_admin.append_host_ace(
        host =&gt; &apos;*&apos;,
        ace =&gt; xs$ace_type(
            privilege_list =&gt; xs$name_list(&apos;connect&apos;),
            principal_name =&gt; &apos;APEX_180200&apos;,
            principal_type =&gt; xs_acl.ptype_db))
        ;
end;
/

-- now disconnect from the database
SQL&gt; exit
</code></pre>
<p>Then we need to copy <em>APEX</em> static files (images, stylesheets, JS files and so on) to the web server directory:</p>
<pre><code class="language-bash">mkdir -p /var/www/apex/images
cp -a /opt/oracle/apex/images/. /var/www/apex/images
</code></pre>
<p>Now we finished with the <em>Application Express</em> installation.</p>
<h2 id="installationofords">Installation of ORDS</h2>
<p>The Oracle Rest Data Services (ORDS) installation consists of unzipping the downloaded archive, running the configuration command, and then deploying the <code>ords.war</code> file into the <em>Tomcat</em> webapps folder.</p>
<p>Change back your directory to <code>/root</code> and unzip the <em>ORDS</em> archive:</p>
<pre><code class="language-bash">cd /root
mkdir -p /opt/oracle/ords
unzip ords-18.*.zip -d /opt/oracle/ords
</code></pre>
<p>Run the ORDS configuration command before deployment. Choose the advanced mode - in this case the installation process will be interactive:</p>
<pre><code class="language-bash">cd /opt/oracle/ords
java -jar ords.war install advanced
</code></pre>
<p>When prompted for <em>ORDS</em> configuration directory (the first question), enter <code>config</code>. Then provide the connection info to your pluggable database (specify <code>XEPDB1</code> for the service name).</p>
<blockquote>
<p><mark>Note</mark> that we specified <code>XEPDB1</code> here, not <code>PDB1</code>, because ORDS needs the service name, not your <code>TNSNAMES</code> entry by default to connect to your database. However, when you complete the installation, you can change ORDS settings to use TNS as the connection method. Find out how in the <a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/18.3/aelig/about-REST-configuration-files.html">official documentation</a>.</p>
</blockquote>
<blockquote>
<p><mark>Note</mark> that &quot;RESTful Services&quot; are required by <em>APEX</em> 5 and above, so enable this by specifying passwords for the <code>APEX_LISTENER</code> and <code>APEX_REST_PUBLIC_USER</code> when prompted.</p>
</blockquote>
<p>After the configuration is completed, the values are saved in <code>opt/oracle/ords/config/ords/defaults.xml</code> file and may be modified there. You can find more information about possible <em>ORDS</em> configuration options in the <a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/18.3/aelig/about-REST-configuration-files.html">official documentation</a>.</p>
<p>The <code>tomcat</code> user (created as part of <em>Tomcat</em> install) must have read-write access to the <em>ORDS</em> configuration folder:</p>
<pre><code class="language-bash">chown -R tomcat:tomcat /opt/oracle/ords/config
</code></pre>
<p>Now it&apos;s high time to deploy <em>ORDS</em> to <em>Tomcat</em> application server. Copy the <code>ords.war</code> into the <em>Tomcat</em> <code>webapps</code> directory for this (and we will restart the <em>Tomcat</em> service later):</p>
<pre><code class="language-bash">cp -a /opt/oracle/ords/ords.war /usr/share/tomcat/webapps/
</code></pre>
<p>Done! We succeeded in installing of <em>ORDS</em> and deploying it to <em>Tomcat</em> by now. Only one step is left.</p>
<h2 id="configurationofapachehttpdtomaphttprequeststoords">Configuration of Apache httpd to map HTTP requests to ORDS</h2>
<p>The last, but not the least step in this part of the guide is to configure <em>Apache httpd</em> to map HTTP-requests to <em>ORDS</em> and therefore <em>APEX</em> engine.</p>
<p>For this, add a custom <code>httpd</code> configuration file. By default, every <code>.conf</code> file placed in the <code>etc/httpd/conf.d/</code> directory is read by <code>httpd</code> as an additional configuration file to the main <code>/etc/httpd/conf/httpd.conf</code> config file.</p>
<blockquote>
<p><mark>Note</mark> that these additional config files are read and processed by <code>httpd</code> in alphabetical order, so name your custom config accordingly if you use multiple config files.</p>
</blockquote>
<p>So, let&apos;s create the <code>10-apex.conf</code> file in the <code>etc/httpd/conf.d/</code> directory with the contents as below:</p>
<pre><code class="language-bash,line-numbers"># additional apache httpd configuration for apex requests proxying
# add this to the end of /etc/httpd/conf/httpd.conf
# or put it in a separate file such as /etc/httpd/conf.d/10-apex.conf

# forward ORDS requests to tomcat
&lt;VirtualHost *:80&gt;
    # uncomment the lines below if you plan to serve different domains 
    # on this web server, don&apos;t forget to change the domain name
    # ServerName yourdomain.tld
    # ServerAlias www.yourdomain.tld
    
    # alias for APEX static files
    Alias &quot;/i&quot; &quot;/var/www/apex/images/&quot;

    # uncomment the line below if you want 
    # to redirect traffic to ORDS from root path
    # RedirectMatch permanent &quot;^/$&quot; &quot;/ords&quot;

    # proxy ORDS requests to tomcat
    ProxyRequests off
    &lt;Location &quot;/ords&quot;&gt;
        ProxyPass &quot;ajp://localhost:8009/ords&quot;
        ProxyPassReverse &quot;ajp://localhost:8009/ords&quot;
    &lt;/Location&gt;
&lt;/VirtualHost&gt;
</code></pre>
<p>Now you are ready to save the configuration file and restart the services.</p>
<h2 id="restartingoftheservices">Restarting of the services</h2>
<p>In order for changes to take effect, we need to restart the services:</p>
<pre><code class="language-bash">systemctl restart httpd
systemctl restart tomcat
</code></pre>
<p>And finally, you&apos;re ready to access <em>APEX</em> from your web browser using a link like <code>http://yourdomain.tld/ords</code> (or <code>http://yourdomain.tld</code> in case you switched on force redirection), where <code>yourdomain.tld</code> is the domain name or the IP-address of your server.</p>
<blockquote>
<p>In case you&apos;re facing the situation when <em>ORDS</em> does not map <em>APEX</em> <em>application and workspace static files</em> properly after its installation (I mean, <em>APEX</em> engine static files work fine, but images, style sheets and other files from your applications generate the <em>404 not found</em> error), try validating of the <em>ORDS</em> installation (and then restart the <code>tomcat</code> service):</p>
</blockquote>
<pre><code class="language-bash">cd /usr/share/tomcat/webapps/
java -jar ords.war validate
systemctl restart tomcat
</code></pre>
<hr>
<h2 id="nextchapters">Next Chapters</h2>
<p>My congratulations! If you achieved this point, it means <em>all the mandatory things are set up and should be running nicely</em>. The next, final chapters of this guide will be covering additional steps to improve your users experience and security of the installation, but all the further actions are <strong>not required</strong> for the installation.</p>
<p>Here are the links for your convenience:</p>
<ul>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-three">Part 3. Additional configuration (optional)</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-four">Part 4. Setting up SSL, redundancy and backups (optional)</a></li>
</ul>
<h2 id="previouschapters">Previous Chapters</h2>
<p>In case you missed some previous steps, please, use the following links to catch up:</p>
<ul>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-introduction">Introduction Part. APEX environment architecture and its components</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-one">Part 1. CentOS installation and its configuration for APEX</a></li>
</ul>
<p>Hope this post will be helpful for you. Be free to leave your comments if you have some advice how to improve the guide or if you found a mistake!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one]]></title><description><![CDATA[The first part of the step-by-step all-in-one guide on how to install a full APEX environment on CentOS. It covers installation and configuration of the operating system for Oracle software and APEX in particular.]]></description><link>https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-one/</link><guid isPermaLink="false">5ab9e4745821510f9c801b59</guid><category><![CDATA[apex]]></category><category><![CDATA[oracle]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Fri, 16 Nov 2018 05:10:00 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/03/cover-square-part-one.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/03/cover-square-part-one.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one"><p>In this chapter of the guide we will be covering the installation and configuration process of <strong>CentOS</strong> Linux on our server (it could be on-prem or VPS - doesn&apos;t matter). Then we will prepare our setup a bit for Oracle software stack.</p>
<h2 id="installationofcentoslinux">Installation of CentOS Linux</h2>
<p>This chapter is here for Linux newcomers, so, if you think you are qualified enough to install the system on your own, be free to skip this chapter totally.</p>
<p>But in case you want to be sure you won&apos;t be doing anything wrong, proceed following my recommendations - I tested them myself at least twice - when installed the system on my server and then when prepared this post.</p>
<p>You can obtain the distribution from the <strong>CentOS</strong> project <a href="https://www.centos.org/download/">official website</a>. Any option of the distro will be suitable, because we are going to set up the minimum install. In the examples below I will be using a <em>DVD-iso version</em>. After you download the disk image, you will need to prepare a <em>bootable CD or flash drive</em> (preferred) with it. I won&apos;t stop on how to make one, but if you&apos;re stuck, there&apos;s even an <a href="https://wiki.centos.org/HowTos/InstallFromUSBkey">official instruction</a> for you. Only one thing here - do not use <code>Unetbootin</code> for this, because many people report some problems during installation process when after using this stuff - installation just freezes somewhere in the middle. I myself used <code>rawrite32</code>, but there are also other options like <code>dd</code>, <code>dd for windows</code> or <code>Win32 Disk Imager</code>.</p>
<p>Ok, now, when you&apos;re ready with your bootable media, let&apos;s start the system with it already. After doing so, <strong>Anaconda</strong> installer should greet you:</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step1.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>After its loading and choosing the language, you&apos;re ended up on the <em>Installation Summary</em> screen:</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step2.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>Here is the first tip - it is a good idea to set up a <em>network connection</em> first, because it&apos;ll help a bit with the further installation. Choose <em>Network &amp; Host Name</em> in the menu, toggle the connection <em>On</em> and then click the <em>Configure...</em> button in the right bottom corner, here choose <em>IPv4 Settings</em> tab and now you&apos;re able to choose <strong>DHCP</strong> or add a <strong>static IP address</strong> if needed:</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step3.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>If everything is ok, you should see something like this:</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step4.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>Now we are ready to set up everything else. Let&apos;s start with date and time. Go back to the <em>Installation Summary</em> screen and choose <em>Date &amp; Time</em> in the menu. On the next screen set your region and city which in its place will set a correct timezone for you, and switch the <em>Network Time</em> on. Everything else will be set up for you automatically because you should already have an Internet connection.</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step5.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>Then keyboard settings. Click <em>Keyboard</em> in the menu. For many users default settings will be ok here, but if you, like me, need an additional layout, just add it. Then configure layout switching options using the <em>Options</em> button. Yes, I&apos;m from dark <em>ctrl+shift</em> side.</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step6.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>If you need support for a different language, click <em>Language Support</em> in the main menu and add the desired languages.</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step7.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>Under the <em>Installation Source</em> menu item you will see that you&apos;re installing the system from local source. It is totally ok and nothing should be done here. Now, just check that you are installing the <em>Minimal Install</em> option under the <em>Software Selection</em> menu item. Everything else we need we are going to install manually.</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step9.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>Now the most interesting part - disk partitioning. Click the <em>Installation Destination</em> menu item and select the desired hard disk. If you have only one, it is checked by default. Now I&apos;d recommend you to select <em>I will configure partitioning</em> in order to be able to partition the disk manually. Click <em>Done</em> then.</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step10.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>On the next screen you&apos;re invited to choose your partitioning schema. I prefer clicking the link <em>Click here to create them automatically</em> to <em>pre-create</em> the schema. When you&apos;re done, you see a screen like this one.</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step12.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>The size of the partitions depend on the amount of RAM in your computer and on the size of the hard drive respectively. As you can see, by default <strong>CentOS 7</strong> offers you a standard partition type for <code>/boot</code> and an LVM-based root partition, both of <code>xfs</code> filesystem. I&apos;m quite ok with this, but if you like, you can choose a standard partition type for your root partition, or a filesystem, different to <code>xfs</code> (for example, <code>ext4</code>, which is also very reliable). Advantage of using LVM-based partitions is the fact they could be easily extended with another physical drive (your logical drive will be the same) or even shrunk (though not on <code>xfs</code> filesystem, for this you will need, for example, <code>ext4</code>). However, LVM is not supported on a boot partition. The only things that I changed in this standard partitioning schema were the name of the <em>Volume Group</em> and the swap size.</p>
<blockquote>
<p><mark>Note</mark> the fact that <strong>Oracle Database XE</strong> requires at least 2GB of swap size and recommends the swap size doubles the size of the RAM available.</p>
</blockquote>
<p>Look through the summary when you click the <em>Done</em> button and then click <em>Accept Changes</em>.</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step13.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>Click the Begin Installation button to start the installation process finally. During the installation you will be prompted to set the <code>root</code> user password. I&apos;d recommend to set a long enough but easy to remember password, something like <code>WithGreatPowerComesGreatResponsibility</code> or <code>YouKnowNothingJohnSnow</code>.</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step15.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>Click the <em>Reboot</em> button when the installation ends and remove your installation media.</p>
<p><img src="https://dsavenko.me/content/images/2018/03/step17.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - part one" loading="lazy"></p>
<p>Congratulations! <strong>CentOS</strong> Linux should be properly installed on your machine now and we are ready to install and configure the needed software.</p>
<h2 id="updatingtheoperationsystem">Updating the operation system</h2>
<p>It is a good idea to update your system regularly. Assuming something could change from time the distribution was released, we need to update the system after first start.</p>
<blockquote>
<p><code>yum</code> is the powerful and standard package manager for <strong>CentOS</strong> Linux. I recommend using it everytime you are installing or deinstalling software, because it keeps a reliable journal of all installations and can automatically resolve all dependencies when you install or deinstall software.</p>
</blockquote>
<p>So, to make your system up-to-date execute this:</p>
<pre><code class="language-bash">yum upgrade -y
</code></pre>
<p>Now we are ready to install software.</p>
<h2 id="installingprerequisitesandsomeusefulutils">Installing prerequisites and some useful utils</h2>
<p>To install some pre-requisites useful software crucial for comfortable usage of your system, execute these commands:</p>
<pre><code class="language-bash">yum install java-1.8.0-openjdk.x86_64 java-1.8.0-openjdk-devel.x86_64 mc net-tools.x86_64 htop iotop iftop unzip wget epel-release -y
yum install rlwrap -y
</code></pre>
<p>A few words about the packages installed:</p>
<ul>
<li><code>java-1.8.0-openjdk.x86_64</code> and <code>java-1.8.0-openjdk-devel.x86_64</code> are Java 8 Development Kit packages, which are needed for <strong>Tomcat</strong> and <strong>ORDS</strong> to operate.</li>
</ul>
<blockquote>
<p>Installing <strong>Java</strong> from official repository using <code>yum</code> should set all the needed environment variables. But you can check it by executing the command <code>java -version</code> and if it works, everything is ok. But if not, then correctly set the <code>$PATH</code> and <code>$JAVA_HOME</code> variables.</p>
</blockquote>
<ul>
<li><code>mc</code> - <strong>Midnight Commander</strong> - a really powerful file manager similar to <strong>Norton Commander</strong> or <strong>FAR Manager</strong>.</li>
<li><code>net-tools.x86_64</code> - useful and customary network utilities such as <code>ifconfig</code> and <code>netstat</code>. The thing is in <strong>CentOS 7</strong> they replaced these usual utilities with <code>ip</code> and <code>ss</code> <code>commands</code>, so to be able to use <code>ifconfig</code> it should be installed manually.</li>
<li><code>htop</code> - a very good alternative to standard <code>top</code> utility. It is much more powerful and can be flexibly configured.</li>
<li><code>iotop</code> - input/output read/write monitoring utility. Very useful to check disk problems when using a database.</li>
<li><code>iftop</code> - an utility to monitor network bandwidth.</li>
<li><code>unzip</code> - unzip utility.</li>
<li><code>wget</code> - a command-line utility to fetch files from the remote source (HTTP or FTP-host for example).</li>
<li><code>epel-release</code> - an additional repository for <strong>CentOS</strong> with loads of useful software, called <strong>EPEL</strong> (stands for <em>Extra Packages for Enterprise Linux</em>).</li>
<li><code>rlwrap</code> - a command-line wrapper utility, irreplaceable thing when it comes to use <code>sqlplus</code> on Linux. Always use <code>rlwrap sqlplus</code> instead of just <code>sqlplus</code> to see the difference. These utils installs from <strong>EPEL</strong> repository.</li>
</ul>
<h2 id="initialsystemconfiguration">Initial system configuration</h2>
<h3 id="networktimesynchronization">Network time synchronization</h3>
<p>There&apos;s an utility called <code>chrony</code> for this purpose in the minimal <strong>CentOS</strong> installation:</p>
<pre><code class="language-bash">systemctl start chronyd
systemctl enable chronyd
</code></pre>
<h3 id="disablingselinuxoncentos7">Disabling SELinux on CentOS 7</h3>
<p>Now, we need to disable <code>selinux</code>. Its configuration and usage is a topic for a different series of blog posts, so here we&apos;ll just omit all this.<br>
Type this command:</p>
<pre><code class="language-bash">mcedit /etc/sysconfig/selinux
</code></pre>
<p>And change the value <code>SELINUX=enforcing</code> to <code>SELINUX=disabled</code>, then save the config file. After doing this, execute this to disable <code>selinux</code> in the runtime:</p>
<pre><code class="language-bash">setenforce 0
</code></pre>
<h3 id="configuringthefirewall">Configuring the firewall</h3>
<p><strong>CentOS 7</strong> uses <code>firewalld</code> as a main firewall service, which is an additional abstraction level above <code>iptables</code>. In many other guides you could see people disabling it and returning to use <code>iptables</code> directly. I don&apos;t know why, maybe because it always hard to pick up something new. And so, I tried, and really liked <code>firewalld</code> ease of configuration (especially in comparison to <code>iptables</code> if you&apos;re new to it).<br>
To configure the firewall, we are going to do these things:</p>
<ol>
<li>Add a new service called <code>oracle-db</code>.</li>
<li>Set the new service description and add the port to it.</li>
<li>Enable such services as <code>http</code>, <code>https</code> and <code>oracle-xe</code> for the default zone <code>public</code>.</li>
<li>Reload firewall list of rules on-the-fly.<br>
To do this all, execute the commands below:</li>
</ol>
<pre><code class="language-bash">firewall-cmd --permanent --new-service=oracle-db
firewall-cmd --permanent --service=oracle-db --set-short=&quot;Oracle Database Listener&quot; --add-port=1521/tcp
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --permanent --zone=public --add-service=oracle-db
firewall-cmd --reload
</code></pre>
<p>Now we are ready to install the <strong>Apache Tomcat</strong> and <strong>httpd</strong>.</p>
<h2 id="installingofapachetomcatandapachehttpd">Installing of Apache Tomcat and Apache httpd</h2>
<p>To install both the application and the web server we will be using <code>yum</code>, because it is the safest and the easiest way I know so far. You&apos;re still able to manually download and install them, but this is not the aim of this guide. Only one thing here - it is assumed that you have completed all the steps from above (installing <strong>JDK</strong> in particular) before proceeding with the installation.</p>
<p>So, in order to install <strong>Tomcat</strong> and <strong>httpd</strong> run this:</p>
<pre><code class="language-bash">yum install tomcat httpd -y
</code></pre>
<p>This will install <strong>Tomcat 7</strong> and <strong>Apache httpd 2.4</strong> on your system. The 7th version of Tomcat is more than enough for us. Then, to start the services and to start them on startup, let&apos;s do the next:</p>
<pre><code class="language-bash">systemctl start tomcat
systemctl enable tomcat
systemctl start httpd
systemctl enable httpd
</code></pre>
<p>That&apos;s it! By now both <strong>Tomcat</strong> and <strong>httpd</strong> should work on your system and should listen ports 8080 and 80 respectively on your server. Note the fact that we intentionally didn&apos;t open port 8080 on our server, because we are not going to use it. Instead, <strong>httpd</strong> will reverse proxy all requests to <strong>Tomcat</strong> using <strong>AJP</strong> protocol listener on port 8009.</p>
<blockquote>
<p>You even may disable the HTTP connector on port 8080 in <strong>Tomcat</strong>&apos;s <code>server.xml</code> config (this is very optional). I will not tell you how to do this, consider it an exercise. Do not forget to restart the <strong>Tomcat</strong> service afterwards in case you already opened your <code>mc</code> file manager.</p>
</blockquote>
<hr>
<h2 id="nextchapters">Next Chapters</h2>
<p>Here we&apos;re done with the operating system and software installation and initial configuration. Our further steps will be to install Oracle software and then to configure it properly.</p>
<p>Here are the links for your convenience:</p>
<ul>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-two">Part 2. Installation of Oracle Database, ORDS and APEX itself</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-three">Part 3. Additional configuration (optional)</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-four">Part 4. Setting up SSL, redundancy and backups (optional)</a></li>
</ul>
<h2 id="previouschapters">Previous Chapters</h2>
<p>In case you missed some previous steps, please, use the following links to catch up:</p>
<ul>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-introduction">Introduction Part. APEX environment architecture and its components</a></li>
</ul>
<p>Hope this post will be helpful for you. Be free to leave your comments if you have some advice or found a mistake!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - introduction]]></title><description><![CDATA[The introduction part of a step-by-step all-in-one guide on how to install Oracle Database Express Edition, Oracle Application Express aka APEX, Oracle Rest Data Services, Apache Tomcat and Apache httpd on a CentOS 7 server. ]]></description><link>https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-introduction/</link><guid isPermaLink="false">5ab8b43e5821510f9c801b53</guid><category><![CDATA[apex]]></category><category><![CDATA[oracle]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Fri, 16 Nov 2018 05:00:00 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/03/cover-square.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/03/cover-square.png" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - introduction"><p>With this introduction post I am starting to write a series of blog posts about how to install the full production environment of <a href="apex.oracle.com">Oracle Application Express</a> aka <strong>APEX</strong> on a <a href="https://www.centos.org/">CentOS Linux 7</a> server. Usual <strong>APEX</strong> production environment involves such components as the OS itself, Apache httpd (or another web server), Apache Tomcat (or another supported application server), ORDS (Oracle Rest Data Services), APEX engine and Oracle Database. If you need something like this for your experiments or a startup, you&apos;re at the right place.</p>
<h2 id="preamble">Preamble</h2>
<p>This series of blog posts assume you&apos;re familiar with <strong>Oracle</strong> software stack (database, APEX, ORDS) at least to a certain extent, but that you&apos;re new to Linux and installing all this on this OS.<br>
Then I can openly say that this guide was inspired by <a href="https://ora-00001.blogspot.ru/2015/06/installing-oracle-xe-ords-and-apex-on-centos-linux-part-zero.html">well-done made almost the same guide</a> by <em>Morten Braten</em>, although it was about the previous and sometimes outdated versions of the involved software and there are some specialties which I am covering in this guide. Frankly speaking, I personally sometimes used the Morten&apos;s guide when was installing and configuring the software for the first time.<br>
There&apos;s also a <a href="https://github.com/OraOpenSource/oraclexe-apex">bunch of scripts</a> developed by <em>Martin D&apos;Souza</em> and company which could help you automatically install all the needed software, however this is not the topic of this guide.<br>
The main idea of the current guide is not just to help you with <em>how to install this or that piece of software</em>, but also to comment on each action and to explain why we need it.</p>
<h2 id="closerlookatcomponents">Closer look at components</h2>
<p><a href="https://dsavenko.me/content/images/2018/03/architecture.jpg"><img src="https://dsavenko.me/content/images/2018/03/architecture.jpg" alt="Oracle Database XE, APEX, ORDS, Tomcat and httpd on CentOS 7: all-in-one guide - introduction" loading="lazy"></a></p>
<p>On the schema above (it&apos;s clickable) I wanted to show the role of each of the components in our setup. This setup was chosen intentionally, because it consists <strong>only of free software</strong> and can be obtained by anybody. Now we are going to stop on each component in detail.</p>
<h3 id="centoslinux7">CentOS Linux 7</h3>
<p>I think it is redundant to explain why we need an operating system. Of course we need one, because without it our server is just a pile of metal. A more interesting question is why <strong>CentOS</strong>. And I have an answer - because it is a community driven version of a very reliable RPM-based commercial linux distribution called <a href="https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux">RHEL</a> (<em>Red Hat Enterprise Linux</em>). Each time a new version of <strong>RHEL</strong> comes out, they have to share their source code to public (due to license limitations), and contributors of <strong>CentOS</strong> take this new version, re-brand, compile and distribute it for free (<strong>CentOS</strong> stands for <em>Community enterprise Operating System</em>). There&apos;s also another option - <a href="https://www.oracle.com/linux/index.html">Oracle Linux</a>, which developers do almost the same, but the <strong>CentOS</strong> community seems to be way bigger and it&apos;s much easier to find an answer on your question when you&apos;re stuck somewhere.</p>
<p>An <em>RPM-based distribution</em>, in its place, was chosen because Oracle distributes their express edition of the RDBMS only as the RPM-package. And to minimize potential problems with the setup, I went for <strong>CentOS</strong> (though it is still possible to install it on a <strong>Debian</strong>-based system or even on <strong>FreeBSD</strong>).</p>
<h3 id="oracledatabasexe">Oracle Database XE</h3>
<p><strong>APEX</strong> engine lives inside the <strong>Oracle Database</strong>. It&apos;s available for free and can be installed into its <em>Express Edition</em> as well, which is a totally free option. Current version of the RDBMS is <strong>18<em>c</em></strong>, and we are going to install exactly this release. Despite the fact that this is only a limited free version of the RDBMS, it offers a lot of great features, which were usually included only with the <em>Enterprise Edition</em> of it, and you are welcome to use them to your benefit.</p>
<h3 id="apex">APEX</h3>
<p><strong>APEX</strong> or Application Express is a low-code web development platform. Quoting the official website, it enables you to design, develop and deploy beautiful, responsive, database-driven applications using only your web browser. It is a free <em>Oracle Database</em> feature. <strong>APEX</strong> needs a web listener to function - and there are three options available at the moment:</p>
<ol>
<li><em>Embedded PL/SQL Gateway (EPG)</em> - it is a built-in <em>Oracle XDB</em> feature. It is not recommended to be used in production environments, because it&apos;s not as fast and as reliable as other options. Though it surely could be used for development and testing purposes.</li>
<li><em>mod_plsql with Oracle HTTP Server</em> - this is an obsolete option which is not recommended to be used anymore.</li>
<li><em>Oracle Rest Data Services (ORDS)</em> - the option which is officially recommended for production use by <em>Oracle</em> and the one we will be observing here.</li>
</ol>
<h3 id="ords">ORDS</h3>
<p><strong>ORDS</strong> is a Java EE-based web application which can run in standalone mode or could be deployed to an application server such as <strong>Oracle WebLogic</strong>, <strong>GlassFish</strong> or <strong>Apache Tomcat</strong>. When run in standalone mode, it leverages a built-in web server powered by <strong>Jetty</strong>.<br>
Besides being a listener for <strong>APEX</strong>, <strong>ORDS</strong> could be used to implement <em>RESTful APIs</em> for your databases. And by saying <em>databases</em> I mean relational databases, document stores and even <strong>Oracle NoSQL Database</strong>.</p>
<h3 id="apachetomcat">Apache Tomcat</h3>
<p>The <strong>Apache Tomcat</strong> software is an open source implementation of the Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket technologies. It is a really powerful and fast application server, which is totally free piece of software and is community driven. At the time it is the most commonly used application server for Java applications.<br>
In our setup it is going to be used because of ability to be flexibly configured and for security reasons. It is also much more convenient to use Tomcat service instead of standalone mode of <strong>ORDS</strong>.</p>
<h3 id="apachehttpd">Apache httpd</h3>
<p><strong>Apache httpd</strong> is a standard de-facto when in comes to HTTP server software. I think it is extra to tell more about it. We are going to use it because it gives us even more freedom in configuration and because <strong>Apache httpd</strong> is the fastest solution when it comes to static files such as pictures, style sheets and so on. In our setup it will serve <strong>APEX</strong> static files and will be reverse-proxying requests to <strong>ORDS</strong> deployed to <strong>Tomcat</strong> using the special <em>AJP</em> protocol, which eliminates HTTP-like overhead.</p>
<hr>
<h2 id="nextchapters">Next chapters</h2>
<p>And this is where I wanted to stop with the introduction part. In the next chapters we&apos;re going to start with OS installation and then proceed to everything else.</p>
<p>Be free to leave your comments on the above and stay tuned for the next chapters!</p>
<p>Here are the links for your convenience:</p>
<ul>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-one">Part 1. CentOS installation and its configuration for APEX</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-two">Part 2. Installation of Oracle Database, ORDS and APEX itself</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-three">Part 3. Additional configuration (optional)</a></li>
<li><a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-part-four">Part 4. Setting up SSL, redundancy and backups (optional)</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?]]></title><description><![CDATA[Is it possible to base an APEX application on top of, let's say, PostgreSQL? What about MySQL? The answer is YES, it is possible right now! Find out how in the article.]]></description><link>https://dsavenko.me/read-write-apex-application-fully-based-on-alien-data/</link><guid isPermaLink="false">5b91792b923c0b070052a545</guid><category><![CDATA[apex]]></category><category><![CDATA[pl/sql]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Mon, 12 Nov 2018 15:52:52 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/11/alf-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/11/alf-cover.jpg" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?"><p>I am sometimes asked by my fellows whether it is possible or not to base an APEX application on some foreign database, in example <em>PostgreSQL</em> or <em>MySQL</em>. I always answer positively, but so far didn&apos;t have a chance to explore the topic in detail. In this article I am going to show you at least two possible ways of how to do it right now.</p>
<h1 id="theaim">The aim</h1>
<p>We are going to develop an APEX application inside an Oracle Database 18c XE and base it on data provided by an alien database. In our case it is going to be <a href="https://www.postgresql.org/">PostgreSQL</a> (but it doesn&apos;t have to). On top of PostgreSQL we are going to organize a REST interface. Then, by leveraging Oracle Database and APEX capabilities to consume REST interfaces, we are going to implement <em>CRUD</em> functionality over two simple <code>departments</code> and <code>employees</code> tables, using two different approaches.</p>
<p>So, in the end, our application is going to be able to read and write data from two native PostgreSQL tables, and represent them as interactive reports.</p>
<h1 id="beforewestart">Before we start</h1>
<p>In the article I am going to consider a setup with two separate hosts - the first with Oracle Database and APEX and the second with PostgreSQL. In my case they both are going to be virtual machines powered by CentOS Linux 7.5 and resided in the same local network area. IP addresses are <strong>192.168.88.30</strong> and <strong>192.168.88.31</strong> for the Oracle and Postgres hosts respectively.</p>
<p>However, you don&apos;t have to go for the same setup and install software on different hosts, it is done here to make the post more general, and you should be quite ok with Oracle Database and PostgreSQL installed on the same machine. In such a case it would even work faster since there would be no network involved.</p>
<p>Also assume that all the commands in the article are run as a <code>root</code> user.</p>
<h1 id="postgresqlside">PostgreSQL side</h1>
<p>As I already mentioned, as an alien database, where our data is going to be, we will be using PostgreSQL. And we need REST interface on top of it. There are several solutions for this, such as <a href="http://postgrest.org">PostgREST</a> or <a href="https://postgres.rest/">pREST</a>, which are open-sourced standalone tools written in Haskell and Go correspondingly. Both tools give their users a fully functional REST representation of SQL on top of a database, so no particular handlers needed. This is very similar to what we see when use <a href="https://www.oracle.com/database/technologies/appdev/rest.html">ORDS</a> <em>REST enabled SQL</em> feature. I stopped on <em>PostgREST</em>, which I found quite reliable judging by its users feedback.</p>
<h3 id="installationofpostgresql">Installation of PostgreSQL</h3>
<ol>
<li>Install EPEL and official PostgreSQL repos:</li>
</ol>
<pre><code class="language-bash">yum epel-release -y
rpm -Uvh https://yum.postgresql.org/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm
</code></pre>
<ol start="2">
<li>Install PostgreSQL 10 using yum:</li>
</ol>
<pre><code class="language-bash">yum install postgresql10-server postgresql10-contrib postgresql10 -y
</code></pre>
<ol start="3">
<li>Initialize database and enable PostgreSQL daemon:</li>
</ol>
<pre><code class="language-bash">postgresql-setup initdb
systemctl start postgresql-10
systemctl enable postgresql-10
</code></pre>
<ol start="4">
<li>Create a new database user which will own our tables:</li>
</ol>
<pre><code class="language-bash">su - postgres
createuser --interactive

Enter name of role to add: restapi
Shall the new role be a superuser? (y/n) n
Shall the new role be allowed to create databases? (y/n) n
Shall the new role be allowed to create more new roles? (y/n) n

createdb restapi
exit
</code></pre>
<ol start="5">
<li>Edit your PostgreSQL Client Authentication Configuration File:</li>
</ol>
<pre><code class="language-bash">mcedit /var/lib/pgsql/10/data/pg_hba.conf
</code></pre>
<ol start="6">
<li>And enable password authentication (make sure the file looks like below), then save changes:</li>
</ol>
<pre><code class="language-bash"># TYPE  DATABASE        USER            ADDRESS                 METHOD
&quot;local&quot; is for Unix domain socket connections only
local   all             all                                     md5
# IPv4 local connections:
host    all             all             127.0.0.1/32            md5
# IPv6 local connections:
host    all             all             ::1/128                 md5
</code></pre>
<ol start="7">
<li>Restart PostgreSQL server:</li>
</ol>
<pre><code class="language-bash">systemctl stop postgresql-10
systemctl start postgresql-10
</code></pre>
<ol start="8">
<li>Now we are ready to create our tables and load some sample data. For this, download <a href="https://github.com/ZZa/apex-on-postgresql-sample/blob/master/postgresql-data.sql">the file</a> I prepared for you and, being in the same directory with the file, load it to the database using <code>psql</code>:</li>
</ol>
<pre><code class="language-bash">psql -U restapi -f postgresql-data.sql
</code></pre>
<h3 id="installationofpostgrest">Installation of PostgREST</h3>
<ol>
<li>Download PostgREST binaries from GitHub and extract them to <code>/usr/bin</code></li>
</ol>
<pre><code class="language-bash">wget https://github.com/PostgREST/postgrest/releases/download/v5.1.0/postgrest-v5.1.0-centos7.tar.xz
tar -Jxf postgrest-v5.1.0-centos7.tar.xz -C /usr/bin
</code></pre>
<ol start="2">
<li>
<p><a href="http://postgrest.org/en/v5.1/admin.html#daemonizing">Daemonize</a> your PostgREST instance. Instead of doing all this you can run it manually.</p>
<ol>
<li>Create PostgREST configuration file:</li>
</ol>
<pre><code class="language-bash">mkdir -p /etc/postgrest
touch /etc/postgrest/config
</code></pre>
<ol start="2">
<li>Put some configuration into it (more <a href="http://postgrest.org/en/v5.1/install.html#configuration">here</a>):</li>
</ol>
<pre><code class="language-bash">db-uri = &quot;postgres://restapi:restapi@localhost:5432/restapi&quot;
db-schema = &quot;public&quot;
db-anon-role = &quot;restapi&quot;
db-pool = 10

# listen on all interfaces
server-host = &quot;0.0.0.0&quot;
# listen on port 8000
server-port = 8000
</code></pre>
<ol start="3">
<li>Create a file for new <code>systemd</code> module for PostgREST service:</li>
</ol>
<pre><code class="language-bash">touch /usr/lib/systemd/system/postgrest.service
</code></pre>
<ol start="4">
<li>Edit it and add the following</li>
</ol>
<pre><code class="language-bash">[Unit]
Description=REST API for any Postgres database
After=postgresql-10.service

[Service]
ExecStart=/bin/postgrest /etc/postgrest/config
ExecReload=/bin/kill -SIGUSR1 $MAINPID

[Install]
WantedBy=multi-user.target
</code></pre>
<ol start="5">
<li>Enable and start PostgREST service:</li>
</ol>
<pre><code class="language-bash">systemctl enable postgrest
systemctl start postgrest
</code></pre>
<ol start="6">
<li>Tweak firewalld to allow connections to PostgREST:</li>
</ol>
<pre><code class="language-bash">firewall-cmd --permanent --new-service=postgrest
firewall-cmd --permanent --service=postgrest --set-short=&quot;PostgREST&quot; --add-port=8000/tcp
firewall-cmd --permanent --zone=public --add-service=postgrest
firewall-cmd --reload
</code></pre>
</li>
</ol>
<h3 id="testingoftheenvironment">Testing of the environment</h3>
<p>Ready to test it? Just open <code>http://your-ip-address-here:8000</code> in your browser. In my case it&apos;s <code>http://192.168.88.31:8000</code>. If you see generated Swagger docs, then everything is alright. Otherwise, look into <code>/var/log/messages</code> where PostgREST writes its logs by default for the reason it doesn&apos;t work.</p>
<p>As an alternative, you also can use <a href="https://www.getpostman.com/">Postman</a> to test your freshly implemented REST API server.</p>
<h1 id="oracledatabaseandapexside">Oracle Database and APEX side</h1>
<p>Be aware of the fact that I am assuming that you already have a running APEX environment. If you don&apos;t, then you are welcome to use <a href="https://dsavenko.me/oracledb-apex-ords-tomcat-httpd-centos7-all-in-one-guide-introduction/">my very detailed guide</a> on how to create one.</p>
<p>But you also should know that I am going to use some features only available in the newest versions of Oracle Database (working with JSON in PL/SQL) and APEX (<code>APEX_EXEC</code> package). It&apos;s quite safe to go for <em>Oracle Database 18c XE</em> and <em>Oracle APEX 18.2</em>, which are currently available to download.</p>
<p>You also should allow outgoing connections for the APEX internal user by creating a proper ACL. This is needed to be able to make REST calls out from APEX applications. For APEX 18.2 this user is called <code>APEX_180200</code>, so to allow outgoing connections without restrictions by host, run this as <code>SYS</code>:</p>
<pre><code class="language-sql">begin
    dbms_network_acl_admin.append_host_ace(
        host       =&gt; &apos;*&apos;,
        ace        =&gt; xs$ace_type(
            privilege_list =&gt; xs$name_list(&apos;connect&apos;),
            principal_name =&gt; &apos;APEX_180200&apos;,
            principal_type =&gt; xs_acl.ptype_db
        )
    );
end;
/
</code></pre>
<p>Then, to start with the application development, choose a workspace or create a new one and then create a new application in it, all settings could be left in their defaults.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_1.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>And from now on we&apos;ll be considering different approaches to fulfil the task.</p>
<h2 id="declarativeway">Declarative way</h2>
<p>At the moment I am writing this post APEX support declarative capabilities for <em>Web Source Modules</em> only partially.</p>
<p>But the functionality is already on the roadmap, so very soon I&apos;ll be rewriting this section of the post in order to tell you how to do everything in a declarative way.</p>
<h2 id="semideclarativeorapex_execway">Semi-declarative or APEX_EXEC way</h2>
<p>This approach consists of three main steps to perform:</p>
<ol>
<li>Define a <em>Web Source Module</em> under <em>Shared Components</em> for the application.</li>
<li>Add an <em>Interactive Report</em> region onto the application&apos;s page, and base it on top of just created <em>Web Source</em> declaratively.</li>
<li>Use the power of <code>APEX_EXEC</code> package to implement writing procedures for the report programmatically.</li>
</ol>
<p>Let&apos;s sort them out in detail.</p>
<h3 id="definitionofwebsource">Definition of Web Source</h3>
<p>First of all, go to <em>Shared Components</em> from <em>AppBuilder</em> home page and choose <em>Web Source Modules</em>.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_2-1.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>Click <em>Create</em> button and choose <em>From Scratch</em>, then click <em>Next</em>.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_3-1.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>Leave <em>Simple HTTP</em> for the <em>Type</em>, since we are not going to use ORDS-based API, specify the <em>Name</em> and the <em>URL Endpoint</em> for the service. In our case they&apos;re going to be <code>ALIEN_DEPARTMENTS</code> and <code>http://192.168.88.31:8000/departments</code> respectively.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_4.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>Leave everything by default on the following two screens, click <em>Next</em> button two times, and then click <em>Discover</em>.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_5.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>If everything is ok, you&apos;ll see the preview screen. Check it out and click the <em>Create Web Source</em> button.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_6.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>When the module is created, click on it to expand the details. You can check its data profile by clicking on <em>Edit Data Profile</em> button, but since our table is pretty simple, APEX likely to guess all the datatypes correctly without our help. What we need to do is add other operations for our service. To do so, click the <em>Add Operation</em> button.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_7.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>First, we are going to add the <code>POST</code> operation. It represents the <code>INSERT</code> database operation in PostgREST. So, put <em>dot</em> symbol for <em>URL Pattern</em>, choose <code>POST</code> for the <em>HTTP Method</em> and <em>Insert row</em> for <em>Database Operation</em>. For <em>Request Body Template</em> use this JSON string: <code>{&quot;name&quot;:&quot;#NAME#&quot;, &quot;location&quot;:&quot;#LOCATION#&quot;, &quot;country&quot;:&quot;#COUNTRY#&quot;}</code>.<br>
Mind how I used placeholders for parameters. Then for each placeholder we need to add a parameter. Also, there&apos;s important to add a header parameter for <code>Content-Type</code>. As a result, you should have something like this.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_8.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>Similarly, we should add <code>PATCH</code> operation for the <code>UPDATE</code> database operation in PostgREST. The difference is in one addtional query string parameter <code>department_id</code>, which specifies what row to update.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_9.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>Last operation to add here is the <code>DELETE</code>, which serves the same database operation in PostgREST. Here we don&apos;t have to specify any parameters, but the only query string parameter <code>department_id</code>, which tells what row to delete.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_10.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>When you complete these steps, your <em>Web Source Module</em> metadata is succesfully saved in the application and now you can use it for report regions and for <code>APEX_EXEC</code> procedures calls.</p>
<h3 id="thecode">The code</h3>
<p>Now I&apos;d like you to switch to your favourite PL/SQL IDE and create a package with a set of procedures to work with the web service we have just created. Let&apos;s call this package <code>ALIEN_DATA_MANAGEMENT</code> and compile it as a workspace parsing schema user.</p>
<p>Package specification:</p>
<pre><code class="language-plsql,line-numbers">create or replace package alien_data_management is

procedure add_department(
  p_name varchar2,
  p_location varchar2,
  p_country varchar2  
);

procedure update_department(
  p_department_id integer,
  p_name varchar2,
  p_location varchar2,
  p_country varchar2  
);

procedure delete_department(
  p_department_id integer
);

end alien_data_management;
/
</code></pre>
<p>Package body:</p>
<pre><code class="language-plsql,line-numbers">create or replace package body alien_data_management is

procedure add_department(
  p_name varchar2,
  p_location varchar2,
  p_country varchar2  
) is
  -- parameters collection variable
  l_parameters apex_exec.t_parameters;
begin
  -- prepare the parameters for the Web Service operation
  apex_exec.add_parameter(p_parameters =&gt; l_parameters, p_name =&gt; &apos;NAME&apos;,     p_value =&gt; p_name);
  apex_exec.add_parameter(p_parameters =&gt; l_parameters, p_name =&gt; &apos;LOCATION&apos;, p_value =&gt; p_location);
  apex_exec.add_parameter(p_parameters =&gt; l_parameters, p_name =&gt; &apos;COUNTRY&apos;,  p_value =&gt; p_country);

  -- invoke POST operation, defined in the Web Service definition
  begin
    apex_exec.execute_web_source(
        p_module_static_id =&gt; &apos;ALIEN_DEPARTMENTS&apos;,
        p_operation        =&gt; &apos;POST&apos;,
        p_parameters       =&gt; l_parameters
    );
  -- we are handling VALUE_ERROR exceptions because of 
  -- weird ORA-06502: PL/SQL: numeric or value error
  -- after a successful PostgREST API call
  -- more here: https://community.oracle.com/message/14988842
  exception when VALUE_ERROR then
    null;
  end;
end add_department;

procedure update_department(
  p_department_id integer,
  p_name varchar2,
  p_location varchar2,
  p_country varchar2  
) is
  -- parameters collection variable
  l_parameters apex_exec.t_parameters;
begin
  -- prepare the parameters for the Web Service operation
  apex_exec.add_parameter(p_parameters =&gt; l_parameters, p_name =&gt; &apos;department_id&apos;, p_value =&gt; &apos;eq.&apos;||to_char(p_department_id));
  apex_exec.add_parameter(p_parameters =&gt; l_parameters, p_name =&gt; &apos;NAME&apos;, p_value =&gt; p_name);
  apex_exec.add_parameter(p_parameters =&gt; l_parameters, p_name =&gt; &apos;LOCATION&apos;, p_value =&gt; p_location);
  apex_exec.add_parameter(p_parameters =&gt; l_parameters, p_name =&gt; &apos;COUNTRY&apos;, p_value =&gt; p_country);

  -- invoke POST operation, defined in the Web Service definition
  begin
    apex_exec.execute_web_source(
        p_module_static_id =&gt; &apos;ALIEN_DEPARTMENTS&apos;,
        p_operation        =&gt; &apos;PATCH&apos;,
        p_parameters       =&gt; l_parameters
    );
  -- we are handling VALUE_ERROR exceptions because of 
  -- weird ORA-06502: PL/SQL: numeric or value error
  -- after a successful PostgREST API call
  -- more here: https://community.oracle.com/message/14988842
  exception when VALUE_ERROR then
    null;
  end;
end update_department;

procedure delete_department(
  p_department_id integer
) is
  -- parameters collection variable
  l_parameters apex_exec.t_parameters;
begin
  -- prepare the parameters for the Web Service operation
  apex_exec.add_parameter(p_parameters =&gt; l_parameters, p_name =&gt; &apos;department_id&apos;, p_value =&gt; &apos;eq.&apos;||to_char(p_department_id));

  -- invoke POST operation, defined in the Web Service definition
  begin
    apex_exec.execute_web_source(
        p_module_static_id =&gt; &apos;ALIEN_DEPARTMENTS&apos;,
        p_operation        =&gt; &apos;DELETE&apos;,
        p_parameters       =&gt; l_parameters
    );
  -- we are handling VALUE_ERROR exceptions because of 
  -- weird ORA-06502: PL/SQL: numeric or value error
  -- after a successful PostgREST API call
  -- more here: https://community.oracle.com/message/14988842
  exception when VALUE_ERROR then
    null;
  end;
end delete_department;

end alien_data_management;
/
</code></pre>
<p>Ok, let&apos;s have a closer look at the package and its procedures.</p>
<p>The <code>add_department</code> procedure&apos;s purpose is obviously to insert a new department into the table in the alien database. As you can see, before invoking the web service <code>POST</code> operation, we prepare parameter values leveraging  special <code>apex_exec.add_parameter</code> API. This function has a set of overloads to accept values of different data types. Then, the only thing which is done in the procedure, is the <code>apex_exec.execute_web_service</code> call. Yes, it&apos;s that easy.</p>
<p>The next, <code>update_department</code> procedure is different from the previous one only in two aspects - it has an additional parameter <code>department_id</code> and the type of operation being performed here is <code>PATCH</code>. This tells PostgREST to transform our call into the <code>UPDATE</code> SQL statement instead of <code>INSERT</code>.</p>
<p>The <code>delete_department</code> procedure looks even simplier, because it has only one parameter - <code>department_id</code>. Also, the web service operation which is used here is <code>DELETE</code>, which reflects <code>DELETE</code> SQL statement in the remote database.</p>
<p>Right, now we are ready to start with our interactive report and a form on it.</p>
<h3 id="thereportandtheform">The report and the form</h3>
<p>Go back to <em>AppBuilder</em> and edit your home page. Add an <em>Interactive Report</em> region to the <em>Content Body</em> section. Give it a proper name and base it on the <em>Web Source</em> we created before. You can rename the columns of the report as you wish their names to be.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_11.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>Next we need to create a new <em>Modal Dialog</em> page which we are going to use to add a new row in our table. Create it and place a <em>Static Content</em> region on the page. Then add three <em>Text Fields</em> items (for the table attributes) and a <em>Save</em> button onto this region.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_12.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>And finally, the most interesting part. In order for data to be saved, we need to add a PL/SQL process, which will invoke our <code>add_department</code> procedure when a user presses the <em>Add department</em> button. To fulfil our task, we need to add this code as the process source:</p>
<pre><code class="language-plsql,line-numbers">alien_data_management.add_department(
  p_name =&gt; :P201_NAME,
  p_location =&gt; :P201_LOCATION,
  p_country =&gt; :P201_COUNTRY
);
</code></pre>
<p>Assuming we did all this, our page should look as follows.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_13.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>Ok, our <em>Add a new department page</em> is ready. Now it&apos;s time to make the same page for updating and deleting the existing rows. For this, you can copy the page we just created and rename it. Then, add a new Display Only item for <code>department_id</code> attribute, and instead of <em>Add department</em> there should be <em>Save Changes</em> and <em>Delete department</em> buttons.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_14.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>To go further, let&apos;s make two processes on this page - one for the <code>UPDATE</code> operation, and one for <code>DELETE</code>. Mind the server-side condition - a process should run only when a corresponding button is pressed.</p>
<p>For the <em>Save Changes</em> button process we need this code to be run:</p>
<pre><code class="language-plsql,line-numbers">alien_data_management.update_department(
  p_department_id =&gt; :P200_DEPARTMENT_ID,
  p_name =&gt; :P200_NAME,
  p_location =&gt; :P200_LOCATION,
  p_country =&gt; :P200_COUNTRY
);
</code></pre>
<p>When for the <em>Delete</em> button process it should be:</p>
<pre><code class="language-plsql,line-numbers">alien_data_management.delete_department(
  p_department_id =&gt; :P200_DEPARTMENT_ID
);
</code></pre>
<p>Save the page and check.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_15.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>Well, we are now ready to link the home page with our form pages. For this, go back to the <em>Home Page</em> in <em>AppBuilder</em> and add a button to open the <em>Add Department</em> page. As a target, choose redirection to the corresponding page.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_16.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>To link the <em>Home Page</em> with the <em>Update Department</em> page, let&apos;s add a link attribute for our report.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_17.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>Then, we should specify the needed page number to redirect and don&apos;t forget to pass the current column values as field values.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_18.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<h3 id="testing">Testing</h3>
<p>If you did everything right, you are now ready to test the application. And the first thing you&apos;re going to see when you run the app, will be our interactive report based on data from alien PostgreSQL database.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_test_1.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>Let&apos;s compare with what we have in the database:</p>
<pre><code class="language-sql"># psql -U restapi
Password for user restapi:
psql (10.5)
Type &quot;help&quot; for help.

restapi=&gt; select * from departments;
 department_id |            name             |  location   |    country
---------------+-----------------------------+-------------+---------------
             1 | Product Support             | Tanquecitos | United States
             2 | Electronic Data Interchange | Sugarloaf   | United States
             4 | Transportation              | Grosvenor   | United States
             5 | Finance                     | Riverside   | United States
             3 | Legal                       | Dale City   | United States
(5 rows)
</code></pre>
<p>Ok, looks familiar, but let&apos;s add a row.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_test_2.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>After clicking on the <em>Add department</em> button, we see our new row in the report.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_test_3.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>But maybe I tricked you and this row wasn&apos;t actually sent to the foreign database? Let&apos;s check it:</p>
<pre><code class="language-sql">restapi=&gt; select * from departments;
 department_id |            name             |  location   |    country
---------------+-----------------------------+-------------+---------------
             1 | Product Support             | Tanquecitos | United States
             2 | Electronic Data Interchange | Sugarloaf   | United States
             4 | Transportation              | Grosvenor   | United States
             5 | Finance                     | Riverside   | United States
             3 | Legal                       | Dale City   | United States
            38 | IT                          | Omsk        | Russia
(6 rows)
</code></pre>
<p>Seems I didn&apos;t trick you and our row is there! As a homework, test the <code>UPDATE</code> and <code>DELETE</code> operations on your own.</p>
<p>It could be harsh, but I tried to show you that it&apos;s not hard to make an application, capable to consume data from a foreign database and write it back there, even now, and this can be done in an <em>almost</em> declarative way.</p>
<h2 id="programmaticorapex_web_serviceway">Programmatic or APEX_WEB_SERVICE way</h2>
<p>In this section I am going to tell you about an alternative, lower-level approach to achieve the same results. This time we are going to organize same interface on top of the <code>employees</code> table.</p>
<p>This approach doesn&apos;t require any <em>Web Source Module</em> defined under <em>Shared Components</em> for the application. Instead of this, we are going to perform these steps:</p>
<ol>
<li>Implement CRUD functionality in our PL/SQL package by leveraging of <code>APEX_WEB_SERVICE</code> API.</li>
<li>Create another <em>Interactive Report</em> and base it on a local PL/SQL pipelined function.</li>
<li>Implement writing capabilities of the report using APEX processes invoking the other PL/SQL package procedures.</li>
</ol>
<p>I also should warn you that in this chapter I am not going to describe things in as detailed way as I did it before. So, my aim here is to highlight main differences in the approaches.</p>
<p>Additionally, I assume that we will be using the same package we created in the previous chapter. Ok, let&apos;s get it started.</p>
<h3 id="thecode">The code</h3>
<p>So, we are going to base our <em>Interactive Report</em> on top of a pipelined function. One of the easiest ways to implement this function is to invoke a <code>GET</code> request by leveraging of <code>APEX_WEB_SERVICE.MAKE_REST_REQUEST</code> API, get the data in JSON format and then transform it to a relational view by using of the <code>JSON_TABLE</code> function, which is there for us since 12c.</p>
<p>But first we need to define a set of data type for the function result set and the function itself in the package specification:</p>
<pre><code class="language-plsql,line-numbers">type t_employee is record (
  employee_id integer,
  department_id integer,
  name varchar2(50),
  email varchar2(255),
  cost_center integer,
  date_hired date,
  job varchar2(255)
);

type tbl_employees is table of t_employee;

function get_employees return tbl_employees pipelined;
</code></pre>
<p>Now, let&apos;s define its body:</p>
<pre><code class="language-plsql,line-numbers">function get_employees return tbl_employees pipelined
is
  E_NO_MORE_ROWS_NEEDED exception;
  pragma exception_init(E_NO_MORE_ROWS_NEEDED, -6548);
  l_response clob;
begin
  -- REST request to get JSON data from alien database
  l_response := apex_web_service.make_rest_request(
    p_url =&gt; &apos;http://192.168.88.31:8000/employees&apos;,
    p_http_method =&gt; &apos;GET&apos;
  );

  -- converting JSON data to relational view
  -- and returning it as result set
  for x in (
    select
      employee_id,
      department_id,
      name,
      email,
      cost_center,
      date_hired,
      job
   from json_table(
      l_response,
      &apos;$[*]&apos; columns (
        employee_id integer,
        department_id integer,
        name varchar2(50),
        email varchar2(255),
        cost_center integer,
        date_hired date,
        job varchar2(255)
      )
    )
  ) loop
    pipe row(
      t_employee(
        x.employee_id,
        x.department_id,
        x.name,
        x.email,
        x.cost_center,
        x.date_hired,
        x.job
      )
    );
  end loop;

  return;
-- APEX reacts inadequately on `NO MORE ROWS NEEDED` exception
-- and generates `404 Not Found` error if not suppress it
-- more here: https://community.oracle.com/thread/4076787
exception when E_NO_MORE_ROWS_NEEDED then
  null;
end get_employees;
</code></pre>
<p>As you can see, at the beginning the function calls the corresponding API endpoint (<code>http://192.168.88.31:8000/employees</code> in our case) using the <code>GET</code> HTTP method and writes the response to a variable. Then, using power of <code>JSON_TABLE</code> function, it translates JSON-formatted data into relational and returns it as a result.</p>
<p>Also keep in mind the fact that it is important to suppress the <code>NO MORE ROWS NEEDED</code> exception, which a pipelined function could generate if all data to return cannot be placed on one page of a report. Otherwise, APEX engine will generate a <code>404 Not Found</code> exception. This is a <a href="https://community.oracle.com/thread/4076787">known issue</a>.</p>
<p>Now we are going to define the writing procedures, first in the package specification:</p>
<pre><code class="language-plsql,line-numbers">procedure add_employee(
  p_department_id integer,
  p_name varchar2,
  p_email varchar2,
  p_cost_center integer,
  p_date_hired date,
  p_job varchar2
);

procedure update_employee(
  p_employee_id integer,
  p_department_id integer,
  p_name varchar2,
  p_email varchar2,
  p_cost_center integer,
  p_date_hired date,
  p_job varchar2
);

procedure delete_employee(
  p_employee_id integer
);
</code></pre>
<p>And then in the package body:</p>
<pre><code class="language-plsql,line-numbers">procedure add_employee(
  p_department_id integer,
  p_name varchar2,
  p_email varchar2,
  p_cost_center integer,
  p_date_hired date,
  p_job varchar2
) is
  l_json_body json_object_t := json_object_t();
  l_response clob;
begin
  -- setting HTTP headers before the REST API call
  apex_web_service.g_request_headers(1).name := &apos;Content-Type&apos;;
  apex_web_service.g_request_headers(1).value := &apos;application/json; charset=utf-8&apos;;

  -- preparation of the body
  -- put method of the json_object_t type accepts various data types
  l_json_body.put(key =&gt; &apos;department_id&apos;, val =&gt; p_department_id);
  l_json_body.put(key =&gt; &apos;name&apos;, val =&gt; p_name);
  l_json_body.put(key =&gt; &apos;email&apos;, val =&gt; p_email);
  l_json_body.put(key =&gt; &apos;cost_center&apos;, val =&gt; p_cost_center);
  l_json_body.put(key =&gt; &apos;date_hired&apos;, val =&gt; p_date_hired);
  l_json_body.put(key =&gt; &apos;job&apos;, val =&gt; p_job);

  -- REST request to alien database to perform insert operation
  l_response := apex_web_service.make_rest_request(
    p_url =&gt; &apos;http://192.168.88.31:8000/employees&apos;,
    p_http_method =&gt; &apos;POST&apos;,
    p_body =&gt; l_json_body.to_clob()
  );

end add_employee;

procedure update_employee(
  p_employee_id integer,
  p_department_id integer,
  p_name varchar2,
  p_email varchar2,
  p_cost_center integer,
  p_date_hired date,
  p_job varchar2
) is
  l_json_body json_object_t := json_object_t();
  l_url varchar2(200);
  l_response clob;
begin
  -- setting HTTP headers before the REST API call
  apex_web_service.g_request_headers(1).name := &apos;Content-Type&apos;;
  apex_web_service.g_request_headers(1).value := &apos;application/json; charset=utf-8&apos;;

  -- preparation of the body
  -- put method of the json_object_t type accepts various data types
  l_json_body.put(key =&gt; &apos;department_id&apos;, val =&gt; p_department_id);
  l_json_body.put(key =&gt; &apos;name&apos;, val =&gt; p_name);
  l_json_body.put(key =&gt; &apos;email&apos;, val =&gt; p_email);
  l_json_body.put(key =&gt; &apos;cost_center&apos;, val =&gt; p_cost_center);
  l_json_body.put(key =&gt; &apos;date_hired&apos;, val =&gt; p_date_hired);
  l_json_body.put(key =&gt; &apos;job&apos;, val =&gt; p_job);

  -- prepare the URL
  -- have not found a better way to specify query string parameters
  l_url := &apos;http://192.168.88.31:8000/employees?employee_id=eq.&apos;||to_char(p_employee_id);

  -- REST request to alien database to perform update operation
  l_response := apex_web_service.make_rest_request(
    p_url =&gt; l_url,
    p_http_method =&gt; &apos;PATCH&apos;,
    p_body =&gt; l_json_body.to_clob()
  );

end update_employee;

procedure delete_employee(
  p_employee_id integer
) is
  l_url varchar2(200);
  l_response clob;
begin
  -- setting HTTP headers before the REST API call
  apex_web_service.g_request_headers(1).name := &apos;Content-Type&apos;;
  apex_web_service.g_request_headers(1).value := &apos;application/json; charset=utf-8&apos;;

  -- prepare the URL
  -- have not found a better way to specify query string parameters
  l_url := &apos;http://192.168.88.31:8000/employees?employee_id=eq.&apos;||to_char(p_employee_id);

  -- REST request to alien database to perform update operation
  l_response := apex_web_service.make_rest_request(
    p_url =&gt; l_url,
    p_http_method =&gt; &apos;DELETE&apos;
  );

end delete_employee;
</code></pre>
<p>Look how these procedures are different from the ones we wrote in the previous chapter to manage departments.</p>
<p>Here we have to manually set the needed HTTP headers by populating of <code>apex_web_service.g_request_headers</code> collection, because we don&apos;t have prepared metadata for this <em>Web Source</em>. Then, we prepare a request body manually. Luckily, in 18c we have very powerful <code>json_object_t</code> data type to help us here - with it we don&apos;t have to worry about formatting and data types of the attributes to be put to a JSON string. And the last thing done by the writing procedures, is a REST API call of a corresponding HTTP method.</p>
<h3 id="thereportandtheform">The report and the form</h3>
<p>Again, it&apos;s time to go back to <em>AppBuilder</em> and edit your home page. Now add another <em>Interactive Report</em> region to the <em>Content Body</em> section. Give it a proper name to reflect management of the <code>employees</code> table this time. Then specify to base your report on SQL query, and use this query as a source:</p>
<pre><code class="language-sql">select 
    employee_id,
    department_id,
    name,
    email,
    cost_center,
    date_hired,
    job
from alien_data_management.get_employees();
</code></pre>
<p>Then you can rename the columns of the report following your preferences. As a result you should get something like this.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_step_19.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>The further steps are quite analogical to the ones we performed in the previous chapter. The only difference is in the set of attributes and names of procedures to use. So, I am going to leave them for you to perform them yourselves.</p>
<h3 id="testing">Testing</h3>
<p>In case you were brave enough to finish the task, you are ready to test the implemented functionality. Run the application and have a look at the report.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_test_4.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>So, we see some data. And that&apos;s what we have in the database:</p>
<pre><code class="language-sql">restapi=&gt; select * from employees order by name limit 10;
 employee_id | department_id |        name        |            email            | cost_center | date_hired |            job
-------------+---------------+--------------------+-----------------------------+-------------+------------+----------------------------
         124 |             1 | August Arouri      | august.arouri@aaam.com      |          54 | 2018-08-07 | Customer Advocate
         117 |             1 | August Rupel       | august.rupel@aaaf.com       |           1 | 2018-10-04 | Analyst
         126 |             1 | Ayana Barkhurst    | ayana.barkhurst@aaao.com    |          42 | 2018-08-26 | Sustaining Engineering
         122 |             1 | Carlotta Achenbach | carlotta.achenbach@aaak.com |          99 | 2018-10-22 | Marketing Manager
         120 |             1 | Chaya Greczkowski  | chaya.greczkowski@aaai.com  |          55 | 2018-09-05 | Support Specialist
         131 |             1 | Dania Grizzard     | dania.grizzard@aaat.com     |          53 | 2018-10-30 | Project Manager
         114 |             1 | Dean Bollich       | dean.bollich@aaac.com       |          83 | 2018-08-14 | Usability Engineer
         113 |             1 | Gricelda Luebbers  | gricelda.luebbers@aaab.com  |          82 | 2018-11-06 | Marketing Associate
         132 |             1 | Inez Yamnitz       | inez.yamnitz@aaau.com       |          75 | 2018-09-18 | HR Representitive
         123 |             1 | Jeraldine Audet    | jeraldine.audet@aaal.com    |          41 | 2018-08-18 | Quality Control Specialist
(10 rows)
</code></pre>
<p>Looks suspiciously similar, doesn&apos;t it? Now let&apos;s change an employee&apos;s data and click the <em>Save changes</em> button.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_test_5.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>The changed row is there in our report.</p>
<p><img src="https://dsavenko.me/content/images/2018/11/alf_app_test_6.png" alt="Read-Write APEX application fully based on alien data or is it mandatory to use exactly Oracle Database?" loading="lazy"></p>
<p>But what about the database side?</p>
<pre><code class="language-sql">restapi=&gt; select * from employees order by name limit 10;
 employee_id | department_id |        name        |            email            | cost_center | date_hired |            job
-------------+---------------+--------------------+-----------------------------+-------------+------------+----------------------------
         117 |             1 | August Rupel       | august.rupel@aaaf.com       |           1 | 2018-10-04 | Analyst
         124 |             1 | August WE DID IT!  | august.arouri@aaam.com      |          54 | 2018-08-07 | Customer Advocate
         126 |             1 | Ayana Barkhurst    | ayana.barkhurst@aaao.com    |          42 | 2018-08-26 | Sustaining Engineering
         122 |             1 | Carlotta Achenbach | carlotta.achenbach@aaak.com |          99 | 2018-10-22 | Marketing Manager
         120 |             1 | Chaya Greczkowski  | chaya.greczkowski@aaai.com  |          55 | 2018-09-05 | Support Specialist
         131 |             1 | Dania Grizzard     | dania.grizzard@aaat.com     |          53 | 2018-10-30 | Project Manager
         114 |             1 | Dean Bollich       | dean.bollich@aaac.com       |          83 | 2018-08-14 | Usability Engineer
         113 |             1 | Gricelda Luebbers  | gricelda.luebbers@aaab.com  |          82 | 2018-11-06 | Marketing Associate
         132 |             1 | Inez Yamnitz       | inez.yamnitz@aaau.com       |          75 | 2018-09-18 | HR Representitive
         123 |             1 | Jeraldine Audet    | jeraldine.audet@aaal.com    |          41 | 2018-08-18 | Quality Control Specialist
(10 rows)
</code></pre>
<p>So this is real! And it&apos;s done for the second time, using a different approach.</p>
<p>In fact, we could implement an even more programmatic approach using the <code>UTL_HTTP</code> package, but in this case we would need to write much more code to achieve the same results. Although, in that case we would have even more control of what is done by our procedures.</p>
<h2 id="downloads">Downloads</h2>
<p>Ok guys, I have good news for you - for those who had some issues with creating of the application on their own, I uploaded the source code of the stuff we were developing here to GitHub. You are free to download and use it in any way you like (don&apos;t forget to change the IPs).</p>
<p>The uploaded project consists of:</p>
<ul>
<li>PostgreSQL database schema and sample data script</li>
<li><code>ALIEN_DATA_MANAGEMENT</code> PL/SQL package</li>
<li>APEX application export script.</li>
</ul>
<p>You can find it here: <a href="https://github.com/ZZa/apex-on-postgresql-sample">https://github.com/ZZa/apex-on-postgresql-sample</a></p>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>By this very long article I tried to answer the question if it is mandatory to use exactly <em>Oracle Database</em> to store APEX application data. And as you can see, the answer is NO, you are welcome to use any database and DBMS of your preference to base your APEX application on, and you can do it right now, though not in a fully declarative way. In fact, the choice of RDBMS isn&apos;t limited by PostgreSQL or MySQL - it literally could be anything with a proper REST API on top of it.</p>
<p>This means you can go for a free <em>Express Edition</em> of Oracle Database to integrate APEX with your companies existing projects. It won&apos;t work as fast and smooth as it could if you based your application on a <em>local</em> Oracle Database, but it still has loads of use-cases.</p>
<p>Of course, the code, I demonstrated here, could be improved in many aspects - we could add proper error handling, modularize it better or make it more universal. But my aim here was to mostly show WHAT YOU CAN DO, not write an ideal application. I also should warn you about the fact that I used very simple examples in my post, and if you need much more complex queries or transactions, it would be a good idea to consider using REST API on views of stored procedures, defined in the remote database.</p>
<p>Do not hesitate to comment on the topic if you have something to add or a question appeared in your head - together we will try to sort it out. You are also welcome with you suggestions on how to improve the article.</p>
<p>Drop me a message any time if you feel I could be useful for you, and we give your problem a closer look.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Reblog: Oracle Database + APEX + JavaScript/Python = Awesome!]]></title><description><![CDATA[Check out a new fascinating MLE feature in APEX and Oracle Database. It will soon allow us have server-side database and APEX code written in JavaScript and Python.]]></description><link>https://dsavenko.me/oracle-database-apex-javascript-python-awesome/</link><guid isPermaLink="false">5bdc0779923c0b070052a57b</guid><category><![CDATA[apex]]></category><category><![CDATA[oracle]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Fri, 02 Nov 2018 08:45:23 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/11/tsss.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/11/tsss.jpg" alt="Reblog: Oracle Database + APEX + JavaScript/Python = Awesome!"><p>How cool it could be if you were able to write your server-side code in database and APEX applications using any language of your preference? What about a mixture of them? There&apos;s a new feature, called <strong>MLE</strong> or <strong>Multilingual Engine</strong> which is going to allow you do this all! But don&apos;t tell anybody! It&apos;s a secret!</p>
<p>The <em>MLE</em> feature is based on the <em>GraalVM</em> project and is planned to be part of <em>Oracle Database</em> in some future. However, it&apos;s already possible to estimate how it works in <em>APEX</em> with <em>JavaScript</em> and <em>Python</em> right now (other languages and more complete support are still under development). To do so, check out the most recent announcement by <em><a href="https://twitter.com/joelkallman">Joel Kallman</a></em> with loads of practical examples. Enjoy the reading and don&apos;t forget to try it yourself!</p>
<h2 id="httpsblogsoraclecomapexoracledatabase2bapex2bjavascriptpython3dawesome"><a href="https://blogs.oracle.com/apex/oracle-database-%2b-apex-%2b-javascriptpython-%3d-awesome">https://blogs.oracle.com/apex/oracle-database-%2B-apex-%2B-javascriptpython-%3D-awesome</a></h2>
<hr>
<p>More languages always mean more possibilities and wider community. This feature will enable us to see <em>JavaScript</em>, <em>Python</em> and <em>Java</em> developers joining our database and APEX pride in some time.</p>
<p>And the more people involved the more job is done and the better future we have for our favourite technology!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Oracle APEX and ORDS deployments automation]]></title><description><![CDATA[Do you still do deployments manually? How long does it take for you to deliver changes in your APEX apps and ORDS Data Services to all environments? In the article automation of APEX and ORDS delpoyments finally explained!]]></description><link>https://dsavenko.me/apex-and-ords-deployments-automation/</link><guid isPermaLink="false">5b27fdb7eab6d906283cf45d</guid><category><![CDATA[apex]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Mon, 29 Oct 2018 19:22:36 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/06/pocket_stopwatch.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/06/pocket_stopwatch.jpg" alt="Oracle APEX and ORDS deployments automation"><p>How much time does it take for your team to deliver your freshly updated applications to all the environments and customers? In case you just play with APEX and ORDS, it is totally ok for you to export and import your workspaces, applications and RESTful Services definitions <em>manually</em> using web-based interface (under <em>App Builder</em> &gt; <em>Export/Import</em> and <em>SQL Workshop</em> &gt; <em>RESTful Services</em> &gt; <em>RESTful Data Services</em>). But it is obviously not a suitable way for more or less serious development, and, surely, for the enterprise use.</p>
<h2 id="whyso">Why so?</h2>
<p>Let&apos;s imagine you have a team of several developers who do different job and once a couple of days you need to deliver their changes to all the environments. If your team develops a single application in a single workspace, which is supposed to be deployed to a <strong>single test</strong> environment and a <strong>single production</strong> server, you still can do all this <em>manually</em>. It&apos;s just not the time for you. Yet.</p>
<p>But imagine you have several testing platforms, and dozens or even hundreds of customers. In such a case it becomes more or less <em>impossible</em> to deliver all the changes as frequently as you could do, providing you had the only customer. However, many people continue exploiting the <em>manual</em> approach, and don&apos;t even realise they&apos;re doing it wrong.</p>
<p>That&apos;s why we need automation - to stop <a href="https://oracle-base.com/blog/2018/06/12/why-automation-matters-lost-time/">losing our time</a>. More than two years ago <a href="https://twitter.com/martindsouza"><em>Martin D&apos;Souza</em></a> <a href="https://insum.ca/oracle-apex-deployments-youre-doing-it-wrong/">already wrote</a> about how APEX applications could be exported and imported using <em>SQLcl</em>. That article was a good start for me, though, I didn&apos;t want to be dependant on SQLcl or any other specific tool, and needed to do even more - deploy not only <em>APEX applications</em>, but also <em>APEX workspaces</em> and <em>RESTful Data Services modules definitions</em>.</p>
<p>So, my ultimate goal was to work out a way to create an applicable set of SQL-scripts with the definitions of all the mentioned database objects from the database metadata.</p>
<p>Additionally, this set of scripts had to be <em>universal</em>, which means they had to be compatible with the <em>SQL*Plus</em>, <em>SQLcl</em> and <em>SQL Developer</em>.</p>
<p>And this is what I finally ended up with.</p>
<h2 id="theexport">The Export</h2>
<h3 id="apexworkspace">APEX Workspace</h3>
<p>The first thing you deploy in order for your APEX applications to work, is your workspaces. Actually, if you have the only server in production environment, automation of this step is quite <em>optional</em> and you can still do the job using the stanard web-based interface (<em>Instance Administration &gt; Manage Workspaces &gt; Export Import &gt; Export Workspace</em>). But if you&apos;re going to install your applications on some number of hosts with their own database instances, it&apos;s a very good idea not to do everything by your bare hands.</p>
<p>To export workspace definitions we are going to use <a href="https://docs.oracle.com/database/apex-18.2/AEAPI/GET_WORKSPACE_Function.htm#AEAPI-GUID-493DB29D-8740-418E-9AF9-419EDE564CC5"><code>APEX_EXPORT.GET_WORKSPACE</code></a> API. Mind the fact that you need to run the script below as a parsing schema user (which is associated with the workspace), or as <code>SYS</code>, otherwise you&apos;re going to get the <code>ORA-20987: APEX - Security Group ID (your workspace identity) is invalid. - Contact your application administrator</code> exception.</p>
<pre><code class="language-sql,line-numbers">-- connecting to the instance (parsing schema user could be used instead of SYS)
connect sys as sysdba

set timing on
timing start TIMER_WORKSPACE_EXPORT

-- SQL*Plus environment settings
-- they are crucial to retrieve a consistent export file
set feedback off
set heading off
set echo off
set flush off
set termout off
set pagesize 0
set long 100000000 longchunksize 32767
column output format a4000
set linesize 4000
set trimspool on

-- variable for storing the export data
variable contents clob

-- generating the export file
declare
    l_files apex_t_export_files;
begin
    l_files := apex_export.get_workspace(
        p_workspace_id =&gt; 100500,
        p_with_date =&gt; false,
        p_with_team_development =&gt; false,
        p_with_misc =&gt; true
    );
    -- in the first file we have the needed export data
    :contents := l_files(1).contents;
end;
/

-- you can specify any file name here
spool my_workspace_export.sql

print contents

spool off

timing stop TIMER_WORKSPACE_EXPORT

exit
</code></pre>
<p>Notice that the size of the export data can be large (though this is mostly true for application exports), that&apos;s why we need to set proper SQL*Plus environment settings.</p>
<h3 id="apexapplication">APEX Application</h3>
<p>Exporting of your APEX applications looks very similar to what you&apos;ve just seen above. But there&apos;s a huge difference in the frequency of its usage - you need to export an application every time you need to get a new version of it in order to deliver the changes to your customers. Even if there&apos;s only one.</p>
<p>As before, to export application we are going to use APEX&apos;s PL/SQL API, but this time it&apos;s going to be <a href="https://docs.oracle.com/database/apex-18.2/AEAPI/GET_APPLICATION_Function.htm#AEAPI-GUID-A8E626D6-D7DE-4E59-8F90-3666A7A41A87"><code>APEX_EXPORT.GET_APPLICATION</code></a>. Again, you must connect as a parsing schema user (which is associated with the workspace with the application to export), or as <code>SYS</code> to run the script.</p>
<pre><code class="language-sql,line-numbers">-- connecting to the instance (parsing schema user could be used instead of SYS)
connect sys as sysdba

set timing on
timing start TIMER_APP_EXPORT

-- SQL*Plus environment settings
-- they are crucial to retrieve a consistent export file
set feedback off
set heading off
set echo off
set flush off
set termout off
set pagesize 0
set long 100000000 longchunksize 32767
column output format a4000
set linesize 4000
set trimspool on

-- variable for storing the export data
variable contents clob

-- generating the export file
declare
    l_files apex_t_export_files;
begin
    l_files := apex_export.get_application(
        p_application_id            =&gt; 150,
        p_with_ir_public_reports    =&gt; true,
        p_with_ir_private_reports   =&gt; true,
        p_with_ir_notifications     =&gt; true,
        p_with_translations         =&gt; true,
        p_with_comments             =&gt; true,
        p_with_acl_assignments      =&gt; true
    );
    :contents := l_files(1).contents;
end;
/

-- you can specify any file name here
spool my_app_export.sql

print contents

spool off

timing stop TIMER_APP_EXPORT

exit
</code></pre>
<p>As a result you will get a huge SQL-script which contains metadata of all the application pages and even static application files. You also have an option to generate a separate file for each application page if you specify the <code>p_split</code> parameter value as <code>true</code>.</p>
<h3 id="ordsmodules">ORDS Modules</h3>
<p>In this chapter we are going to consider only the ORDS-based RESTful Services, which is quite a standard at the moment.</p>
<p>To export ORDS modules you, as usual, can use the comfortable APEX&apos;s browser interface (under <em>SQL Workshop &gt; RESTful Services &gt; RESTful Data Services &gt; Export</em>), but again it doesn&apos;t hurt much to automate the process, at least if your RESTful services tend to evolve through time. And once more time, there&apos;s an existing <a href="https://oracle-base.com/articles/misc/oracle-rest-data-services-ords-use-sqlcl-to-display-ords-web-service-definitions">blog post</a> by <a href="https://twitter.com/oraclebase">Tim Hall</a> on how to do all this using <em>SQLcl</em>. And still, I didn&apos;t want to be dependant on it, hence needed a different approach.</p>
<p>So, we&apos;re going to use the <code>ORDS_EXPORT</code> PL/SQL package in the <code>ORDS_METADATA</code> schema, which is, unfortunately, still not documented properly. Actually, under the hood exactly this package is used by the APEX SQL Workshop&apos;s pages.</p>
<p>To get a correct export file, you need to run the script below as a REST-enabled schema user.</p>
<pre><code class="language-sql,line-numbers">-- connecting to the instance (you must use the REST-enabled schema user - RESTAPI in this case)
connect restapi

set timing on
timing start TIMER_REST_EXPORT

-- SQL*Plus environment settings
-- they are crucial to retrieve a consistent export file
set feedback off
set heading off
set echo off
set flush off
set termout off
set pagesize 0
set long 100000000 longchunksize 32767
column output format a4000
set linesize 4000
set trimspool on

-- variable for storing the export data
variable contents clob

-- generating the export file
begin
    :contents := ords_metadata.ords_export.export_schema();
end;
/

-- you can specify any file name here
spool my_rest_modules_export.sql

print contents
-- the trailing &apos;/&apos; symbol is needed to terminate the generated PL/SQL block
prompt /

spool off

timing stop TIMER_REST_EXPORT

exit
</code></pre>
<p>After running the script, you end up with exactly the same SQL-script as though you used the SQL Workshop&apos;s interface.</p>
<p>It&apos;s also possible to export a particular module, if you like, for this exact purpose there&apos;s another function <code>export_module(p_module_name =&gt; &apos;MY_MODULE&apos;)</code> in the <code>ORDS_EXPORT</code> package. Check out the package specification for more info on this.</p>
<h2 id="theimport">The Import</h2>
<p>Generally, the import process is quite simple if you did everything right, since all the exported files are usual SQL-scripts which can be executed by your favourite tool. The only difference here is the user as whom you should run the scripts.</p>
<h3 id="apexworkspace">APEX Workspace</h3>
<p>To import a workspace, you must be connected to the database as APEX internal user (for instance, <code>APEX_180100</code>) or as <code>SYS</code> (but in this case you should change your current schema to the APEX internal user&apos;s by invoking, in our case, <code>alter session set current_schema = APEX_1801000;</code> statement).</p>
<p>Note the fact that if you go for the APEX internal user, it is usually in the <em>locked</em> state, so you first need to unlock it and set a password for the user. After doing the job (the import), the APEX internal user must be locked back, because leaving it unlocked is <a href="https://dsavenko.me/how-apex-executes-sql-statements-without-having-permissions/">way dangerous</a>.</p>
<p>You also should be in the directory where the script resides, since we&apos;re going to load it directly.</p>
<pre><code class="language-sql,line-numbers">-- connecting to the instance as SYS
connect sys as sysdba

-- changing the current schema
alter session set current_schema = APEX_180100;

set echo on
set timing on
set serveroutput on
set linesize 120

whenever sqlerror continue none

timing start TIMER_WORKSPACE_IMPORT

spool workspace_import.log

@my_workspace_export.sql

spool off

set serveroutput off

purge recyclebin;

timing stop TIMER_WORKSPACE_IMPORT

exit
</code></pre>
<h3 id="apexapplication">APEX Application</h3>
<p>Things could be a little different with the APEX applications import, due to the fact it has its own API to configure the process. It&apos;s done by using the <code>APEX_APPLICATION_INSTALL</code> package. Though, its usage is totally optional and is needed only if you intend to change something about the application before the actual import. There&apos;s even a <a href="https://docs.oracle.com/database/apex-18.2/AEAPI/Import-Script-Examples.htm#AEAPI560">set of examples</a> on how to use the package in the official documentation.</p>
<p>Here I&apos;m going to show the trivial situation when you&apos;re okay with the defaults (no need to change of application ID, offset, workspace, alias or any other application property). The script below should be run as a parsing schema user.</p>
<pre><code class="language-sql,line-numbers">-- connecting to the instance as a parsing schema user APEX_APP
connect apex_app

set echo on
set timing on
set serveroutput on
set linesize 120

whenever sqlerror continue none

timing start TIMER_APP_IMPORT

spool app_import.log

@my_app_export.sql

spool off

set serveroutput off

purge recyclebin;

timing stop TIMER_APP_IMPORT

exit
</code></pre>
<h3 id="ordsmodules">ORDS Modules</h3>
<p>To import your recently exported RESTful Data Services definitions you should switch to the REST-enabled schema user and change the name of the script.</p>
<pre><code class="language-sql,line-numbers">-- connecting to the instance as a REST-enabled schema user RESTAPI
connect restapi

set echo on
set timing on
set serveroutput on
set linesize 120

whenever sqlerror continue none

timing start TIMER_REST_IMPORT

spool rest_services_import.log

@my_rest_modules_export.sql

spool off

set serveroutput off

purge recyclebin;

timing stop TIMER_REST_IMPORT

exit
</code></pre>
<p>That&apos;s it!</p>
<p>Obviously, the mentioned scripts could be put together when you work with your particular situation and when you know what you&apos;re doing. The only point here is that separate scripts give you more freedom when it comes to automation. You can set up your own pipelines to run particular scripts when a particular event occurs.</p>
<h2 id="butwhereistheautomation">But where is the automation?</h2>
<p>Right. We only wrote a bunch of scripts and nothing more. But as I like to say, <em>automation starts where interactivity stops</em>. All web-based user interfaces are interactive and need a human to function, but the set of scripts, which we worked out, could be used in both interactive and non-interactive ways. Which means, you can definitely use them with your favourite CI/CD tool.</p>
<p>It could cost you some time and effort in the beginning to set up all this, but will save much more in the future. And stopping wasting time on the routine on every day basis is not the only reason. Automation of deployments enables you to implement such things as automated testing, continious integration and delivery, which, in their turn, decrease number of bugs in your code and shortens the time your customers see the brand-new funtionality.</p>
<p>I think it&apos;s enough to try, isn&apos;t it?</p>
<hr>
<p>Do you have any other ideas how to automate development process with APEX and ORDS worth sharing? You&apos;re warmly welcome to put it here in the comments section - we&apos;ll definitely discuss them all!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[ODC Appreciation Day: Significance of Sharing and Feedback]]></title><description><![CDATA[Today is the Oracle Developer Community Appreciation Day. Let's pause for a while and value all the effort which people apply to contribute to the web. And while being thankful, let's discuss the importance of sharing of obtained knowledge and significance of any kind of feedback.]]></description><link>https://dsavenko.me/odc-appreciacion-day-significance-of-sharing-and-feedback/</link><guid isPermaLink="false">5bb3bc8c923c0b070052a557</guid><category><![CDATA[off-topic]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Thu, 11 Oct 2018 00:00:00 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/10/thankful.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/10/thankful.jpg" alt="ODC Appreciation Day: Significance of Sharing and Feedback"><p>Today is the <a href="https://twitter.com/search?q=%23ThanksODC"><strong>Oracle Developer Community Appreciation Day</strong></a> of the year 2018. And despite the fact it&apos;s more than a month to the official <em>Thanksgiving Day</em>, I think it&apos;s a good opportunity to say thanks once again to all those people who helped us on everyday basis by contributing to the community this whole year. To people who wrote great articles in their blogs and shared their personal experience with us, to those who tried to sort out <em>our</em> troubles on forums and in chats, and of course to those who supported and motivated us by liking and spreading our posts and ideas in Twitter. And special thanks to the awesome <strong>Oracle APEX community</strong> which accepted me and led me to the right direction so many times this year! I hope this would always stay the same and all these people carry on making our world a bit better place to live!</p>
<p>And while being thankful to others, let&apos;s remember if you spread your fresh knowledge to people around you.</p>
<h2 id="significanceofsharing">Significance of Sharing</h2>
<p>Indeed, it happened that IT community is used to all that stuff available on the net and take it for granted. What does one do if they got into trouble with some piece of technology nowadays? They google it. They seek for a prepared answer in advance on their question. And in most cases they find and then use it. But what happens if they do not? Well, some &apos;experts&apos; stop here and give up, but the better half tries to sort out the problem themselves. And in most cases they succeed - that&apos;s what we are paid for, after all, and then our ways part.</p>
<p>Unfortunately, after working out a solution for a specific issue, most developers never share their experience with anybody. Even with their immediate colleagues, not saying about global community. They forget that a little while ago they themselves were seeking for a piece of advice on the matter, they forget how useful their solution could be for others, who stuck with the same problem.</p>
<p>That&apos;s, of course, not true about everybody - we are lucky enough to have people with different thinking on our side. But here&apos;s my <strong>first point</strong> - it&apos;s very important to share your experience with others, especially when you already confirmed the fact that there&apos;s no information available regarding your issue. Don&apos;t think your issue is that exclusive and isn&apos;t worth discussing - this is not true in most cases.</p>
<p>Furthermore, by sharing your experience you do better not only for others, but for yourself in the first place. Indeed, if you look deeper at this, you realise that by composing a proper blog post on some particular topic you:</p>
<ol>
<li><em>Consolidate your fresh knowledge.</em> By writing a post you put things in order in your own brain which helps you remember things better. Don&apos;t be scared by the fact you may seem incompetent - we all learn and by learning we become better. If one stops learning it doesn&apos;t mean they know everything, it only means they stop developing.</li>
<li><em>Save your own notes for future you.</em> It&apos;ll be you who will refer to them in a some while in the first place. Do you really think you&apos;ll never come across this issue again in your whole life? Will you remember what you did before to sort it out?</li>
<li><em>Show others you know how things work and who you are.</em> And this stands out you from other <em>invisible</em> developers, about whom we know absolutely nothing. Remember - your posts describe you better than any kind of CVs.</li>
</ol>
<p>I really hope these reasons would motivate at least someone to compose their first post in their brand-new blog. In this case I&apos;d say my mission was accomplished. At least they were the reasons which did the job for me.</p>
<p>I agree that it is time and energy consuming to write a good enough post, but the more you write the easier it is for you. And maybe we&apos;ll see more talented specialists sharing their knowledge on the web in some future.</p>
<h2 id="significanceoffeedback">Significance of Feedback</h2>
<p>My <strong>second point</strong> is that even if you don&apos;t write your own posts, don&apos;t underestimate the power of feedback on those you use in your work. Your words of gratitude surely motivate authors to carry on, you criticism helps authors polish their articles to your requests. Your questions give writers ideas about such things as:</p>
<ol>
<li>How to improve a post, what information to add into it, what aspects of the subject to consider in more details.</li>
<li>What interest their readers and about what to write in the future.</li>
<li>What to change in their style and how to improve the language.</li>
</ol>
<p>You even can&apos;t imagine how hard it could be for authors to realise some things which seem obvious for their readers. Sometimes it&apos;s on the surface, but is not seen for the blogger from their perspective of view, because of different life experience.</p>
<h2 id="significanceofallkindsoffeedback">Significance of All Kinds of Feedback</h2>
<p>Let me start with some of my recent observations about difference in russian and european mentality. It happened that I wrote exactly the same post in two languages - in <a href="https://habr.com/post/352400/">russian</a> for the local community on a very popular collaborative blogging platform and in <a href="https://dsavenko.me/migrating-centos-system-from-hdd-to-smaller-ssd-on-xfs-filesystem/">english</a> for the rest of the world here (and then tweeted about it). And I realized that behaviour of different communities was like they were from different worlds.</p>
<p>The russian version of the article got loads of immediate feedback like <em>Why so complicated? It would be much easier if instead of A you&apos;d use B..</em> or <em>So many movements, it wasn&apos;t needed to do X at all..</em> and the type. And all this was said after my disclaimer that I wasn&apos;t a guru in that particular area, I was just sharing my recent experience of working with the stuff I came across for the first time in my life and people were warmly welcome to offer their, better solutions in comments. Instead of this, they only criticised without actually offering anything valuable instead, like <em>You solution sucks</em> and that&apos;s it. This, of course, was not true about everybody and my post got its up-votes, but what really surprised me was the fact how easily people left negative feedback and how hard it was for them even to say thanks for the job done. I mean, where&apos;s golden mean?</p>
<p>The situation with the english version was orthogonal - I got a lot of likes and comments like <em>Great job, dude, carry on</em>, but there wasn&apos;t a single suggestion on how to improve the guide. I found this fact very weird - like my guide was ideal, though I knew it was not, and it was really easy for people to praise me, whereas they didn&apos;t tend to seek for any negative aspects of the post.</p>
<p>Furthermore, this difference in peoples&apos; mentality seems to affect them even more when they are given unexpected type of feedback. I mean, when somebody praises a post in a russian blog, author doesn&apos;t know how to react and often think they&apos;re just made fun of. People tend to forget what sincere gratitude look like. In the english-speaking part of the world, on the contrary, I noticed that if one gets reasonable criticism on their job, they also answer inadequately, or don&apos;t answer at all. It&apos;s as though they were insulted rather offered some really valuable information about how they could improve their post, or product.</p>
<p>But what frustrates me even more here is the fact how easily english-speaking community spread the word about something positive, and how lasily they do so when something unpleasant happens. I have a really good example about exploiting undocumented features of some product - people tend to praise so happily those who find such features (and they are surely useful sometimes), but if you point out the fact of lacking of documentation on them or that they shouldn&apos;t be there in the first place, this is likely to be ignored totally. Even despite the fact it is the right thing to say - this kind of feedback helps vendors find gaps in their products and docs, which, to my mind, should be spread by community very eagerly, if they want the vendor to pay attention on the problem.</p>
<p>Maybe I don&apos;t know how things work, and behind the curtains people take all kinds of feedback very seriously (please, leave your thoughts about this in the comments below), however it&apos;s not clear for us, the end-users, customers or readers, for whom products and content exist. We don&apos;t see any reaction on our feedback, and we don&apos;t know if we are heard. Yes, it could be unpleasant to accept you made a mistake, but it&apos;s natural to make mistakes. Bother to appreciate the fact that community pointed out them and helped you find them.</p>
<p>This was a long preamble to my <strong>third point</strong> which I wanted to denote in this article - the fact that all kinds of feedback are important. Positive and praising feedback gives us more energy and helps understand we&apos;re doing the right thing, whereas negative type of it helps us to improve our product and content.</p>
<p>I would only add that criticising in the sake of criticising is surely a bad thing which doesn&apos;t lead anywhere. If you bother to criticise, also bother to offer something in replace and motivate your point of view using strong argumentation.</p>
<hr>
<p>To finish this article, I&apos;d like my readers to remember one very simple fact - if each of us becomes today just a little bit better than they were the day before, we&apos;ll see our world shining very soon.</p>
<p><img src="https://dsavenko.me/content/images/2018/10/Keep-Calm-and-Carry-On.jpg" alt="ODC Appreciation Day: Significance of Sharing and Feedback" loading="lazy"></p>
<p>And I believe that it&apos;s there in our nature to want to be better, so, just be yourself, keep calm and carry on. And thank you very much for your attention, I hope this post would be useful for at least some of my followers! <a href="https://twitter.com/search?q=%23ThanksODC">#ThanksODC</a>!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[VirtualBox: thorny path from GUI to headless command-line interface]]></title><description><![CDATA[In this post I am telling my story about moving from Windows-hosted Oracle VirtualBox machines to the headless linux-hosted way]]></description><link>https://dsavenko.me/virtualbox-thorny-path-from-gui-to-headless-cli/</link><guid isPermaLink="false">5b6aab69034d1c0690016362</guid><category><![CDATA[off-topic]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Thu, 09 Aug 2018 18:33:27 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/08/virtualbox-from-gui-to-headless-cli.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/08/virtualbox-from-gui-to-headless-cli.png" alt="VirtualBox: thorny path from GUI to headless command-line interface"><p>This post, as I already <a href="https://twitter.com/whatsupbros/status/1027116359124230144">promised on Twitter</a>, is going to be a story behind bash scripts, which I recently shared on <a href="https://github.com/ZZa/vbox-cli-utils">GitHub</a>. Their purpose is to simplify management of headless <a href="https://www.virtualbox.org/"><strong>Oracle VirtualBox</strong></a> virtual machines on linux-based host systems. So, in case you would like to know how I ended up with them, carry on reading!</p>
<h2 id="lowstart">Low start</h2>
<p>It happened that I use virtual machines on my Windows desktop PC as development and testing environments for some of my projects. This is very convenient, because first - I sandbox my working environment from my personal stuff and have an ability to save states of the machines when needed. In example, when I&apos;m not sure with some process or feature, I like testing them, but I&apos;d rather create a snapshot before I begin - I find this function really useful. And this is very easy since VirtualBox has simple and clean graphical interface.</p>
<p>But, as you know, sometimes things change. My customer demanded to be able to look at and test the application, I develop for them, before I deploy it to production. And, this wasn&apos;t suitable for me to keep my laptop switched on for the whole day long to enable customer&apos;s employees to estimate the application. So, I decided to move my virtual machines to my <a href="https://dsavenko.me/fully-functional-linux-server-in-just-a-5-on-5-inch-box/">tiny home server</a>.</p>
<p>However, that wasn&apos;t that easy, since the operation system, which I have on the server, is <strong>CentOS Linux</strong> without any <em>X Window System</em> (only the command line with <code>bash</code> is available). And, I didn&apos;t want to lose in convenience.</p>
<h2 id="settingupvirtualbox">Setting up VirtualBox</h2>
<p>So, the first thing which I faced was installation of <strong>VirtualBox</strong> itself. There&apos;re several options to do this. In example, it&apos;s possible to download Linux packages for a vast majority of distributions right from <a href="https://www.virtualbox.org/wiki/Linux_Downloads">official website</a>. But, I&apos;d have needed to resolve all the dependencies manually in this case.</p>
<p>That&apos;s why I preferred the most recommended way on the web - to install it from official repository. Luckily, there&apos;re a <a href="https://www.if-not-true-then-false.com/2010/install-virtualbox-with-yum-on-fedora-centos-red-hat-rhel/">number of good instructions</a> on how to do it, which makes the installation process as easy as a pie.</p>
<p>And, ten minutes later, <strong>VirtualBox</strong> was succesfully installed on my Linux system. The issue was that I still didn&apos;t have graphical interface on my server and wasn&apos;t going to start using it, so, I started the research on alternatives to it.</p>
<h2 id="overviewofvirtualboxalternativefrontends">Overview of VirtualBox alternative front-ends</h2>
<p>In fact, there&apos;re a few additional options to the standard and well-known Qt-based <strong>VirtualBox</strong> interface as it is given in <a href="https://www.virtualbox.org/manual/ch01.html#frontends">the documentation</a>:</p>
<ul>
<li><em>VBoxSDL</em> is an alternative, simple graphical front-end with an intentionally limited feature set, designed to only display virtual machines. It&apos;s here just for mention since this option is another type of <em>graphical</em> interface, which wasn&apos;t suitable for me.</li>
<li><em>VBoxManage</em> is a command-line interface tool for automated and very detailed control of every aspect of <strong>VirtualBox</strong>. This sounded exactly like something I looked for.</li>
<li>Finally, <em>VBoxHeadless</em> is yet another front-end that produces no visible output on the host at all. As opposed to the other graphical interfaces, the headless front-end requires no graphics support.</li>
</ul>
<p>Actually, as I found out a bit later, on a server without any GUI, the one needs to combine leveraging of <code>vboxmanage</code> and <code>vboxheadless</code>. In such a case the first one is used to control the virtual machines, their settings and states, when the second is used to run headless instances of them.</p>
<h2 id="iwantiteasy">I want it easy</h2>
<p>I can&apos;t put this in words what I felt when saw <a href="https://www.virtualbox.org/manual/ch08.html"><code>vboxmanage</code> docs</a> for the first time. This command-line tool is very powerful and have a lot of options, hence not that easy for a newcomer. Especially when you are used to the convinience of the standard GUI.</p>
<p>So, what I wanted is a set of scripts which would be very easy to use and kind of repeat the GUI functionality for the features, mostly used by me. Basically, I needed scripts for starting, stopping, powering off specified virtual machines and for managing of machines snapshots, since I&apos;m an active user of them.</p>
<p>That&apos;s where I came up with an idea to write a set of scripts which would be able to take command line arguments on the one hand and user input on the other, when the argument is not provided. And you already know <a href="https://github.com/ZZa/vbox-cli-utils">what</a> I had as a result of my efforts.</p>
<p>Usage of the scripts is very easy - just give them the <em>execute</em> permission and hit the <code>enter</code> button on a desired script. Optionally, it&apos;s possible to specify a command line argument, in example, when you want to automate powering on of a particular virtual machine on system startup.</p>
<p>To put it simple, the main purpose of the scripts is to enable the user to control their headless virtual machines with a hit on a button instead of leveraging of the complex command-line utility <code>vboxmanage</code>, which is almost as convinient as using the GUI.</p>
<h2 id="movingthevms">Moving the VMs</h2>
<p>It wasn&apos;t hard for me to move the virtual machines, I even didn&apos;t have to export/import them. Everything which I did was copying virtual machines files from my laptop to the server. Then, using the prepared <code>register</code> script, I just registered them with the new environment and that&apos;s it.</p>
<p>Though, things weren&apos;t that easy, because my machines didn&apos;t start after moving. There were two reasons why. First - I use <em>bridged network</em> on my VMs, and they didn&apos;t see the bridging interface anymore (since it was from my desktop), second - 3d-acceleration feature was not supported on the headless environment and I needed to switch it off before running the VMs.</p>
<p>To fix this, I had to run these two commands for each VM:</p>
<pre><code>vboxmanage modifyvm &lt;VM_NAME&gt; --nic1 bridged --bridgeadapter1 &lt;INTERFACE_NAME&gt;
vboxmanage modifyvm &lt;VM_NAME&gt; --accelerate3d off
</code></pre>
<p>Where &lt;VM_NAME&gt; is the name of a VM and &lt;INTERFACE_NAME&gt; is the name of the bridging network interface (determined by <code>ifconfig</code>, for instance).</p>
<p>After doing so, I succesfully powered on all my virtual machines with my brand new <code>02_start.sh</code> script.</p>
<h2 id="whatdoidowiththevmwithoutinterface">What do I do with the VM without interface?</h2>
<p>I think some of you would ask me what to do with a headless VM? The answer is to <em>use it remotely</em>. Since I still prefer setting up my machines on my laptop, I always enable some way of remote control of the VM. If guest operation system is a unix-based one, it&apos;s usually enough for me to have the SSH daemon enabled. If it&apos;s a VM with Windows, then you can always set up <em>Remote Desktop</em>. Then I move them to the server for the further exploitation.</p>
<p>Alternatively, it&apos;s possible to intall <a href="https://download.virtualbox.org/virtualbox/5.2.16/Oracle_VM_VirtualBox_Extension_Pack-5.2.16.vbox-extpack">VirtualBox extention pack</a> and set up <a href="https://www.virtualbox.org/manual/ch07.html#vrde">VRPD</a>, which is kind of extention of standard remote desktop protocol. This is very useful if you want to create a VM on your server from scratch, though this is a totally different story.</p>
<h2 id="conclusion">Conclusion</h2>
<p>As it appeared, it&apos;s more than possible to manage VirtualBox machines using only command line. Maybe that&apos;s not as convenient as usage of GUI, but I hope <a href="https://github.com/ZZa/vbox-cli-utils">my scripts</a> will simplify the most common tasks not only for me, but for somebody else.</p>
<p>To make it short, you need to perform these steps to complete movement of your VMs from GUI to headless CLI:</p>
<ol>
<li>Set up and configure your VMs on your desktop system, in example on your laptop, using convenient GUI.</li>
<li>Install <strong>VirtualBox</strong> on your Linux server system.</li>
<li>Move all VMs files to the server.</li>
<li>Register your VMs using <code>vboxmanage</code> or my <code>01_register.sh</code> script.</li>
<li>(Optional) Fix the bridging network interface and 3D acceleration issues.</li>
<li>Manage you VMs using <a href="https://github.com/ZZa/vbox-cli-utils">my bunch of scripts</a> or <code>vboxmanage</code> and <code>vboxheadless</code> utilities directly.</li>
<li>Use your VMs remotely through SSH or RDP.</li>
<li>...</li>
<li>PROFIT!</li>
</ol>
<p>Do not hesitate to comment on them and on the approach in general. I&apos;m sure there&apos;re many other good options of how to do the same things. I didn&apos;t pay much attention to the <a href="https://www.virtualbox.org/manual/ch07.html#teleporting">teleporting</a> feature of <strong>VirtualBox</strong> environment, for example, though it might be quite suitable for a similar case to the one I was talking about in this article.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Migrating CentOS system from HDD to smaller SSD on XFS filesystem]]></title><description><![CDATA[A step-by-step guide to migrate a whole CentOS Linux system from an HDD to a much smaller SSD drive when you're using XFS]]></description><link>https://dsavenko.me/migrating-centos-system-from-hdd-to-smaller-ssd-on-xfs-filesystem/</link><guid isPermaLink="false">5ab4f9a30b38630670a14c5e</guid><category><![CDATA[off-topic]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Sat, 04 Aug 2018 18:00:00 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/03/cover.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/03/cover.png" alt="Migrating CentOS system from HDD to smaller SSD on XFS filesystem"><p>I think I am not alone who acquired an SSD drive and wanted to move the whole living Linux system to it. The problem is that it appeared to be not an easy task for me because of these reasons:</p>
<ul>
<li>The SSD drive is much smaller than the old HDD (SSD is expensive).</li>
<li>I use CentOS 7 which has <code>xfs</code> as default filesystem (as well as RHEL 7, Oracle Linux 7). Hence, my disk partitions are on <code>xfs</code>.</li>
<li>System configuration, permissions and so on must be saved.</li>
</ul>
<p>Let me explain in short why these things make everything harder.</p>
<p>First, if the size of the SSD drive was the same or greater than the HDD size, it would be possible to perform partition cloning. There are a lot of utilities which can do this - <strong>dd</strong>, <strong>ddrescue</strong>, <strong>partclone</strong> or <strong>clonezilla</strong>. On LVM partitions (which is also in CentOS 7 by default) moving data to another disk of actual or bigger size could be done even easier with <code>pvmove</code> command.</p>
<p>Then, if the filesystem was something different from <code>xfs</code>, for example, <code>ext4</code>, it would be possible to &apos;shrink&apos; partitions to the size smaller than the new disk and after that perform an operation mentioned above. But, unfortunately, shrinking an <code>xfs</code> partition is not possible (due to filesystem limitations), you can only extend it.</p>
<p>Last, but not the least, if it was not necessary to save working system, it would be much easier just to save a couple of important files and then to reinstall the system from the scratch. It didn&apos;t fit me because of two reasons. First, I have just spent two weeks configuring all and everything, so  I hated the idea to repeat this all. And second, it was kind of a challenge to me, which I didn&apos;t want to refuse.</p>
<h2 id="stage1preparation">Stage 1. Preparation</h2>
<p>Ok, let&apos;s make a list of what we need to start migration:</p>
<ol>
<li>Working Linux system to migrate. In my case it was CentOS 7.4, but I&apos;m sure everything would work on any Linux system with <code>xfs</code> on disk partitions.</li>
<li>CentOS bootable live-cd or USB flash drive. I love the <a href="http://ftp.neowiz.com/centos/7/isos/x86_64/CentOS-7-x86_64-LiveGNOME-1708.iso">Gnome version</a>, but there&apos;s a KDE option if preffered. I won&apos;t stop on how to burn a live-cd or make a bootable flash drive, there&apos;re loads of articles about it out there. CentOS live-cd distribution contains everything we need out-of-the-box since all that we need is <code>xfsdump</code> package which is preinstalled.</li>
<li>Data on the previous drive must fit the size of the new one. In my case only 10GB were busy on the 1TB disk, so, it was not a problem for me at all.</li>
<li>You also will need a cup of coffee to relax.</li>
</ol>
<h2 id="stage2movingthedata">Stage 2. Moving the data</h2>
<p>A good start will be to make a sip from your coffee cup and then boot the system from live-cd media. Then open a terminal window.</p>
<blockquote>
<p>All operations should be performed by super user (root).</p>
</blockquote>
<h3 id="step1enablingremoteaccessoptional">Step 1. Enabling remote access (optional)</h3>
<p>For me it is more convenient to perform operations from my desktop PC, because I can just copy and paste prepared in advance commands. If this is also about you, then enable remote access to your system. Set <code>root</code> user password and start SSH daemon for this:</p>
<pre><code>su
passwd
systemctl start sshd
</code></pre>
<p>Now, connect to the system using your SSH client (for example, <strong>PuTTY</strong>).</p>
<h3 id="step2partitioningyournewdisk">Step 2. Partitioning your new disk</h3>
<p>You can use any tool for doing this, but in this guide I will use <code>fdisk</code> intentionally, since other tools like <code>gparted</code> seem not to support NVMe disks yet (and so my SSD as well).</p>
<p>We should partition the new disk the same way the old one was partitioned. I am not partition-maniac and so on my previous disk there were only two partitions: 1GB <code>/boot</code> standard linux partition, 4GB <code>swap</code> and the rest of the disk was <code>\</code> (swap and root were under LVM volume group <code>main</code>).</p>
<p>So, let&apos;s do it:</p>
<pre><code class="language-bash">lsblk # check your new disk name
fdisk /dev/nvme0n1 # nvme0n1 here is the name of my new disk
n # create new partition (for /boot)
p # primary partition
# leave default (1st partition)
# leave default 
+1G # size for /boot partition
# done!
n # create new partition (for LVM volume group)
p # primary partition
# leave default (2nd partition)
# leave default 
# leave default (all the rest of the disk)
# done!
a # set bootable flag
1 # partition 1
p # check that everyting is ok
w # write partition table to the disk
</code></pre>
<p>Since <code>/boot</code> partition should be a standard linux partition, let&apos;s create a filesystem on it:</p>
<pre><code>mkfs.xfs /dev/nvme0n1p1 -f
</code></pre>
<p>And now we need to create LVM structure on new disk. And I will use name <code>newmain</code> for the new volume group:</p>
<pre><code class="language-bash">pvcreate /dev/nvme0n1p2 # create a new physical volume in LVM
vgcreate newmain /dev/nvme0n1p2 # create a volume group and add PV into it
lvcreate -L 4G -n swap newmain # create logical volume for swap of size 4G
lvcreate -l 100%FREE -n root newmain # create logical volume for root partition with the rest of the disk
vgchange -a y newmain # make the new volume group active
</code></pre>
<p>Now we are ready to create a filesystem on logical volumes:</p>
<pre><code class="language-bash">mkfs.xfs /dev/newmain/root # create file system on new root partition
mkswap -L swap /dev/newmain/swap # recreating swap in new place
swapon /dev/newmain/swap
</code></pre>
<h3 id="step3activephase">Step 3. Active phase</h3>
<p>Before we start, we need to make the old LVM volume group active:</p>
<pre><code>vgchange -a y main
</code></pre>
<p>Now, create directories for mount points and mount old and new partitions to them:</p>
<pre><code class="language-bash">mkdir -p /mnt/old/boot
mkdir -p /mnt/old/root
mkdir -p /mnt/new/boot
mkdir -p /mnt/new/root
mount /dev/sda1 /mnt/old/boot
mount /dev/nvme0n1p1 /mnt/new/boot
mount /dev/main/root /mnt/old/root
mount /dev/newmain/root /mnt/new/root
</code></pre>
<p>Let&apos;s check is everything is ok with <code>lsblk</code>:</p>
<pre><code class="language-bash">lsblk
NAME             MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda                8:0    0 931.5G  0 disk
&#x251C;&#x2500;sda1             8:1    0     1G  0 part /mnt/old/boot
&#x2514;&#x2500;sda2             8:2    0 930.5G  0 part
  &#x251C;&#x2500;main-swap    253:0    0   3.6G  0 lvm  [SWAP]
  &#x2514;&#x2500;main-root    253:1    0 926.9G  0 lvm  /mnt/old/root
nvme0n1          259:0    0 119.2G  0 disk
&#x251C;&#x2500;nvme0n1p1      259:3    0     1G  0 part /mnt/new/boot
&#x2514;&#x2500;nvme0n1p2      259:4    0 118.2G  0 part
  &#x251C;&#x2500;newmain-swap 253:5    0     4G  0 lvm  [SWAP]
  &#x2514;&#x2500;newmain-root 253:6    0 114.2G  0 lvm  /mnt/new/root
</code></pre>
<p>If you see something like this, you&apos;re in one step to real magic - we are going to use <code>xfsdump</code> to move the data. This utility is very smart and knows how <code>xfs</code> works, that&apos;s why it copies only busy blocks and does it really fast saving all the metadata like permissions. So, let&apos;s use it to dump the data from old partitions and restore it on the fly to the new ones:</p>
<pre><code class="language-bash">xfsdump -l0 -J - /mnt/old/boot | xfsrestore -J - /mnt/new/boot # clone boot partition
xfsdump -l0 -J - /mnt/old/root | xfsrestore -J - /mnt/new/root # clone root partition
</code></pre>
<p>A couple of words about the flags used:</p>
<ul>
<li><code>-J</code> disables verbosity</li>
<li><code>-</code> tells <code>xfsdump</code> and <code>xfsrestore</code> to use <code>stdout</code> and <code>stdin</code> respectively instead of a file.</li>
</ul>
<p>This operation <strong>can take several minutes</strong> (depends on how much data you have). So, it is a good time to finish your coffee.</p>
<p>If you done well, your data is copied. Now you need to fix some configs and install Grub2 on the new disk in order to make the system on new disk bootable.</p>
<h3 id="step4makingnewdiskbootable">Step 4. Making new disk bootable</h3>
<p>First, find out UUIDs of your old and new <code>/boot</code> partitions using <code>blkid</code>:</p>
<pre><code class="language-bash">blkid
...
/dev/nvme0n1p1: UUID=&quot;3055d690-7b2d-4380-a3ed-4c78cd0456ba&quot; TYPE=&quot;xfs&quot;
/dev/sda1: UUID=&quot;809fd5ba-3754-4c2f-941a-ca0b6fb5c86e&quot; TYPE=&quot;xfs&quot;
...
</code></pre>
<p>Assuming <code>sda1</code> is the old <code>\boot</code> partition, and <code>nvme0n1p1</code> - the new one, perform UUID replacement like this:</p>
<pre><code class="language-bash">sed -i &quot;s/809fd5ba-3754-4c2f-941a-ca0b6fb5c86e/3055d690-7b2d-4380-a3ed-4c78cd0456ba/g&quot; /mnt/new/root/etc/fstab
sed -i &quot;s/809fd5ba-3754-4c2f-941a-ca0b6fb5c86e/3055d690-7b2d-4380-a3ed-4c78cd0456ba/g&quot; /mnt/new/boot/grub2/grub.cfg
</code></pre>
<p>This two commands will prepare your system configs for the new disk.</p>
<p>Now, it&apos;s high time to place the new LVM volume group in the place where the old was and unmount disks:</p>
<pre><code class="language-bash">umount /mnt/{old,new}/{boot,root}
vgrename -v {,old}main
vgrename -v {new,}main
</code></pre>
<p>The only thing which is left is to install Grub2 to the new disk. It must be done using <code>chroot</code>:</p>
<pre><code class="language-bash">mount /dev/main/root /mnt
mkdir -p /mnt/boot
mount /dev/nvme0n1p1 /mnt/boot
mount -t devtmpfs /dev /mnt/dev
mount -t proc /proc /mnt/proc
mount -t sysfs /sys /mnt/sys
chroot /mnt/ grub2-install /dev/nvme0n1
</code></pre>
<h3 id="step5done">Step 5. Done!</h3>
<p>Now you need to restart the system and use the new disk as a boot device. You can remove the old disk from the system if everything is ok:</p>
<pre><code>systemctl reboot -f
</code></pre>
<hr>
<blockquote>
<p>If something went wrong and your system doesn&apos;t work, you are able to rollback the changes by booting from live-cd again and renaming LVM volume groups back by performing <code>vgrename -v {,new}main</code> and <code>vgrename -v {old,}main</code></p>
</blockquote>
<hr>
<h2 id="usingtheoldhddasmediastorage">Using the old HDD as media storage</h2>
<p>In case you, like me, want to use your old disk as media storage, perform these operations.</p>
<p>First, repartition the disk:</p>
<pre><code class="language-bash">fdisk /dev/sda
d # delete partition 2
d # delete partition 1
n # new partition
p # primary
# default
# default
# 100%
# done!
p # check if everything is ok
w # write partition
</code></pre>
<p>We won&apos;t create a filesystem on the new partition. Instead, we will create a new LVM volume group and add this disk to it. After that we will create a logical volume within the group which is going to occupate all the available space. And only after that we will create filesystem on the logical volume:</p>
<pre><code class="language-bash">pvcreate /dev/sda1 # new LVM physical volume
vgcreate media /dev/sda1 # new LVM volume group
lvcreate -l 100%FREE -n media1 media # new logical volume
vgchange -a y media # make the volume group active

mkfs.xfs /dev/media/media1 # create filesystem on new logical volume

mkdir -p /var/media # dir for mount point
mount /dev/media/media1 /var/media # mount new disk to the system
</code></pre>
<p>Using LVM volume group allows you to extend this partition in the future on-the-fly very easily (for example, when you run out of free space).</p>
<p>In order to save changes after reboot, you need to add a record about new mount point to <code>/etc/fstab</code>:</p>
<pre><code class="language-bash">/dev/mapper/media-media1 /var/media                       xfs     defaults        0 0
</code></pre>
<p>And finally we can say that we successfully moved the working system on another physical drive, which was smaller than the original one. As a bonus, we started to use the original disk as media storage.</p>
<h2 id="extendingthemediastoragewithanadditionalphysicaldrive">Extending the media storage with an additional physical drive</h2>
<p>It happened, that I ran out of free space of my media storage really fast. And I decided to obtain another hard drive as an extention. So, I installed it on my system and powered up the server.</p>
<p>At this point I had several options of &apos;how&apos; to use a new drive. It could be a standard partition, a new logical volume in my LVM volume group <code>media</code> or, which I actually wanted to do the most, this disk could be a true extention of the existing logical volume <code>media1</code>, which we previously created.</p>
<p>So, is this chapter I&apos;d like to share with you how easy it is if you use <code>xfs</code> filesystem on a LVM volume group.</p>
<p>First, let&apos;s check what we have after we installed a physical drive.</p>
<pre><code class="language-bash">lsblk
NAME             MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda                8:0    0 931.5G  0 disk
sdb                8:16   0 931.5G  0 disk
&#x2514;&#x2500;sdb1             8:17   0 931.5G  0 part
  &#x2514;&#x2500;media-media1 253:2    0 931.5G  0 lvm  /var/media
nvme0n1          259:0    0 119.2G  0 disk
&#x251C;&#x2500;nvme0n1p1      259:1    0     1G  0 part /boot
&#x2514;&#x2500;nvme0n1p2      259:2    0 118.2G  0 part
  &#x251C;&#x2500;main-root    253:0    0 114.2G  0 lvm  /
  &#x2514;&#x2500;main-swap    253:1    0     4G  0 lvm  [SWAP]
</code></pre>
<p>As you can see, the new disk became <code>sda</code> because of some reason, and the old one is now <code>sdb</code>. So, let&apos;s create a partition table on the new disk, we&apos;re going to use <code>fdisk</code> again for this purpose (though I&apos;m not sure if this step is mandatory, so, do not hesitate to comment on this).</p>
<pre><code class="language-bash">fdisk /dev/sda
# DOS partition table is created automatically on a new disk after running `fdisk`
n # new partition
p # primary
# default
# default
# 100%
# done!
p # check if everything is ok
w # write partition
</code></pre>
<p>Let&apos;s check.</p>
<pre><code class="language-bash">lsblk
NAME             MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda                8:0    0 931.5G  0 disk
&#x2514;&#x2500;sda1             8:1    0 931.5G  0 part
sdb                8:16   0 931.5G  0 disk
&#x2514;&#x2500;sdb1             8:17   0 931.5G  0 part
  &#x2514;&#x2500;media-media1 253:2    0 931.5G  0 lvm  /var/media
nvme0n1          259:0    0 119.2G  0 disk
&#x251C;&#x2500;nvme0n1p1      259:1    0     1G  0 part /boot
&#x2514;&#x2500;nvme0n1p2      259:2    0 118.2G  0 part
  &#x251C;&#x2500;main-root    253:0    0 114.2G  0 lvm  /
  &#x2514;&#x2500;main-swap    253:1    0     4G  0 lvm  [SWAP]
</code></pre>
<p>So, now we have a partition <code>sda1</code>, which we&apos;re ready to add to our LVM volume group. Then, we need to extend our logical volume inside the volume group by all unallocated space. And after that, grow the filesystem on the volume in order for it to occupy the free space. Sounds scarier than it actually is.</p>
<pre><code class="language-bash">pvcreate /dev/sda1 # create a new LVM physical volume for the new partition
vgextend media /dev/sda1 # extend the LVM volume group with the physical volume
lvextend -l +100%FREE /dev/media/media1 # extend the logical volume
xfs_growfs /dev/media/media1 # grow the filesystem
</code></pre>
<p>That&apos;s it! The new hard drive is now a part of our old media storage, and we don&apos;t need to think about where to put our new media - we still can save it in the old place. What a wonderful thing LVM!</p>
<p>Hope this post will be helpful for somebody else besides myself. Do not hesitate to express your thoughts in comments if any.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Reblog: APEX as a PWA]]></title><description><![CDATA[Reblog of a complete guide on how to make an APEX application a Progressive Web Application, which was kindly shared with us by Vincent Morneau]]></description><link>https://dsavenko.me/reblog-apex-as-a-pwa/</link><guid isPermaLink="false">5b629165c0c06007ce6e61ed</guid><category><![CDATA[apex]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Thu, 02 Aug 2018 10:12:20 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/08/apex-pwa-5.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/08/apex-pwa-5.png" alt="Reblog: APEX as a PWA"><p>In the most recent <a href="https://asktom.oracle.com/pls/apex/f?p=100:551:::NO:551:P551_CLASS_ID:4821&amp;cs=19CC38CD2AE043DD677815F56E7146E43">APEX AskTOM Office Hours</a> I heard <a href="https://youtu.be/oTA1NdjBqyY?t=3134">a question</a> about turning an APEX application into a <strong>PWA</strong>. I didn&apos;t know about the fact such a term existed before that moment. And now we have a complete guide on how to make a <strong>PWA</strong> from an Oracle APEX application.</p>
<p>You could think I only reblog articles by <em><a href="https://twitter.com/vincentmorneau">Vincent Morneau</a></em>, because I&apos;m his fan. And though this is not true (he is not the only one whose fan I am), I definitely think that every blog post composed by him is a true golden nugget.</p>
<p>And this complete guide on how to turn an APEX application to a PWA is not an exception. <a href="https://en.wikipedia.org/wiki/Progressive_Web_Apps">PWA</a> (or Progressive Web Application) - is a web application which is a traditional website (or set of web pages), but can appear to the user like a native mobile or a traditional application, which also work offline. This guide, which consists of eight parts and a demo, will introduce you to this world and Oracle APEX&apos;s place in it. Enjoy the reading!</p>
<h2 id="httpvmorneaumeapexpwa"><a href="http://vmorneau.me/apex-pwa/">http://vmorneau.me/apex-pwa/</a></h2>
<hr>
<p>Thanks, Vincent, for the great effort which you applied to compose such a complete instruction and for sharing it with us! I&apos;m quite assured that such <em>pieces of knowledge</em> should be shared amongst more people and hope my 2 cent will help here a little.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop]]></title><description><![CDATA[In the post I'm trying to express my personal opinion about my freshly obtained laptop by Lenovo.]]></description><link>https://dsavenko.me/lenovo-thinkpad-x1-carbon-personal-impression-of-laptop/</link><guid isPermaLink="false">5b50964feab6d906283cf49a</guid><category><![CDATA[off-topic]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Fri, 20 Jul 2018 19:06:18 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/07/lenovo-thinkpad-x1-carbon-logo.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/07/lenovo-thinkpad-x1-carbon-logo.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop"><p>It was quite a while since the last time I composed a blog post. And this time it&apos;s going to be an off-topic laid-back article about my new toy.</p>
<p>I have not used my old laptop regularly for at least 5 years - always preferred working on my desktop PC over it. However, in the past, when I was a university student, I used to work a lot on it. Why things changed? Because I graduated from the university, hence didn&apos;t want to take it with me every day, because it&apos;s quite heavy and not comfortable to be taken casually, and maybe because it&apos;s not <em>that cool</em> to open a full-sized notebook from 2008 in a cafe in the era of MacBooks.</p>
<p>On the other hand, recently I often was in a situation when I lack a device on a conference or even on a workday meeting. So, I started to scan the market casually to find out what machine I would like to obtain. And when I came across <a href="https://twitter.com/oraclebase/status/998454143072124928">a tweet</a> by <em><a href="https://twitter.com/oraclebase">Tim Hall</a></em> about his new purchase, I dared and got my <strong>Lenovo ThinkPad X1 Carbon (6th gen)</strong>. And here&apos;s my personal impression of it.</p>
<p>This is not going to be an ordinary review of the laptop (if you still need one, you can get it <a href="https://www.theverge.com/2018/4/12/17226578/lenovo-thinkpad-x1-carbon-2018-laptop-sixth-generation-windows-review-price-specs">here</a> or <a href="https://www.windowscentral.com/lenovo-x1-carbon-2018-review">here</a>), it&apos;s rather going to be a kind of my answer on the question <em>Why?</em>. There&apos;s not going to be much of technical stuff or specs, but rather more personal impressions and thoughts. So, if you like this kind of reading, enjoy!</p>
<h2 id="criteriaofchoice">Criteria of choice</h2>
<p>I may seem naive, but what I value the most in my devices is not the specs or numbers in review tests, but comfort and pleasure when using them. I am a developer and I spend a lot more time with my tech than with family or friends, which sometimes frustrates me. Hence my life partially consists of using this tech, and since we have only one life, why waste it on devices we don&apos;t like. So, I advocate subjective approach here than following the objective guidelines (within reasonable limits, of course).</p>
<p>That&apos;s why I paid the most of my attention on such things as look and feel of laptops, their mobility and keyboard usage comfort. It was also crucial for me to have a good set of ports (which is not an often case nowadays), because I wanted to be able to connect even my old peripherals to the machine. Then, I&apos;m not that guy who changes their tech every year, so I wanted a device which would be actual for at least half a dozen years.</p>
<p>Well, I suppose that is it, let&apos;s start with what models I took into consideration.</p>
<h2 id="competitors">Competitors</h2>
<p>There&apos;s a number of devices which meet my expectation at least to a certain extent. Though there was a bit of a problem - unfortunately, it was impossible for me to phisically play with the devices before buying, because the local shops in my town don&apos;t have them in their show rooms. Hence I could rely only on the reviews in the web. And here&apos;s what I had.</p>
<h3 id="dellxps13">Dell XPS 13</h3>
<p><img src="https://dsavenko.me/content/images/2018/07/competitors-dell-xps-13.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"><br>
The full review is <a href="https://www.techradar.com/reviews/pc-mac/laptops-portable-pcs/laptops-and-netbooks/dell-xps-13-1328971/review">here</a>.</p>
<h4 id="likes">Likes</h4>
<ul>
<li><em><a href="https://twitter.com/oraclebase">Tim Hall</a></em> uses the 15-inch version of it. This means this piece of tech is awesome.</li>
<li>Really nice overall dimensions - it is one of the smallest in the niche.</li>
<li>Very thin screen bazels.</li>
<li>Shell materials.</li>
<li>Well-known and reliable brand.</li>
</ul>
<h4 id="dislikes">Dislikes</h4>
<ul>
<li>Awkward camera placement - and this was unacceptable for me. I know that I&apos;m not Brad Pitt, but such camera would make me look just ridiculous during video calls.</li>
<li>Not the best keyboard for me.</li>
<li>Not enough ports (seriously, let me live with more than 3 USB-C ports for the at least next five years).</li>
<li>Lid opens only up to 135 degrees. I sometimes like laying on my sofa with my knees bent and a laptop laying on them. It&apos;s a very comfortable pose to read or watch some videos, or even to type someting. But to make it comfortable, the device should be opened up to about 160 degrees.</li>
</ul>
<h3 id="hpspectre13">HP Spectre 13</h3>
<p><img src="https://dsavenko.me/content/images/2018/07/competitors-hp-spectre-13.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"><br>
The full review is <a href="https://www.techradar.com/reviews/hp-spectre-13">here</a>.</p>
<h4 id="likes">Likes</h4>
<ul>
<li>Very light and thin.</li>
<li>They promised that there won&apos;t be fingerprints on the laptop&apos;s surface.</li>
<li>Bundled cover case.</li>
<li>Better keyboard layout. Really liked the <code>PgUp</code>, <code>PgDn</code>, <code>Home</code> and <code>End</code> buttons placement.</li>
</ul>
<h4 id="dislikes">Dislikes</h4>
<ul>
<li>Not really my design, no, it&apos;s awesome, but just not mine - don&apos;t like all these gold and bronze shiny elements. Although, I&apos;d definitely consider this notebook for my girlfriend.</li>
<li>Very weird lid opening mechanism - the device don&apos;t look solid when opened.</li>
<li>The trackpad don&apos;t look great.</li>
<li>Very few very strangely places ports (again only three USB-C ports over here).</li>
</ul>
<h3 id="huaweimatebookxpro">Huawei MateBook X Pro</h3>
<p><img src="https://dsavenko.me/content/images/2018/07/competitors-huawei-matebook-x-pro.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"><br>
The full review is <a href="https://www.techradar.com/reviews/huawei-matebook-x-pro">here</a>.</p>
<h4 id="likes">Likes</h4>
<ul>
<li>The design is very appealing to me. I find it nicer than the MacBook&apos;s.</li>
<li>Very thin bazels around the display.</li>
<li>Good battery lifetime promised (about 12 hours).</li>
</ul>
<h4 id="dislikes">Dislikes</h4>
<ul>
<li>Again very awkward camera placement.</li>
<li>Keyboard sucks in all aspects (layout and look and feel).</li>
<li>Very few ports one more time (only two USB-C here and one USB-A).</li>
<li>No options without useless touchscreen.</li>
<li>No localized version for my country.</li>
<li>No SD card slot.</li>
<li>Huawei making notebooks? Okay, they convinced me they can make good phones, but I can&apos;t say the same about laptops.</li>
</ul>
<h3 id="applemacbookpro">Apple MacBook Pro</h3>
<p><img src="https://dsavenko.me/content/images/2018/07/competitors-macbook-pro.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"><br>
The full review is <a href="https://www.techradar.com/reviews/macbook-pro">here</a>.</p>
<h4 id="likes">Likes</h4>
<ul>
<li>It&apos;s a MacBook. We all know about a MacBook quality. MacOS works perfectly smooth, there&apos;re tons of applications.</li>
<li>Good shell materials.</li>
<li>The display quality.</li>
<li>Deeper iPhone integration - and since I&apos;m an owner of this smartphone, this is a plus for me.</li>
</ul>
<h4 id="dislikes">Dislikes</h4>
<ul>
<li>It&apos;s a MacBook. MacOS is not always the best option for a database developer. Also, people often complain how Apple ruins everything with the OS updates.</li>
<li>Gosh, it is pricey.</li>
<li>The worst keyboard ever. This was the only option which I could touch before buying, and I loathed the feel of keyboard. And when I started to hear very &apos;nice&apos; feedback about it from the current users, I loathed it even more.</li>
<li>Not long battery life.</li>
<li>Very thick borders around the screen for these days.</li>
</ul>
<p>And finally.</p>
<h3 id="lenovothinkpadx1carbon6thgen">Lenovo ThinkPad X1 Carbon (6th gen)</h3>
<p><img src="https://dsavenko.me/content/images/2018/07/competitors-lenovo-thinkpad-x1-carbon.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"><br>
The full review is <a href="https://www.theverge.com/2018/4/12/17226578/lenovo-thinkpad-x1-carbon-2018-laptop-sixth-generation-windows-review-price-specs">here</a>.</p>
<h4 id="likes">Likes</h4>
<ul>
<li>I just loved the keyboard. There&apos;s adequate room between the keys, the keys move deeply, good shape of the keys.</li>
<li>14-inch screen instead of 13.3.</li>
<li>Very light (1.12 kg) and compact by overall dimensions.</li>
<li>Made of carbon, must be very durable.</li>
<li>Good battery life promised.</li>
<li>Thanks gosh, a good set of ports (two USB-C, two USB-A and separate HDMI).</li>
<li>Dispay lid can be opened up to 180 degrees (I described above why it&apos;s cool).</li>
<li>MicroSD card slot.</li>
</ul>
<h4 id="dislikes">Dislikes</h4>
<ul>
<li>Keyboard layout. Placement of <code>Fn</code> and <code>Ctrl</code> buttons is very disturbing.</li>
<li>Poor fingerprint sensor.</li>
<li>Pricey.</li>
</ul>
<blockquote>
<p>Note: All these points were made before actual purchase of the laptop.</p>
</blockquote>
<h2 id="personalimpression">Personal impression</h2>
<p>And finally, let&apos;s sort out what I ended up with.</p>
<p>As I already mentioned above, I went for the machine by <em>Lenovo</em>. For those who already desperate to know - I&apos;m not dissapointed. I loved my new toy a lot from the first sight - this is really hard to decribe that difference between the new device and my old <strong>Samsung R460</strong>. So, the tech was definitely worth its money.</p>
<p>I must say that almost all the points which I made about the <strong>ThinkPad X1 Carbon</strong> before buying it came true. The only thing I&apos;m not totally sure about is the battery life time. It rather feels like 8 to 9 hours, not 12 as was promised. But maybe this was because I used it very actively during the first days when was setting it up.</p>
<p>But I&apos;m sure you are here not for the praise, but for the blame. Well, I have something to say here.</p>
<p>First of all, not very good quality of the fingerprint sensor was confirmed. I&apos;m already spoiled by the iPhone&apos;s touchID, which works extremely fast and accurate, and in comparison to it, the sensor on <strong>X1 Carbon</strong> leaves much to be desired. Though, it fulfils its function and it&apos;s still more than possible to log in using it.</p>
<p>Then, what frustrated me the most is the soft touch surface material. It&apos;s a real pleasure to touch the device, but it&apos;s as well extremely easy to leave your fingerprints on it, and what is even worse - it&apos;s harder to remove these stains then than to leave new ones.</p>
<p>Also, there could be an issue with the noise when the battery charger is connected to the laptop. This is because of the default thermal settings in BIOS/UEFI - every time the laptop is on AC, it by default chooses the maximum performance profile, and starts to heat much faster, so I&apos;d recommend to switch the profile if you don&apos;t like the noise.</p>
<p>But enough about the drawbacks, there&apos;re some unexpected points which made me a bit happier. First - the problem with the keyboard layout (<code>Fn</code> and <code>Ctrl</code> buttons placement) is easily fixed - you can switch the buttons functions in BIOS/UEFI or in the Windows Control Panel using <code>Lenovo Keyboard Manager</code> application.</p>
<p><img src="https://dsavenko.me/content/images/2018/07/lenovo-keyboard-manager-1.png" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"></p>
<p>And another nice addition - the phisical camera shutter. I guess this is a crucial feature to have in the world where even <em>Mark Zuckerberg</em> tapes his webcam.</p>
<p><img src="https://dsavenko.me/content/images/2018/07/mark-zuckerberg-tapes-his-camera.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"></p>
<p>To sum up all this, I can say that my new <strong>Lenovo ThinkPad X1 Carbon</strong> had a really good first impression on me. However, how it often is, it&apos;s not ideal, but the found drawbacks are not crucial for me, so I&apos;m still a happy owner of a new toy with a lot of use cases. And what I like the most about it - being this light and compact on the one hand and really powerful on the other makes this device truly universal for me.</p>
<p>The only thing which is left is to decide where to apply my red <a href="https://twitter.com/hashtag/orclapex">#orclapex</a> sticker on this brand-new tool. And then I&apos;m fully equiped to start with <strong>APEX</strong> development on it!</p>
<h2 id="gallery">Gallery</h2>
<p><img src="https://dsavenko.me/content/images/2018/07/thinkpad-x1-carbon-photo1.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"><br>
View with the lid closed and the Lenovo ThinkPad Laser Mouse accessory.</p>
<p><img src="https://dsavenko.me/content/images/2018/07/thinkpad-x1-carbon-photo2.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"><br>
View with the lid opened.</p>
<p><img src="https://dsavenko.me/content/images/2018/07/thinkpad-x1-carbon-samsung-r460-photo1.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"><br>
Comparison with my old <strong>Samsung R460</strong> laptop. Top view.</p>
<p><img src="https://dsavenko.me/content/images/2018/07/thinkpad-x1-carbon-samsung-r460-photo2.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"><br>
Comparison with my old <strong>Samsung R460</strong> laptop. Rear view.</p>
<p><img src="https://dsavenko.me/content/images/2018/07/thinkpad-x1-carbon-samsung-r460-photo3.jpg" alt="Lenovo ThinkPad X1 Carbon 2018: personal impression of laptop" loading="lazy"><br>
Comparison with my old <strong>Samsung R460</strong> laptop. Front view.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Reblog: How to avoid JavaScript mess in APEX applications]]></title><description><![CDATA[Reblog of a great guide on how to keep JavaScript in APEX applications clean and tidy]]></description><link>https://dsavenko.me/reblog-how-to-avoid-javascript-mess-in-apex-applications/</link><guid isPermaLink="false">5b508d11eab6d906283cf497</guid><category><![CDATA[apex]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Thu, 19 Jul 2018 13:37:48 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/07/javascript-code.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/07/javascript-code.jpg" alt="Reblog: How to avoid JavaScript mess in APEX applications"><p>APEX is a great tool, and how it often happens, you can achieve same things in a dozen of ways using it. That&apos;s great, but sometimes it&apos;s really hard to determine how to organize and structure your applications, because they tend to become a catastrophy with the speed of light.</p>
<p>Why care, if it still works? Because a well-structured application is much easier to improve even for different developers, it&apos;s much easier for them to work together when there&apos;re clear development guidelines. And I haven&apos;t even mentioned errors, which are much more likely to be made in a messy application.</p>
<p>That&apos;s why I wanted to share with you an awesome guide by <em><a href="https://twitter.com/vincentmorneau">Vincent Morneau</a></em> on <em>how to keep your applications clean and tidy</em> when it comes to custom <strong>JavaScript</strong>. Enjoy the reading!</p>
<h2 id="httpvmorneaumeavoidjavascriptmess"><a href="http://vmorneau.me/avoid-javascript-mess/">http://vmorneau.me/avoid-javascript-mess/</a></h2>
<hr>
<p>I find all the tips really useful, but what is even more important is how coherently they are given. Seriously, guys, if only we had such guides on all aspects of APEX development, such as database architecture, business logic development using SQL and PL/SQL and version control, it would be ridiculously simple to start making world-class applications. Thank you, Vincent!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[How APEX executes SQL statements without having permissions?]]></title><description><![CDATA[How APEX executes SQL in against other database schemas? Its internal user wasn't given a single permission! In this post I am trying to investigate this matter and question if it is a security concern or not]]></description><link>https://dsavenko.me/how-apex-executes-sql-statements-without-having-permissions/</link><guid isPermaLink="false">5b2cd208eab6d906283cf46b</guid><category><![CDATA[apex]]></category><dc:creator><![CDATA[Denis Savenko]]></dc:creator><pubDate>Fri, 22 Jun 2018 13:54:36 GMT</pubDate><media:content url="https://dsavenko.me/content/images/2018/06/shocked.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://dsavenko.me/content/images/2018/06/shocked.jpg" alt="How APEX executes SQL statements without having permissions?"><p>I don&apos;t know about you, but I was <em>shocked</em> when saw how easily APEX executes SQL statements against my schemas for the first time. Seriously, I didn&apos;t grant a single permission to any APEX user, I only added a workspace to schema assignment inside the instance administration panel. And after that, all workspace developers started to have the same rights as the owner of the database schema, associated with the workspace. And this raises a set of questions.</p>
<h2 id="howitworks">How it works?</h2>
<p>So, what mechanisms work under the APEX&apos;s hood to make it able to execute SQL and PL/SQL statements with the owner rights? And if you google this, you could find <a href="https://blog.pythian.com/using-dbms_sys_sql-to-execute-statements-as-another-user/">some sources</a> telling that it leverages the package called <code>DBMS_SYS_SQL</code> in the <code>SYS</code> schema.</p>
<p><code>DBMS_SYS_SQL</code> is an undocumented package, which is <a href="https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1325202421535">not meant</a> to be used outside of Oracle, because it gives the invoker an ability to execute <em>anything</em> with rights of <em>anybody</em>. Mostly, it repeats the functionality of <code>DBMS_SQL</code>, but instead of, for instance, <code>parse</code> function, there&apos;s the <code>parse_as_user</code> and so forth.</p>
<p>In the recent <a href="https://asktom.oracle.com/pls/apex/f?p=100:551:::NO:551:P551_CLASS_ID,P551_STUDENT_ID:864,&amp;cs=18A76805E9960CA80A50FD22FE7F0B10E">Database Administration AskTOM Office Hours</a> I asked this question to <em><a href="https://twitter.com/connor_mc_d">Connor Mc&apos;Donald</a></em>, a recognized <em>Oracle Master</em>, and he confirmed the fact that APEX uses <code>DBMS_SYS_SQL</code> to execute statements. And he insisted on not granting <code>EXECUTE</code> permission on the package to anyone. Seriously. Nobody.</p>
<p>But if you go deeper, you will be surprised even more. The thing is that <code>DBMS_SYS_SQL</code> is indeed not granted to be executed by anyone. I tried everything to prove the opposite - from querying system views such as <code>DBA_TAB_PRIVS</code> and <code>ALL_TAB_PRIVS</code> to <em>Pete Finnigan</em>&apos;s <a href="http://www.petefinnigan.com/tools.htm"><em>Who-Can-Access-set-of-scripts</em></a>, but I failed to find a single user permitted to use the package!</p>
<p>This means that nobody can use <code>DBMS_SYS_SQL</code> directly (except <code>SYS</code>, of course), neither can APEX. But how then does APEX do it? And why did <em>Connor Mc&apos;Donald</em> confirm it?</p>
<p>That&apos;s because APEX calls <code>DBMS_SYS_SQL</code> through a broker package, called <code>WWV_DBMS_SQL_APEX_180100</code> (the suffix depends on the version of APEX installed), which is also owned by the user <code>SYS</code>, and this package is granted to the APEX internal user <code>APEX_180100</code> (the user name also depends on the version of APEX installed).</p>
<p>So, this literally means everybody who has access to the APEX internal user, has potential to elevate their rights to execute any SQL with the rights of any database user.</p>
<h2 id="isitabigsecurityconcern">Is it a big security concern?</h2>
<p>It depends.</p>
<p>The thing is that if we query <code>ALL_TAB_PRIVS</code> to get the users who have grants on the <code>WWV_DBMS_SQL_APEX_180100</code> package, we&apos;ll find out there&apos;s nobody, but <code>APEX_180100</code>.</p>
<p>And the user <code>APEX_180100</code> is locked during APEX installation process. It means that, at least, <em>nobody can connect</em> to your database as this user to leverage its power.</p>
<p>Though, there&apos;re other packages which are owned by this user and some of these packages are granted to <code>PUBLIC</code>. Hence, every user in the database can execute subprograms in them. And when they invoke a procedure from one of those packages, this procedure is invoked with the rights of the package owner (read <code>APEX_180100</code>).</p>
<p>To be even more precise, there&apos;s a query to retrieve a list of packages dependant on the dangerous <code>WWV_DBMS_SQL_APEX_180100</code>:</p>
<pre><code class="language-sql">select distinct owner||&apos;.&apos;||name from all_dependencies where referenced_owner = &apos;SYS&apos; and referenced_name = &apos;WWV_DBMS_SQL_APEX_180100&apos; order by 1;
</code></pre>
<p>We see a plenty:</p>
<pre><code>APEX_180100.WWV_FLOW_AUTHENTICATION_NATIVE
APEX_180100.WWV_FLOW_AUTHORIZATION
APEX_180100.WWV_FLOW_CALENDAR
APEX_180100.WWV_FLOW_CUSTOM_AUTH_STD
APEX_180100.WWV_FLOW_DATALOAD_XML
APEX_180100.WWV_FLOW_DATA_UPLOAD
APEX_180100.WWV_FLOW_DISP_PAGE_PLUGS
APEX_180100.WWV_FLOW_DML
APEX_180100.WWV_FLOW_DYNAMIC_EXEC
APEX_180100.WWV_FLOW_EXEC_LOCAL
APEX_180100.WWV_FLOW_EXEC_REMOTE
APEX_180100.WWV_FLOW_F4000_PLUGINS
APEX_180100.WWV_FLOW_FEEDBACK_INT
APEX_180100.WWV_FLOW_INSTANCE_ADMIN
APEX_180100.WWV_FLOW_INSTANCE_REST_ADMIN
APEX_180100.WWV_FLOW_ITEM
APEX_180100.WWV_FLOW_LOAD_DATA
APEX_180100.WWV_FLOW_LOAD_EXCEL_DATA
APEX_180100.WWV_FLOW_PLUGIN
APEX_180100.WWV_FLOW_PLUGIN_DEV
APEX_180100.WWV_FLOW_PLUGIN_UTIL
APEX_180100.WWV_FLOW_PROPERTY_DEV
APEX_180100.WWV_FLOW_REST
APEX_180100.WWV_FLOW_SECURITY
APEX_180100.WWV_FLOW_SESSION
APEX_180100.WWV_FLOW_SESSION_RAS
APEX_180100.WWV_FLOW_SW_API
APEX_180100.WWV_FLOW_TEAM_FILE
APEX_180100.WWV_FLOW_TREE
APEX_180100.WWV_FLOW_UTILITIES
APEX_180100.WWV_FLOW_WEB_SERVICES
APEX_180100.WWV_FLOW_WS_API
APEX_180100.WWV_FLOW_WS_ATTACHMENT
APEX_180100.WWV_FLOW_WS_GEOCODE
APEX_180100.WWV_FLOW_WS_IMPORT
APEX_180100.WWV_FLOW_WS_SETUP
SYS.WWV_DBMS_SQL_APEX_180100
</code></pre>
<p>After rewriting our query to get only those which are granted to <code>PUBLIC</code>:</p>
<pre><code class="language-sql">select 
  distinct owner||&apos;.&apos;||name from all_dependencies x
where referenced_owner = &apos;SYS&apos; 
  and referenced_name = &apos;WWV_DBMS_SQL_APEX_180100&apos; 
  and exists (select null from all_tab_privs where table_name = x.name and grantee = &apos;PUBLIC&apos;)
order by 1;
</code></pre>
<p>It looks like we still see some..</p>
<pre><code>APEX_180100.WWV_FLOW_CUSTOM_AUTH_STD
APEX_180100.WWV_FLOW_DATA_UPLOAD
APEX_180100.WWV_FLOW_ITEM
APEX_180100.WWV_FLOW_PLUGIN_UTIL
APEX_180100.WWV_FLOW_REST
APEX_180100.WWV_FLOW_UTILITIES
</code></pre>
<p>But should we start to panic right now? I don&apos;t think so. Why? Because I believe that people who developed these packages thought about security and that there&apos;s no possibility to <em>abuse subprograms</em> in this set of packages, which can make it possible to <em>elevate rights</em> of a target user to a desired level.</p>
<p>But we should remember that such probability still exists.</p>
<h2 id="whattodo">What to do?</h2>
<p>So what to do now? Nothing. Seriously. There&apos;re no <em>what to do&apos;s</em>, there&apos;re <em>what NOT to do&apos;s</em>:</p>
<ul>
<li>Never unlock the internal APEX user (in example, <code>APEX_180100</code> for the 18.1 version of APEX). It was locked <strong>intentionally</strong>.</li>
<li>Never grant <code>EXECUTE</code> privilege on either <code>DBMS_SYS_SQL</code> or <code>WWV_DBMS_SQL_APEX_180100</code> to anybody. Seriously. They are not documented and not meant to be used <strong>deliberately</strong>.</li>
<li>Never set a weak password for the APEX instance administrator user. These strong default password complexity rules are there <strong>on purpose</strong>. Remember, by creating a workspace-schema assignment, the instance administrator automatically gives owner&apos;s privileges on the given database schema objects to all workspace developers.</li>
<li>Do not associate APEX workspaces with powerful schemas. Always create a specific schema dedicated to a particular APEX workspace. Then give needed permissions on needed database objects to this particular schema user. Otherwise, you risk to end up giving much more power to workspace developers and APEX applications than they need. And giving more than needed is always a bad thing when it comes to security.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>I should warn you that despite the fact that I definitely have some experience in working with <em>Oracle Database</em>, I am not an <em>Oracle Ace</em>, neither by title, nor by skills. So, I can surely be wrong in some of my conclusions. But I believe if I&apos;m mistaken somewhere or missing something, I&apos;d just be told so.</p>
<p>The message of this blog post is simple - APEX is a great tool, I loved it from the first sight (c&apos;mon, I&apos;m a database developer who was given a chance to play with desktop and mobile web applications using the tools I&apos;m good at), but still, <em>you should</em> know how the tool works under the hood, regardless you use it for a long time, or only going to try the platform. <em>You should</em>, because it enables you to know what to expect from the framework and how to cook it better. The only point here which makes me sad a bit is the fact that there&apos;s <em>very few official guidelines</em> by Oracle on <em>how to cook stuff better</em> in APEX - and we don&apos;t have any choice but sort them out ourselves. Maybe, in a some while, we&apos;ll see more official whitepapers and other sources of recommendations on the matter.</p>
<p>It happened that we are people who work with the most valuable thing of our customers - <em><strong>data</strong></em>, so, don&apos;t let yourself think you can afford not to care about its security.</p>
<p>And remember - <em>with great power comes great responsibility</em>!<br>
<img src="https://dsavenko.me/content/images/2018/06/great-power-great-responsibility-1.jpg" alt="How APEX executes SQL statements without having permissions?" loading="lazy"></p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>