понедельник, 16 июля 2018 г.

Взаимодействие между приложениями и Watch Service

Взаимодействие между разными приложениями Java можно сделать с помощью файловой системы, базы данных и JMS. Рассмотрим случай ФС - в Java SE 7 появился новый и очень интересный механизм отслеживания изменений файловой системы в заданном каталоге - так называемый Watch Service, позволяющий остлеживать события создания / удаления / изменения файлов в реальном времени. То есть вы можете например создать каталоги /tmp/tasks/task1, /tmp/tasks/task2 и тогда к примеру одно приложение будет вносить в эти каталоги какие-либо изменения - а другое приложение будет отслеживать эти изменения в real-time и в зависимости от каталога и характера изменений запускать соответствующие задачи. Вот метод из десктопного проекта с использованием Spring, который работает в отдельном потоке от основного и отслеживает изменения в каталоге /tmp/tasks/task1 и когда вы руками (вместо другого приложения:)) кладете / удаляете файл в /tmp/tasks/task1 - выводится соответствующее сообщение.
// async-метод для получения задач
@Async // СКАНИРУЕМ НА НОВОЕ ЗАДАНИЕ в отдельном потоке
public void receiveTaskChecker() {
  try {
    WatchService watcher = FileSystems.getDefault().newWatchService();
   // сканируем на изменения соотв каталог для задачи номер 1:
    Path dir = FileSystems.getDefault().getPath("/tmp/tasks/task1");
    WatchKey key = dir.register(watcher,    
             StandardWatchEventKinds.ENTRY_CREATE, 
             StandardWatchEventKinds.ENTRY_DELETE);
   // Экземпляры WatchKey  потокобезопасны.
   while (true) {
      key = watcher.take(); // блокирует поток пока не появится событие
      // есть также неблокирующий способ получения key:
     //key = watcher.poll(5, TimeUnit.SECONDS);
     for (WatchEvent<?> event : key.pollEvents()) {
        if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
            // запускаем выполнение нужной задачи
            System.out.println("File task created \n");
        }
        if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
            // запускаем выполнение нужной задачи
            System.out.println("File task deleted \n");
        }                   
     }
    key.reset();
    }
   } catch (IOException | InterruptedException ex) {     }
}
demo-пример с исходниками - https://github.com/harp077/Watch-Service-Demo

Хеши - это просто ! Apache Commons Codec.

Расскажем об очень полезной библиотеке Apache Commons Codec ( https://commons.apache.org/proper/commons-codec/ ) - а точнее о классе утилит DigestUtils, который позволяет считать хеши от текста, файлов и т д. Поддерживает хеши md2, md5, sha1, sha256, sha384, sha512. Зависимость maven:
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.11</version>
</dependency>

Подсчет хешей для текста:

import javax.swing.JOptionPane;
import org.apache.commons.codec.digest.DigestUtils;

public class HashTextTest {

    public static String GetTextHash(String buf, String Tip) {
        if (!buf.isEmpty()) {
            switch (Tip) {
                case "md2":   return DigestUtils.md2Hex(buf);
                case "md5":   return DigestUtils.md5Hex(buf);
                case "sha1":   return DigestUtils.sha1Hex(buf);
                case "sha256": return DigestUtils.sha256Hex(buf);
                case "sha384": return DigestUtils.sha384Hex(buf);
                case "sha512": return DigestUtils.sha512Hex(buf);
                default:       return "wrong hash type";
            }
        } else {
            JOptionPane.showMessageDialog(null, "'Input Text' is empty !");
        }
        return "some error";
    }

    public static void main(String[] args) {
        String tip = JOptionPane.showInputDialog("input Hash Type:");
        String str = JOptionPane.showInputDialog("input string for hash:");
        JOptionPane.showMessageDialog(null, GetTextHash(str, tip));
    }

}
исходники - https://github.com/harp077/HashTextDemo
подсчет хеша строки для кодировки UTF-8:
import static org.apache.commons.codec.binary.StringUtils.getBytesUtf8;
String str = "bla-bla" ;
DigestUtils.md5Hex(getBytesUtf8(str)); 

Подсчет хешей для файлов:
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import org.apache.commons.codec.digest.DigestUtils;

public class HashFileTest {

    public static String hash_of_File(File buffile, String tip) {
        if (!buffile.isFile()) {
            JOptionPane.showMessageDialog(null, "not a file !");
            return "";
        }
        try (FileInputStream fis = new FileInputStream(buffile);
                BufferedInputStream bis = new BufferedInputStream(fis);) {
            switch (tip) {
                case "md2":   return DigestUtils.md2Hex(bis);
                case "md5":   return DigestUtils.md5Hex(bis);
                case "sha1":   return DigestUtils.sha1Hex(bis);
                case "sha256": return DigestUtils.sha256Hex(bis);
                case "sha384": return DigestUtils.sha384Hex(bis);
                case "sha512": return DigestUtils.sha512Hex(bis);
                default:       return "wrong hash type";
            }
        } catch (FileNotFoundException fex) {
        } catch (IOException ex) {        }
        return "";
    }

    public static void main(String[] args) {
        String tip = JOptionPane.showInputDialog("input Hash Type:");
        JFileChooser fileopen = new JFileChooser();
        int ret = fileopen.showDialog(null, "Открыть файл");
        if (ret == JFileChooser.APPROVE_OPTION) {
            File file = fileopen.getSelectedFile();
            JOptionPane.showMessageDialog(null, hash_of_File(file, tip));
        }
    }

}
исходники - https://github.com/harp077/HashFileDemo 
В commons-codec еще много что хорошего.

Полиморфизм в java

Через абстратный класс:
public abstract class Skot {
    public abstract String Zvuk ();
}
public class Korova extends Skot {
    @Override
    public String Zvuk() {
       return "Korova = mu-mu";
    }
}
public class Koza extends Skot {
    @Override
    public String Zvuk() {
       return "Koza = be-be";
    }
}
Через интерфейс:
public interface Skotina {
    String Golos ();
}
public class Dog implements Skotina {
    @Override
    public String Golos() {
       return "Dog = gav";
    }   
}
public class Cat implements Skotina {
    @Override
    public String Golos() {
       return "Cat = mau";
    }   
}
Метод main:
 public class PolimorfizmDemo {

    public static void main(String[] args) {
        Skot korova = new Korova();
        Skot koza = new Koza();
        System.out.println(korova.Zvuk());
        System.out.println(koza.Zvuk());
        ///////////
        Skotina dog = new Dog();
        Skotina cat = new Cat();
        System.out.println(dog.Golos());
        System.out.println(cat.Golos());
    }
}
вывод:
Korova = mu-mu
Koza = be-be
Dog = gav
Cat = mau
При обьявлении переменных вы указываете более всеобьемлющий тип чем тот, который вы используете при создании переменных в операторе new, однако вызываются именно те методы, которые нужны - происходит это за счет динамического связывания (во время выполнения) в отличие от статического связывания (во время компиляции). Здесь также следует упомянуть знаменитый холивар - "что лучше - абстрактный класс или интерфейс ?".

суббота, 14 июля 2018 г.

Способы создания/уничтожения бинов и паттерн "фабрика"

Коротко - паттерн "фабрика" - это реализация класса который обеспечивает настройку, создание и получение обьектов с помощью фабричных методов.
Spring
Вы можете отмечать классы аннотациями @Component, @Service, @Repository, @Controller, @Named - и тогда при наличии аннотации @ComponentScan для класса конфигурации приложения контейнер Spring автоматически определит бины и внедрит их куда нужно. Также бины можно создавать с помощью настроек XML - но мы это не будем рассматривать. Есть еще способ - это фабричные методы в классе конфигурации приложения, отмеченные аннотацией @Bean. Наиболее яркий пример - это бин, реализующий интерфейс DataSource. Вот класс конфига приложения:
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"PKG"})
public class AppContext {

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        BasicDataSource bds = new BasicDataSource();
        bds.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
        bds.setUrl("jdbc:derby:./db/primer");
        bds.setPassword("12345");
        bds.setUsername("romka");
        return bds;
    }
....................   
}
Теперь можно внедрить этот бин куда и как угодно - через поле / метод / конструктор, например:
@Inject
private DataSource ds;
JavaEE CDI
Таким же функционалом обладает контейнер CDI JavaEE - только здесь такие фабричные методы называют продюсерами а используются аннотации @Produces и @Disposes и использовать их можно в любом бине CDI, аналог примера выше для CDI:
@Named
public class DSproducer {

    @Produces @ApplicationScoped
    @Named("ds") 
     public DataSource dataSource() {
        BasicDataSource bds = new BasicDataSource();
        bds.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
        bds.setUrl("jdbc:derby:./db/primer");
        bds.setPassword("12345");
        bds.setUsername("romka");
        return bds;
    }

    public void dsClose (@Disposes DataSource ds) {
        ds.close();
    }

}
отметим, что аннотация  @Disposes применяется к параметру метода уничтожения и кроме того - методы создания и уничтожения бина должны быть в одном классе. По сути @Produces - это аналог @Bean, а метод уничтожения с аннотацией @Disposes - это аналог параметра "destroyMethod" аннотации @Bean из Spring. Метод уничтожения вызывается по окончании действия контекста данного бина (здесь - @ApplicationScoped).
И да - точно так же теперь можно внедрить этот бин куда и как угодно - через поле / метод / конструктор (все эти способы контейнер CDI поддерживает точно так же как и Spring), например:
@Inject
private DataSource ds;


Spring - как бину узнать свое имя

Для этого надо реализовать интерфейс BeanNameAware и его единственный метод setBeanName(String str):

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
public class HashOfFile implements BeanNameAware {
   
    private String myName;
   
    @Override
    public void setBeanName (String myName) {
        this.myName=myName;
    }

   
    @PostConstruct
    public void afterBirn(){
       System.out.println("HashOfFile: my name is = " + myName);
    }
..................................
}
Пример исходного кода - https://github.com/harp077/hofat

Интеграция Spring и JPA EclipseLink

Класс конфигурации приложения:

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = {"jennom"})
@EnableLoadTimeWeaving
@PropertySource(value = "classpath:jennom.properties")
public class AppContext {


    @Bean
    public JpaTransactionManager jpaTransactionManager() {
        JpaTransactionManager jtm = new JpaTransactionManager();
        jtm.setDataSource(this.ds());
        jtm.setJpaDialect(new EclipseLinkJpaDialect());
        jtm.setPersistenceUnitName("jennomPU");
        return jtm;
    }

    @Bean(destroyMethod = "close")
    public DataSource ds() {
        BasicDataSource dsNodes = new BasicDataSource();
        dsNodes.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
        dsNodes.setUrl("jdbc:derby:./db/primer");
        dsNodes.setPassword("12345");
        dsNodes.setUsername("romka");
        return dsNodes;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean eemf = new LocalContainerEntityManagerFactoryBean();
        eemf.setDataSource(this.ds());
        eemf.setJpaVendorAdapter(elJpaVendorAdapter());
        eemf.setPackagesToScan("jennom.jpa");
        eemf.setPersistenceUnitName("jennomPU");
        eemf.setJpaDialect(new EclipseLinkJpaDialect());
        eemf.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE); 
        return eemf;
    }

    @Bean
    public EclipseLinkJpaVendorAdapter elJpaVendorAdapter() {
        EclipseLinkJpaVendorAdapter jpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
        jpaVendorAdapter.setDatabase(Database.DERBY);
        jpaVendorAdapter.setGenerateDdl(true);
        jpaVendorAdapter.setShowSql(true);
        jpaVendorAdapter.setDatabasePlatform("org.eclipse.persistence.platform.database.DerbyPlatform");
        return jpaVendorAdapter;
    }

}

после этого вы можете создавать свой DAO:


@Service
@Repository
@Transactional
public class EjbDaoJPA {

    @PersistenceContext(unitName = "jennomPU")
    private EntityManager em;

    @Transactional(readOnly=true)
    public List<JpaNodes> findAllNodesJPQL () {
        List<JpaNodes> nodes=em.createNamedQuery("JpaNodes.findAll", JpaNodes.class).getResultList();
        return nodes;
    }
    @Transactional(readOnly=true)
    public JpaNodes findNodeJPQL (int id) {
        JpaNodes nodes=em.createNamedQuery("JpaNodes.findById", JpaNodes.class)
        .setParameter("id", id).getSingleResult();
        return nodes;
    }   
    public void removeNode (JpaNodes jpaNode) {
        em.remove(jpaNode);
    } 

    public void createNode (JpaNodes jpaNode) {
        em.persist(jpaNode);
    }   

    public void editNode (JpaNodes jpaNode) {
        em.merge(jpaNode);
    } 
      public void clearAllNodesSQL () {

        em.createNativeQuery("delete from nodes");
    } 

    public void clearAllNodesJPQL () {
        em.createNamedQuery("JpaNodes.clear", JpaNodes.class);
    }   

}


Сущность JpaNodes

@Entity
@Cacheable(true)
@Table(name="nodes")
@NamedQueries ({
    @NamedQuery(name="JpaNodes.clear",
            query="delete from JpaNodes"),   
    @NamedQuery(name="JpaNodes.findAll",
            query="select c from JpaNodes c order by c.ip"),   
    @NamedQuery(name="JpaNodes.findByIP",
            query="select c from JpaNodes c where c.ip = :ip"),
    @NamedQuery(name="JpaNodes.findById",
            query="select c from JpaNodes c where c.id = :id")   
})
public class JpaNodes implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO) /// !!!!!!!! = IDENTITY
    private int id;
   
    @Basic   
    @Column(name = "time", nullable=true, insertable=true, updatable=true, length=255)    
    private String time="";
   
    @Basic 
    @Size(min=1,max=5,message="stateFar")
    @Column(name = "StateFar", nullable=true, insertable=true, updatable=true, length=255)    
    private String StateFar="-";
   
    @Basic
    @NotNull(message="IP/DNS not null")
    //@Pattern(regexp="",message="")
    @Column(name = "ip", nullable=false, insertable=true, updatable=true, length=255)    
    private String ip;
   
    @Basic   
    @Column(name = "info", nullable=true, insertable=true, updatable=true, length=255)    
    private String info="";
   
    @Basic 
    @NotNull(message="Type not null")
    @Column(name = "tip", nullable=false, insertable=true, updatable=true, length=255)    
    private String tip="";   
   
    @Basic   
    @Column(name = "loss", nullable=true, insertable=true, updatable=true, length=255)    
    private String loss="0";
   
    @Basic
    @Size(min=1,max=5,message="state")
    @Column(name = "state", nullable=true, insertable=true, updatable=true, length=255)    
    private String state="-";
    ...........геттеры + сеттеры + equals + hashCode + toString}

пятница, 13 июля 2018 г.

Асинхронный вызов методов

При таком вызове метод выполняется в отдельном потоке не прерывая основной поток приложения. Иногда такой прием называют "паттерн асинхронность".
Spring
Класс конфигурации приложения надо отметить аннотацией @EnableAsync. Если это веб-приложение - то достаточно снабдить метод аннотацией @Async - и любой контейнер сервлетов (например Tomcat) запустит метод в отдельном потоке. Для десктоп-приложения надо будет добавить в класс конфигурации следующий бин:
@Configuration
@EnableAsync
public class AppContext {
   
    @Bean
    public TaskExecutor taskExecutor() {
        return new SimpleAsyncTaskExecutor();
    }
........................................
}

JavaEE
Такой же функционал обеспечивает контейнер EJB из "коробки" без всяких дополнительных настроек (начиная с JavaEE 6 - EJB 3.1 - Например TomEE v1.7.4 с профайлом jaxrs - http://tomee.apache.org/download-archive.html или http://openejb.apache.org/downloads.html ) - достаточно в бине EJB отметить метод аннотацией @Asynchronous - она применима для всех типов бинов EJB - @Stateless, @Stateful, @Singleton. Если отметить весь класс - то все его методы станут асинхронными.

Взаимодействие между приложениями и Watch Service

Взаимодействие между разными приложениями Java можно сделать с помощью файловой системы, базы данных и JMS. Рассмотрим случай ФС - в Java ...