Saturday, June 23, 2018

When do I need to call this method Runtime.getRuntime().addShutdownHook() > Java.lang.Runtime.addShutdownHook(Thread hook) Method > Java Shutdown hook – Runtime.addShutdownHook() > Grails on Groovy: Add a ShutdownHook > Runtime: addShutdownHook(Thread hook)

The java.lang.Runtime.addShutdownHook(Thread hook) method registers a new virtual-machine shutdown hook.The Java virtual machine shuts down in response to two kinds of events

> The program exits normally, when the last non-daemon thread exits or when the exit (equivalently, System.exit) method is invoked

> The virtual machine is terminated in response to a user interrupt, such as typing ^C, or a system-wide event, such as user logoff or system shutdown
Below is a example of how shutdown hook can be used:
package com.pkm;

public class RegisterShutDownHook {
    public static void main(String[] args) {
        System.out.println("PROGRAM STARTED");

        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                System.out.println("SHUTDOWN HOOK FIRED");
            }
        });

        System.out.println("PROGRAM RUNNING");
    }
}
Output of the above code snippet would be like below:
PROGRAM STARTED
PROGRAM RUNNING
SHUTDOWN HOOK FIRED
When you are using Grails application, you can do that various process. Either registering shutdown hook or implementing DisposableBean
Below is the process using shutdown hook
class BootStrap {
    def init = { servletContext ->
        addShutdownHook {
            println("Running Shutdown Hook");
        }
    }

    def destroy = {

    }
}
Or as below, create another class and registering that from resources.groovy like below:
package com.pkm

import org.springframework.context.ApplicationContextAware
import org.springframework.context.ApplicationContext

class ShutdownHook implements ApplicationContextAware {
    void setApplicationContext(ApplicationContext applicationContext) {
        Runtime.runtime.addShutdownHook {
            println("Application context shutting down...")
            applicationContext.close()
            println("Application context shutdown.")
        }
    }
}

// Adding the following block in grails-app/conf/spring/resources.groovy
// Place your Spring DSL code here
beans = {
    myShutdownHook(com.pkm.ShutdownHook)
}
And my last known process is using DisposableBean
package com.pkm

import grails.transaction.Transactional
import org.springframework.beans.factory.DisposableBean

@Transactional
class HomeService implements DisposableBean {
    @Override
    void destroy() throws Exception {
        println("APPLICATION DESTROYED")
    }
}

How to pass parameters / arguments to your PHP script via the command line

If you want to be able to assign variable names for the values being passed in like php script.php -pvalue1, getopt() is the way to do it. Lets look at a different version of the script now >
$val = getopt("p:");
There are some major differences here. First with getopt() you must specify which command line argument you want to retrieve. In the case of this script, it looks for the "-name" argument, that's specified by the "name:" value passed to getopt(). The colon (:) means that the parameter must have a value. If you're used to doing something like "script.php?name=Name&roll=Roll" this is an equivalent for the command line.
php script.php --name=test --roll="03 d"
$val = getopt("name:roll:");

Friday, June 22, 2018

Header only retrieval in php via curl | PHP CURL get content type from URL | PHP CURL retrieve headers information from URL

Response headers can contain valuable information and may help to keep your API responses simpler by separating the actual response data from accessory metadata.
For instance, when querying the API for a list of posts, the response body includes just the content but there are also some other valualbe information sent as headers:
The PHP code for the CURL request would look something like this:
$agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 X-Client-Data: CIa2yQEIpLbJAQjBtskBCKmdygEIqKPKARiSo8oB";
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_FILETIME, true);
curl_setopt($curl, CURLOPT_NOBODY, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_USERAGENT, $agent);
$header = curl_exec($curl);
$info = curl_getinfo($curl);
curl_close($curl);
Which will output as below:
HTTP/1.1 200 OK
Date: Fri, 22 Jun 2018 06:47:27 GMT
Server: Apache/2.4.33 (Win32) OpenSSL/1.0.2n PHP/5.6.35
Last-Modified: Sun, 11 Feb 2018 16:39:41 GMT
ETag: "5ae6f-564f2687e28f8"
Accept-Ranges: bytes
Content-Length: 372335
Content-Type: application/pdf

Array
(
    [url] => http://localhost/text-finder/download.pdf
    [content_type] => application/pdf
    [http_code] => 200
    [header_size] => 265
    [request_size] => 258
    [filetime] => 1518367181
    [ssl_verify_result] => 0
    [redirect_count] => 0
    [total_time] => 0.016
    [namelookup_time] => 0.016
    [connect_time] => 0.016
    [pretransfer_time] => 0.016
    [size_upload] => 0
    [size_download] => 0
    [speed_download] => 0
    [speed_upload] => 0
    [download_content_length] => 372335
    [upload_content_length] => -1
    [starttransfer_time] => 0.016
    [redirect_time] => 0
    [redirect_url] => 
    [primary_ip] => ::1
    [certinfo] => Array
        (
        )

    [primary_port] => 80
    [local_ip] => ::1
    [local_port] => 51877
)


HTTP/1.1 200 OK
Date: Fri, 22 Jun 2018 06:48:11 GMT
Server: Apache/2.4.33 (Win32) OpenSSL/1.0.2n PHP/5.6.35
Last-Modified: Fri, 22 Jun 2018 04:18:28 GMT
ETag: "92-56f3352eebe85"
Accept-Ranges: bytes
Content-Length: 146
Content-Type: text/html

Array
(
    [url] => http://localhost/text-finder/test2.html
    [content_type] => text/html
    [http_code] => 200
    [header_size] => 253
    [request_size] => 256
    [filetime] => 1529641108
    [ssl_verify_result] => 0
    [redirect_count] => 0
    [total_time] => 0.015
    [namelookup_time] => 1.0E-6
    [connect_time] => 0.015
    [pretransfer_time] => 0.015
    [size_upload] => 0
    [size_download] => 0
    [speed_download] => 0
    [speed_upload] => 0
    [download_content_length] => 146
    [upload_content_length] => -1
    [starttransfer_time] => 0.015
    [redirect_time] => 0
    [redirect_url] => 
    [primary_ip] => ::1
    [certinfo] => Array
        (
        )

    [primary_port] => 80
    [local_ip] => ::1
    [local_port] => 51944
)

jQuery AJAX fetch only headers and decide wheather to get the content | Use jQuery to send a HEAD request with AJAX and get the size of a file | Getting response headers data from an AJAX request with javascript

Response headers can contain valuable information and may help to keep your API responses simpler by separating the actual response data from accessory metadata.
For instance, when querying the API for a list of posts, the response body includes just the content but there are also some other valualbe information sent as headers:
The jQuery code for the AJAX request would look something like this:
$.ajax({
    type: 'HEAD',
    url: 'http://example.com/api.php',
    complete: function (xhr) {
        var contentLength = xhr.getResponseHeader('Content-Length');
        // OR YOU CAN SEE ALL INFORMATION USING
        var headers = xhr.getAllResponseHeaders();
    }
});
Which will output as below:
http://localhost/text-finder/download.pdf >> application/pdf
date: Fri, 22 Jun 2018 06:35:52 GMT
last-modified: Sun, 11 Feb 2018 16:39:41 GMT
server: Apache/2.4.33 (Win32) OpenSSL/1.0.2n PHP/5.6.35
etag: "5ae6f-564f2687e28f8"
content-type: application/pdf
connection: Keep-Alive
accept-ranges: bytes
keep-alive: timeout=5, max=100
content-length: 372335



http://localhost/text-finder/test2.html/forum >> text/html; charset=utf-8
date: Fri, 22 Jun 2018 06:35:52 GMT
server: Apache/2.4.33 (Win32) OpenSSL/1.0.2n PHP/5.6.35
vary: accept-language,accept-charset
content-language: en
connection: Keep-Alive
accept-ranges: bytes
content-type: text/html; charset=utf-8
keep-alive: timeout=5, max=97

Get URL and URL Parts in JavaScript | Get an Absolute URL with JavaScript | How to get the exact href value only without their domain | How to check if any Link is for some specific domain | Link attribute HOST > PATHNAME > Filter links by domain | host name

I'm having trouble in getting the exact value of href only
It's a little known (maybe) fact that most browsers convert Anchor node elements into Location objects as well. So you can access all parts available to Location too;
Suppose consider below html code:
<body>
HELLO '59 IS PRESENT <a href="test2.html">HI</a>
<a href="https://stackoverflow.com/questions/domain">Another LINK</a>
</body>
In above html, two link exists, one for self domain and another for stackoverflow, now if I look through them, below output would be generated:
PROTOCOL=http:, HOST=localhost, PORT=
LINK PATH=/test2.html

PROTOCOL=https:, HOST=stackoverflow.com, PORT=
LINK PATH=/questions/domain

A regex to match a substring that isn't followed by a certain other substring > Find Any Word Not Followed by a Specific Word > Regex Negative Lookbehind

The problem is I need to match a word say "match" in a string. It is very easy using the expression /(match)/gi
Above expression is very easy. Now original problem is need to match word "match" not starting with any other word like 10 or 20 or any word. To do so we need to use negative lookbehind. Below is a complete regex that will match word match but not start with 10 or 20 or '.

/(?<!(10|20))(?<!')(match)/gi
Meaning of the above regex is will match a word match but not followed by 10 or 20 or '

Output of this regex is as below

Explanation of this regex:


Sunday, June 17, 2018

Grails on Groovy > Invalidate Session > Error Handling - Session already invalidated

Problem is that in my grails application, when I invalidating the existing http session using session.invalidate() and creating a new session using request.getSession(true) is working fine for some time.
But this new session is not getting reflected everywhere in grails application. Due to this I do get 'Session already invalidated'.
I don't want to do request.getSession() everywhere, actually I would not get notified when this required or not. I am just using 'session'.
Grails holds the reference to a session object and every time you ask it for a session it returns the same reference.. so if you invalidate a session and then ask for the session it will return the same invalidated session, and cause 'session already invalidated' exception..
Execute following line Just after you do session.invalidate
import org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequest

session.invalidate()
GrailsWebRequest.lookup(request).session = null

Grails on Groovy > Grails Filter to Redirect HTTP to HTTPS > Redirecting WWW to Root with Grails > Grails Append Something to URL before Redirect > URL Modification On Grails Filters

The problem is need to modify http to https as well as add www to domain name if not exists. To do so have to modify in our Grails Filters.

For Grails applications, a filter can be used to improved security by redirecting traffic from regular HTTP to encrypted HTTPS. The convention is that filters are written in Groovy using filenames ending in Filters, and the files go into the grails-app/conf folder.
Redirecting from HTTP to HTTPS provides a better user experience than simply blocking HTTP requests, as redirecting seamlessly forwards users to the web pages they expect to see.
The example below shows the redirect code
package com.pkm

import grails.util.Environment

import javax.servlet.http.HttpServletRequest

class SecurityFilters {
    String getDomainName(HttpServletRequest request) {
        return request.getRequestURL().substring(0, request.getRequestURL().indexOf("/", 8)) + request.contextPath
    }
    String getFullRequestURI(HttpServletRequest request) {
        String query = request.getQueryString()
        String request_uri = request.getAttribute("javax.servlet.forward.request_uri")
        if (request_uri == null) {
            return request.getRequestURL().toString().substring(0, request.getRequestURL().toString().length() - 1) + (query ? "?$query".toString() : "")
        }
        return request.getRequestURL().substring(0,request.getRequestURL().indexOf("/", 8)) + request_uri + (query ? "?$query".toString() : "")
    }

    def filters = {
        filter1(uri: "/**") {
            before = {
                Boolean isSecure = request.isSecure(), doRedirect = false
                String domain = getDomainName(request)
                String url = getFullRequestURI(request)

                println("SECURE=${isSecure.toString().capitalize()}" +
                        "\n\t >DOMAIN=${domain}" +
                        "\n\t\t>URL=${url}")

                /*if (!request.getServerName().toLowerCase().startsWith("www")) {
                    doRedirect = true
                    url = url.substring(0, url.indexOf("//")) + "//www." + url.substring(url.indexOf("//") + 2)
                }*/
                if (!request.isSecure() && !Environment.isDevelopmentMode()) {
                    doRedirect = true
                    url = "https://" + url.substring(url.indexOf("//") + 2)
                }
                if (!url.toLowerCase().endsWith("redirected=true-2")) {
                    doRedirect = true
                    url = url + (url.contains("?") ? "&redirected=true-2" : "?redirected=true-2")
                }
                if (doRedirect && request.isGet()) {
                    response.setStatus(302)
                    response.setHeader("Location", url)
                    response.flushBuffer()
                    return false
                }
            }
            after = { Map model ->

            }
            afterView = { Exception e ->

            }
        }
    }
}
If your server listens for https requests (or any requests on ports other than 80), you can add checks using the same format, replacing http and port 80 with the appropriate values. You can also redirect from any subdomain you want to the root site (or another subdomain), by simply swapping www with your subdomain.
And output would be like below. First request forwarded to second URL with additional parameters.
SECURE=False
  >DOMAIN=http://localhost:3346/CONTEXT_PATH
  >URL=http://localhost:3346/CONTEXT_PATH/home/index
SECURE=False
  >DOMAIN=http://localhost:3346/CONTEXT_PATH
  >URL=http://localhost:3346/CONTEXT_PATH/home/index?redirected=true-2

Grails on Groovy > Transform Collection / List to a Map with collectEntries > Get Map From Array List > Convert List To Corresponding Map Entity

Since Groovy 1.7.9 we can use the collectEntries method for a Collection to get a Map. We use a closure to transform the elements in the collection to a map entry. And the end result is a Map with all the map entries.
List<String> words = ["Grails", "On", "Groovy"]
Map map = words.collectEntries {
    [ (it): it.contains("G") ]
}
println(map)
Which will output as below:
[Grails:true, On:false, Groovy:true]

Grails on Groovy > Transactions > Transaction Block > Read Only Transactions > With New Transaction > Transactions With Read Only Stage

We all know that Grails services are transactional by default. When you create a service you see that there is a annotation @Transactional exists over class name. Transactional means all transactions will persist on database or no one will persist.
We can set transactions read only mode if we wish in a transactional block if we need. Below is a code snippet:
package com.pkm

import grails.transaction.Transactional
import org.springframework.transaction.TransactionStatus

@Transactional
class HomeService {
    TransactionStatus transactionStatus

    void callMe() {
        Table1 table1 = Table1.last()
        new Table2(table1: table1, subject: "S3", score: 3D).save()
        transactionStatus.setRollbackOnly()

        Table2.withNewTransaction { TransactionStatus tx ->
            table1 = Table1.last()
            new Table2(table1: table1, subject: "S4", score: 3D).save()
            tx.setRollbackOnly()
        }
    }
}
In above code block, we inject a bean named transactionStatus which actually maintain if a service definition is read only or not. We can set it to read only mode using transactionStatus.setRollbackOnly(). We can do the same job for when we do anything in withNewTransaction block. You can see there is another transactionStatus occur for that new region. So you can also set it to read only mode.

Grails on Groovy > Create Criteria > Create Alias > Grails Create Alias > Create Alias With Additional Criteria

We frequently use createAlias to create alias between entity. It actually create connection based on foreign key and primary key between two entities. Below is a example how we create alias between entities:
Table1.createCriteria().list {
    createAlias("scores", "scores")
}
Which will produce SQL like below where we can see that foreign key of table1 create a link with table2_child based on primary key id as following:
select ... from table1 this_ inner join table2_child scores1_ on (this_.id=scores1_.table1_id)
But if we need more on add to filter when joining them, yeah, we can do that. We can add extra conditions to on part of that SQL. To do so, first need to create a groovy file like below:
package com.pkm

import org.hibernate.Criteria
import org.hibernate.HibernateException
import org.hibernate.criterion.CriteriaQuery
import org.hibernate.criterion.Criterion
import org.hibernate.engine.spi.TypedValue

class CustomSqlJoin implements Criterion {
    private Double score = null;

    CustomSqlJoin(Double score) {
        this.score = score
    }

    //Use Example > createAlias("scores", "scores", JoinType.INNER_JOIN, new CustomSqlJoin(2D))
    @Override
    String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
        String alias = criteriaQuery.getSQLAlias(criteria)
        return "${alias}.score > ${this.score}"
    }

    @Override
    TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
        return new TypedValue[0]
    }
}
And you can use the above custom condition using as below example:
Table1.createCriteria().list {
    createAlias("scores", "scores", JoinType.INNER_JOIN, new CustomSqlJoin(2D))
    "ne" "id", System.currentTimeMillis()
}
Which will generate below SQL
select ... from table1 this_ inner join table2_child scores1_
on this_.id=scores1_.table1_id and ( scores1_.score > 2.0 )
where this_.id<>?

Saturday, June 16, 2018

How to handle HTTP 403 forbidden error in Java

Sometimes when trying to connect to a web service using a java client, we may face a 403 forbidden HTTP response code, it is workable but in some reason the service is accessible normally from web browsers.
The HTTP 403 forbidden error doesn’t necessarily occur due to missing authentication attributes or invalid authentication, some web services would only authorize web browsers or some specific clients to access them, while they deny any requests coming from third-party clients. And we are going to handle this situation.
This problem is normally resolved by imitating the web browser request so that the web service deals with the java client as if it was a web browser.
Below is a typical request logged from a browser:
GET /feeds HTTP/1.1
Host: publicservice.com:443
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: OGP=-4061129:; SID=FAYIU7tO....
Referer: https://clients5.google.com/pagead/drt/dn/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
X-Client-Data: CIa2yQEIpLbJAQjBtskBCKmdygEIqKPKARiSo8oB
As noticed, the “User-Agent” header specifies the name and the type of the client which is trying to access the service, so in order to imitate the web browser we need to add this header to our request. Following is how to add it using HttpUrlConnection:
String url = "https://xxx.com/yyy";
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");

Grails on Groovy > Bind Metamethod of Hibernate Criteria Builder > Create Custom Function / Custom Method Under HibernateCriteriaBuilder > HibernateCriteriaBuilder Meta Method

To bind custom method with hibernate criteria builder you first need to create a groovy file named HibernateCriteriaListener like below:
package com.pkm

import grails.util.Holders
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.grails.datastore.mapping.core.Datastore
import org.grails.datastore.mapping.engine.event.*
import org.springframework.context.ApplicationEvent
import grails.orm.HibernateCriteriaBuilder

class HibernateCriteriaListener extends AbstractPersistenceEventListener {
    protected HibernateCriteriaListener(Datastore datastore) {
        super(datastore)

        HibernateCriteriaBuilder.metaClass.myMethod = { info, value = null ->
            println("NAME=${info} VALUE=${value}")
        }
    }

    @Override
    protected void onPersistenceEvent(AbstractPersistenceEvent event) {

    }

    @Override
    boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
        return false
    }

    static void initialize(GrailsApplication application) {
        application.mainContext.eventTriggeringInterceptor.datastores.each { k, datastore ->
            Holders.applicationContext.addApplicationListener(new HibernateCriteriaListener(datastore))
        }
    }
}
Now we need to register the above functionality from BootStrap, so you need to do following:
import com.pkm.HibernateCriteriaListener
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes
import org.hibernate.dialect.function.SQLFunctionTemplate
import org.springframework.context.ApplicationContext

class BootStrap {
    def init = { servletContext ->
        ApplicationContext applicationContext = servletContext.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT)
        GrailsApplication application = (GrailsApplication) applicationContext.getBean("grailsApplication")

        /* REGISTER HIBERNATE EVENT LISTENER */
        HibernateCriteriaListener.initialize(application)
    }

    def destroy = {

    }
}
And finally you can use custom criteria method using below sample:
Table1.createCriteria().list {
    delegate.myMethod("name", "value")
}

jQuery UI Dialog > Disable Auto Focus > Prevent jQuery UI Dialog From Setting Focus To First Textbox > Disable Auto Focus Of First Input Box > Prevent Auto Scroll To Top Of Dialog Box

There are several problems related to auto focus of jQuery UI Dialog. Some of them are below:

> Opening jQuery Date picker automatically on focus of dialog, because of first element get auto focused.

> If dialog has scrollbar visible, its auto scrolled to top to auto focus first element of dialog.
So to disable auto focus input element inside UI dialog, add the following code before you call dialog. This will clear out the autofocus code.
$.ui.dialog.prototype._focusTabbable = function(){};

BEGENING OF CSS > TABLE WITH SCROLLBAR AND FIXED HEADER POSITION > How to make Scrollable Table with fixed headers using CSS


Jsfiddle link to check

<div class="fixed-table-container">
      <div class="header-background"> </div>
      <div class="fixed-table-container-inner">
        <table cellspacing="0">
          <thead>
            <tr>
              <th class="first">
                <div class="th-inner">First</div>
              </th>
              <th>
                <div class="th-inner">Second</div>
              </th>
              <th>
                <div class="th-inner">Third</div>
              </th>
              <th>
                <div class="th-inner">Fourth</div>
              </th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>First</td>
              <td>First</td>
              <td>First</td>
              <td>First</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Third</td>
              <td>Fourth</td>
            </tr>
            <tr>
              <td>First</td>
              <td>Second</td>
              <td>Fourth</td>
              <td>Third</td>
            </tr>
            <tr>
              <td>Last</td>
              <td>Last</td>
              <td>Last</td>
              <td>Last</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>

.fixed-table-container {
    width: 70%;
    height: 200px;
    border: 1px solid black;
    margin: 10px auto;
    background-color: white;
    position: relative;
    padding-top: 30px;
}
.fixed-table-container .header-background {
    background-color: #D5ECFF;
    height: 30px;
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    border-bottom: 1px solid black;
}
.fixed-table-container .fixed-table-container-inner {
    overflow-x: hidden;
    overflow-y: auto;
    height: 100%;
}
.fixed-table-container table {
    background-color: white;
    width: 100%;
    overflow-x: hidden;
    overflow-y: auto;
}
.fixed-table-container th {
    padding: 0 5px;
    text-align: left;
}
.fixed-table-container .first .th-inner {
    border-left: none;
    padding-left: 6px;
}

.fixed-table-container .th-inner {
    position: absolute;
    top: 0;
    line-height: 30px;
    text-align: left;
    border-left: 1px solid black;
    padding-left: 5px;
    margin-left: -5px;
}
.fixed-table-container td + td {
    border-left: 1px solid #ccc;
}
.fixed-table-container td {
    border-bottom: 1px solid #ccc;
    padding: 5px;
    text-align: left;
}