NodeJS. Running as a service using forever on Ubuntu Server

После реализации nodejs приложения возникла задача его демонизации. Один из способов - использование модуля, разработанного nodejitsu - forever. Для установки данного модуля воспользуйтесь командой (Ubuntu Server):
 
sudo npm -g install forever

После этого можно запустить выполнение вашего nodejs приложения с помощью forever в виде самостоятельного сервиса:

forever start /path/to/directory/containing_your_app/your_app_name.js

forever будет отслеживать за падениями приложения и пытаться его поднять. В случае же рестарта системы, запуска вашего сервиса не произойдет. Здесь нужно реализовать автозагрузку на уровне системы. Создайте скрипт запуска в директории /etc/init.d. Ниже приведен пример скрипта автозапуска /etc/init.d/your_app_name:

#!/bin/bash
#
# description: your_app_name (node) service
#
# use commands:
# service your_app_name <command (start|stop|status|restart)>

SERVICE_NAME=your_app_name
SOURCE_DIR=/path/to/directory/containing_your_app
SOURCE_NAME=your_app_name.js
SOURCE_PATH=$SOURCE_DIR/$SOURCE_NAME
LOG_DIR=$SOURCE_DIR/forever
USER=tester

pidfile=/home/$USER/.forever/pids/$SERVICE_NAME.pid
logfile=$LOG_DIR/forever.log
outfile=$LOG_DIR/output.log
errfile=$LOG_DIR/error.log

case "$1" in
    start)
        forever start -l $logfile -o $outfile -e $errfile --pidFile $pidfile -a $SOURCE_PATH
        ;;
    stop)
        forever stop --pidFile $pidfile $SOURCE_PATH
        ;;
    status)
        forever list
        ;;
    restart)
        forever restart -l $logfile -o $outfile -e $errfile --pidFile $pidfile -a $SOURCE_PATH
        ;;
    *)
        echo "Usage: $SERVICE_NAME {start|stop|status|restart}"
        exit 1
        ;;
esac
exit 0

Далее выдайте права:

sudo chmod 755 /etc/init.d/your_app_name

и пропишите его в автозагрузку:

sudo update-rc.d your_app_name defaults

Oracle. ORA-21561: OID generation failed

После установки клиента при попытке коннекта выбрасывается следующая ошибка 

ORA-21561: OID generation failed

Для устранения необходимо добавить имя хоста в файл hosts:

$ hostname
$ nano /etc/hosts
 > 127.0.0.1      localhost      hostname

PLSQL. JSON validator (string validation)

Как известно, JSON формат представляет собой альтернативу XML формату. В настоящее время данный формат получил довольно широкое распространение. Так же и Oracle не отстает от времени и в версии 12.1.0.2 была добавлена нативная поддержка JSON формата. Если Вы работаете на более поздних версиях, то можно добавить собственные реализации для поддержки формата. Есть уже и готовые решения, например можно воспользоваться следующим ресурсом - https://github.com/pljson/pljson.

В моем же случае все немного проще. На стороне сервера по триггеру запускался обработчик, который формировал данные в строковом виде в JSON формате и после отправлял POST запросом на определенный ресурс. Перед отправкой необходимо было убедиться в валидности JSON формата. Так возникла потребность в организации такого рода JSON валидатора. За основу был взят JSON парсер, написанный Дугласом Крокфордом на javascript, с которым можно ознакомиться по ссылке - https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js. Данный парсер предоставляет альтернативу встроенному парсеру JSON.parse в случае отсутствия поддержки оного в Вашем браузере. Ниже представлена реализация тела пакета данного валидатора. В спецификации внешний доступ предоставлен трем методам:

function unsafety_validate(source in varchar2) return boolean;
function safety_validate(source in varchar2) return boolean;
function safety_validate(source in varchar2, errmsg in out varchar2) return boolean;

Префиксы - safety и unsafety - указывают на возможность генерирования возникающих в ходе валидации исключений. С полным описанием пакета можно ознакомиться по ссылке - https://github.com/mozg1984/PL-JSON-VALIDATOR.


create or replace package body json_validator is

  /* @private
   * type for instantiate keys storages
   */
  type array_t is table of varchar2(1000);

  atCh integer; -- the index of the current character
  ch char;      -- the current character
  text varchar2(32767);

  /* @private
   * call error when something is wrong
   */
  procedure error(m in varchar2 default '')
  is
    errmsg varchar2(32767);
  begin
    errmsg := 'name: SyntaxError, ';
    errmsg := errmsg || 'message: ' || m || ', ';
    errmsg := errmsg || 'at: ' || atCh || ', ';
    errmsg := errmsg || 'text: ' || text;
    raise_application_error(-20000, errmsg);
  end;

  /* @private
   * escape sequence
   */
  function escapee(ch in char default '') return varchar2
  is
  begin
    case ch
      when '"' then return '"';
      when '\' then return '\';
      when '/' then return '/';
      when 'b' then return '\b';
      when 'f' then return '\f';
      when 'n' then return '\n';
      when 'r' then return '\r';
      when 't' then return '\t';
      else return null;
    end case;
  end;

  /* @private
   * add key to keys storage
   */
  procedure add_key(p_array in out nocopy array_t, p_key in varchar2)
  is
  begin
    p_array.extend();
    p_array(p_array.count()) := p_key;
    exception
      when collection_is_null then 
        p_array := array_t(p_key);
  end;
  
  /* @private
   * check duplicate keys in keys storage
   */
  function has_key(p_array in out nocopy array_t, p_key in varchar2) return boolean
  is
    is_equal boolean := false;
  begin
    for i in p_array.first..p_array.last loop
      if p_array(i) = p_key then
        is_equal := true;
      end if; 
    end loop;
    
    return is_equal;
    exception
      when collection_is_null then 
        return is_equal;
  end;

  /* @private
   *  get char from string by index position
   */
  function charAt(str in varchar2, pos in integer) return char
  is
  begin
    return substr(str, pos, 1);
  end;

  /* @private
   * check if given number
   */
  function isNumber(num in varchar2) return boolean
  is
  begin
    case regexp_like(num, '^\d*\.?\d+$')
      when true then 
        return true;
      else 
        return false; 
    end case;
  end;
  
  /* @private
   * check if given character
   */
  function exist(ch in varchar2 default '') return boolean
  is
  begin
    return ch is not null; 
  end;

  /* @private
   * convert hex number (character) to decimal number
   */
  function hex2dec(ch in char) return number
  is
  begin
    return to_number(ch, 'x');
    exception
      when value_error then
        return null;
  end;
  
  /* @private
   * convert hex code to unicode character
   */
  function getUnicodeChar(hexcode in varchar2) return varchar2
  is
  begin
    return trim(unistr('\' || hexcode || ' '));
    exception
      when others then
        return ''; 
  end;

  /* @private
   * get the next character
   */
  function nextChar(c in char default '') return char
  is
  begin
    -- if a c parameter is provided, verify that it matches the current character
    if (exist(c) and c != ch) then
      error('Expected ''' || c || ''' instead of ''' || ch || '''');
    end if;

    -- when there are no more characters, return the empty string
    ch := charAt(text, atCh);
    atCh := atCh + 1;
    return ch;
  end;
  
  /* @private
   * move current char to next
   */
  procedure nextChar(c in char default '')
  is
  begin
    -- if a c parameter is provided, verify that it matches the current character
    if (exist(c) and c != ch) then
      error('Expected ''' || c || ''' instead of ''' || ch || '''');
    end if;

    ch := charAt(text, atCh);
    atCh := atCh + 1;
  end;

  /* @private
   * forward declaration of common validate function 
   */
  function validate return boolean;

  /* @private
   * validate a number value
   */
  function validateNumber return boolean
  is
    l_number varchar2(32767) := '';
  begin
    if (ch = '-') then
      l_number := '-';
      nextChar('-');
    end if;

    while (ch >= '0' and ch <= '9') loop
      l_number := l_number || ch;
      nextChar();
    end loop;

    if (ch = '.') then
      l_number := l_number || '.';
      while (exist(nextChar()) and ch >= '0' and ch <= '9') loop
        l_number := l_number || ch;
      end loop;
    end if;

    if (ch = 'e' or ch = 'E') then
      l_number := l_number || ch;
      nextChar();

      if (ch = '-' or ch = '+') then
        l_number := l_number || ch;
        nextChar();
      end if;

      while (ch >= '0' and ch <= '9') loop
        l_number := l_number || ch;
        nextChar();
      end loop;
    end if;

    if (not(isNumber(l_number))) then
      error('Bad number');
    else
      return true;
    end if;
  end;

  /* @private
   * validate a string value
   */
  function validateString return boolean
  is
    decnum number;
  begin
    -- when parsing for string values, we must look for " and \ characters
    if (ch = '"') then
      while (exist(nextChar())) loop
        if (ch = '"') then
          nextChar();
          return true;
        end if;

        if (ch = '\') then
          nextChar();
          if (ch = 'u') then
            for i in 1..4 loop
              decnum := hex2dec(nextChar());              
              if (not isNumber(decnum)) then
                error('Malformed Unicode character escape sequence');
              end if;
            end loop;
          elsif (escapee(ch) is not null) then
            null;
          else
            exit;
          end if;
        end if;
      end loop;
    end if;
    error('Bad string');
  end;
  
  /* @private
   * get string key value (for checking duplicate keys in object value)
   */
  function stringKey return varchar2
  is
    l_string varchar2(32767) := '';
    decnum number;
    uffff varchar2(32767);
  begin
    -- when parsing for string values, we must look for " and \ characters
    if (ch = '"') then
      while (exist(nextChar())) loop
        if (ch = '"') then
          nextChar();
          return l_string;
        end if;

        if (ch = '\') then
          nextChar();
          if (ch = 'u') then
            uffff := '';
            for i in 1..4 loop
              decnum := hex2dec(nextChar());
              if (not isNumber(decnum)) then
                error('Malformed Unicode character escape sequence');
              end if;
              uffff := uffff || ch;
            end loop;
            l_string := l_string || getUnicodeChar(uffff);
          elsif (escapee(ch) is not null) then
            l_string := l_string || escapee(ch);
          else
            exit;
          end if;
        else
          l_string := l_string || ch;
        end if;
      end loop;
    end if;
    error('Bad string');
  end;
  
  /* @private
   * skip whitespace
   */
  procedure white 
  is
  begin
    while (exist(ch) and ch <= ' ') loop
      nextChar();
    end loop;
  end;

  /* @private
   * validate a word (true, false or null)
   */
  function validateWord return boolean
  is
  begin
    case ch
      when 't' then
        nextChar('t');
        nextChar('r');
        nextChar('u');
        nextChar('e');
        return true;
      when 'f' then
        nextChar('f');
        nextChar('a');
        nextChar('l');
        nextChar('s');
        nextChar('e');
        return true;
      when 'n' then
        nextChar('n');
        nextChar('u');
        nextChar('l');
        nextChar('l');
        return true;
      else
        error('Unexpected ''' || ch || '''');
    end case; 
  end;
  
  /* @private
   * validate an array value
   */
  function validateArray return boolean
  is
    l_result boolean := true;
  begin
    if (ch = '[') then
      nextChar('[');
      white();
      if (ch = ']') then
        nextChar(']');
        return l_result; -- empty array
      end if;
      
      while (exist(ch)) loop
        l_result := validate();
        white();
        if (ch = ']') then
          nextChar(']');
          return l_result;
        end if;
        nextChar(',');
        white();
      end loop;
    end if;
    error('Bad array');
  end;

  /* @private
   * validate an object value
   */
  function validateObject return boolean
  is
    l_result boolean := true;
    l_key varchar2(1000);
    l_keys array_t;
  begin
    if (ch = '{') then
      nextChar('{');
      white();
      if (ch = '}') then
        nextChar('}');
        return l_result; -- empty object
      end if;
      
      while (exist(ch)) loop
        l_key := stringKey();
        white();
        nextChar(':');
        
        if (has_key(l_keys, l_key)) then
          error('Duplicate key "' || l_key || '"');
        end if;
        
        add_key(l_keys, l_key);
        l_result := validate();
        white();
        
        if (ch = '}') then
          nextChar('}');
          return l_result;
        end if;
        
        nextChar(',');
        white();
      end loop;
    end if;
    error('Bad object');
  end;

  /* @public
   * validate a JSON value. 
   * It could be an object, an array, a string, a number, or a word.
   */
  function validate return boolean
  is
  begin
    white();
    case ch
      when '{' then
        return validateObject();
      when '[' then
        return validateArray();
      when '"' then
        return validateString();
      when '-' then
        return validateNumber();
      else
        return case
          when (ch >= '0' and ch <= '9') then validateNumber() else validateWord() end; 
    end case;
  end;
  
  /* @public
   * unsafety validate JSON string (throw exception ora-20000)
   */
  function unsafety_validate(source in varchar2) return boolean
  is
    l_result boolean;
  begin
    text := source;
    atCh := 1;
    ch := ' ';
    
    l_result := validate();
    white();
    
    if (exist(ch)) then
      error('Syntax error');
    end if;
    
    return l_result; 
  end;
  
  /* @public
   * safety validate JSON string (catch all exceptions)
   */
  function safety_validate(source in varchar2) return boolean
  is
    l_result boolean;
  begin
    text := source;
    atCh := 1;
    ch := ' ';
    
    l_result := validate();
    white();
    
    if (exist(ch)) then
      error('Syntax error');
    end if;
    
    return l_result;
    
    exception
      when others then
        return false;
  end;
  
  /* @public
   * safety validate JSON string (catch all exceptions with error message) 
   */
  function safety_validate(source in varchar2,
                           errmsg in out varchar2) return boolean
  is
    l_result boolean;
  begin
    text := source;
    atCh := 1;
    ch := ' ';
    
    l_result := validate();
    white();
    
    if (exist(ch)) then
      error('Syntax error');
    end if;
    
    return l_result;
    
    exception
      when others then
        errmsg := sqlerrm;
        return false;
  end;

begin
  -- init
  null;
end json_validator;


Пример использования данного пакета


declare
  json_string varchar2(1000);
  errmsg varchar2(32767);
begin
  
  json_string := '{"number": 123456, "text": "active", "array": [], "object": {}}';
  
  if json_validator.safety_validate(json_string, errmsg) then
    dbms_output.put_line('JSON is valid');
  else
    dbms_output.put_line('JSON is not valid');
    dbms_output.put_line(errmsg);
  end if;
  
end;

Javascript. Unicode char by hex code

Ниже представлена реализация получения unicode-символа по шестнадцатеричному коду в методе getUnicodeChar утилитного объекта util.

// utility object
var util = {   
    // modulo
    mod: function (a, b) {
        return a % b;
    },

    // integer division
    div: function (a, b) {
        return (a - a % b) / b;
    },

    // convert hex to decimal number
    hex2dec: function (hex) {
        hex = '' + hex;
       
        var result = 0,
              dec, i, len;
       
        for (i = 0, len = hex.length; i < len; i++) {
            dec = parseInt(hex.charAt(i), 16);
            if (!isFinite(dec)) {
                break;
            }
            result = result * 16 + dec;
        }
       
        return result;
    },

    // convert decimal number to hex
    dec2hex: function (number) {
        number = +number;
       
        var dec = 0,
              result = '',
              self = this;
       
        while ((dec = self.mod(number, 16)) > 0) {
            result = dec.toString(16) + result;
            number = self.div(number, 16);
        }
       
        return result;
    },
   
    // get unicode char
    getUnicodeChar: function (unicode) {
        var unicodeChar = '',
              uffff = 0,
              self = this;
       
        if (unicode.charAt(0) === '\\' && unicode.charAt(1) === 'u') {
            uffff = self.hex2dec(unicode.substr(2));
        }
       
        return unicodeChar + String.fromCharCode(uffff);
    }
};


console.log(util.getUnicodeChar('\\u041a')); // К
console.log(util.hex2dec('041a')); // 1050
console.log(util.dec2hex(1050));  // 41a


Стоит отметить, что методы hex2dec и dec2hex можно реализовать и более простым способом.

// convert hex to decimal number
function hex2dec(hex) {
    return parseInt(hex, 16);
}

// convert decimal number to hex
function dec2hex(dec) {
    return dec.toString(16);
}


console.log(hex2dec('041a')); // 1050
console.log(dec2hex(1050));   // 41a

PLSQL. Object type reflection

Возможно ли просматривать начинку объектных типов независимо от их типа. Для этого в Oracle есть поддержка таких универсальных типов как ANYTYPE, ANYDATA и ANYDATASET. Ниже представлен пример рефлексии объектных типов, с элементами типа VARCHAR2, NUMBER и DATE, при желании список поддерживаемых типов можно расширить (см. документацию).

-- sql
create or replace type order_t as object (
   order_id number,
   order_date date,
   description varchar2(1000)
)

-- pl/sql
declare
  v_order order_t := order_t(123456, sysdate, 'Description text');
  v_anydata anydata;

  -- print object information (reflection)
  procedure objectInfo(p_object in out anydata) -- out for piecewise
  is
    l_typecode pls_integer;
    l_anytype anytype;
    l_result pls_integer;
    l_varchar2 varchar2(32767);
    l_number number;
    l_date date;
    
    -- type-level metadata
    type typeinfo_r is record ( 
      prec        pls_integer
     ,scale       pls_integer
     ,len         pls_integer
     ,csid        pls_integer
     ,csfrm       pls_integer
     ,schema_name varchar2(30)
     ,type_name   varchar2(30)
     ,version     varchar2(30)
     ,count       pls_integer
    );
    typeinfo typeinfo_r;

    -- attribute-level metadata
    type attrinfo_r is record (
      prec           pls_integer
     ,scale          pls_integer
     ,len            pls_integer
     ,csid           pls_integer
     ,csfrm          pls_integer
     ,attr_elt_type  anytype
     ,aname          varchar2(32767)
    );
    attrinfo attrinfo_r;
      
  begin
    -- get information for the ANYDATA instance
    l_typecode := p_object.getType(l_anytype);
    dbms_output.put_line('Typecode: ' || l_typecode);

    if l_typecode = dbms_types.typecode_object then -- if given object
      p_object.piecewise();
      
      -- get type-level metadata
      l_typecode := l_anytype.GetInfo(typeinfo.prec,
                                      typeinfo.scale,
                                      typeinfo.len,
                                      typeinfo.csid,
                                      typeinfo.csfrm,
                                      typeinfo.schema_name,
                                      typeinfo.type_name,
                                      typeinfo.version,
                                      typeinfo.count);
      
      dbms_output.put_line('-------------------------------------');
      dbms_output.put_line('Typename: ' || typeinfo.type_name);
      dbms_output.put_line('Attributes: ' || typeinfo.count);
      
      for i in 1..typeinfo.count loop -- loop by atrributes 
        -- get attribute-level metadata
        l_typecode := l_anytype.getAttrElemInfo(i,
                                                attrinfo.prec,
                                                attrinfo.scale,
                                                attrinfo.len,
                                                attrinfo.csid,
                                                attrinfo.csfrm,
                                                attrinfo.attr_elt_type,
                                                attrinfo.aname);
        
        dbms_output.put_line('-------------------------------------');
        dbms_output.put_line('Attribute: ' || attrinfo.aname);
        dbms_output.put_line('Typecode: ' || l_typecode);
        dbms_output.put_line('Length: ' || nvl(attrinfo.len, attrinfo.prec));
           
        if l_typecode = dbms_types.typecode_varchar2 then -- varchar2
          l_result := p_object.getVarchar2(c => l_varchar2);
          dbms_output.put_line('Value: ' || l_varchar2);
          
        elsif l_typecode = dbms_types.typecode_number then -- number
          l_result := p_object.getNumber(num => l_number);
          dbms_output.put_line('Value: ' || l_number);
          
        elsif l_typecode = dbms_types.typecode_date then -- date
          l_result := p_object.getDate(dat => l_date);
          dbms_output.put_line('Value: ' || to_char(l_date, 'dd.mm.yyyy'));
          
        else
          raise_application_error(-20000, 'Unexpected ' || i || 'st attribute typecode: ' || l_typecode);
        end if;
          
      end loop;
    end if;      
  end;

begin
  
  v_anydata := anydata.convertObject(v_order);
  objectInfo(v_anydata);

end;



Typecode: 108
-------------------------------------
Typename: ORDER_T
Attributes: 3
-------------------------------------
Attribute: ORDER_ID
Typecode: 2
Length: 0
Value: 123456
-------------------------------------
Attribute: ORDER_DATE
Typecode: 12
Length: 
Value: 15.10.2015
-------------------------------------
Attribute: DESCRIPTION
Typecode: 9
Length: 1000
Value: Description text