/*
* Copyright 2009-2014 Jagornet Technologies, LLC. All Rights Reserved.
*
* This software is the proprietary information of Jagornet Technologies, LLC.
* Use is subject to license terms.
*
*/
/*
* This file JdbcIaManager.java is part of Jagornet DHCP.
*
* Jagornet DHCP is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jagornet DHCP is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jagornet DHCP. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.jagornet.dhcp.db;
import java.io.IOException;
import java.net.InetAddress;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import com.jagornet.dhcp.server.config.DhcpServerPolicies;
import com.jagornet.dhcp.server.config.DhcpServerPolicies.Property;
import com.jagornet.dhcp.server.request.binding.Range;
import com.jagornet.dhcp.util.Util;
/**
* The JdbcIaManager implementation class for the IaManager interface.
* This is the main database access class for handling client bindings.
*
* @author A. Gregory Rabil
*/
public class JdbcIaManager extends JdbcDaoSupport implements IaManager
{
private static Logger log = LoggerFactory.getLogger(JdbcIaManager.class);
protected IdentityAssocDAO iaDao;
protected IaAddressDAO iaAddrDao;
protected IaPrefixDAO iaPrefixDao;
protected DhcpOptionDAO dhcpOptDao;
// Spring bean init-method
public void init() throws Exception {
String schemaType = DhcpServerPolicies.globalPolicy(Property.DATABASE_SCHEMA_TYTPE);
if (schemaType.toLowerCase().endsWith("derby")) {
DbSchemaManager.validateSchema(getDataSource(), DbSchemaManager.SCHEMA_DERBY_FILENAME, 1);
}
else {
DbSchemaManager.validateSchema(getDataSource(), DbSchemaManager.SCHEMA_FILENAME, 1);
}
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#createIA(com.jagornet.dhcpv6.db.IdentityAssoc)
*/
public void createIA(IdentityAssoc ia)
{
if (ia != null) {
log.debug("Creating: " + ia.toString());
iaDao.create(ia); // sets the id
Collection<? extends IaAddress> iaAddrs = ia.getIaAddresses();
if (iaAddrs != null) {
for (IaAddress iaAddr : iaAddrs) {
iaAddr.setIdentityAssocId(ia.getId());
if (ia.getIatype() == IdentityAssoc.PD_TYPE)
iaPrefixDao.create((IaPrefix)iaAddr);
else
iaAddrDao.create(iaAddr); //sets the id
Collection<DhcpOption> opts = iaAddr.getDhcpOptions();
if (opts != null) {
for (DhcpOption opt : opts) {
if (ia.getIatype() == IdentityAssoc.PD_TYPE)
opt.setIaPrefixId(iaAddr.getId());
else
opt.setIaAddressId(iaAddr.getId());
dhcpOptDao.create(opt);
}
}
}
}
Collection<DhcpOption> opts = ia.getDhcpOptions();
if (opts != null) {
for (DhcpOption opt : opts) {
opt.setIdentityAssocId(ia.getId());
dhcpOptDao.create(opt);
}
}
}
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#updateIA(com.jagornet.dhcpv6.db.IdentityAssoc, java.util.Collection, java.util.Collection, java.util.Collection)
*/
public void updateIA(IdentityAssoc ia, Collection<? extends IaAddress> addAddrs,
Collection<? extends IaAddress> updateAddrs, Collection<? extends IaAddress> delAddrs)
{
iaDao.update(ia);
if (addAddrs != null) {
for (IaAddress addAddr : addAddrs) {
// ensure the address points to the given IA
addAddr.setIdentityAssocId(ia.getId());
if (ia.getIatype() == IdentityAssoc.PD_TYPE)
iaPrefixDao.create((IaPrefix)addAddr);
else
iaAddrDao.create(addAddr);
}
}
if (updateAddrs != null) {
for (IaAddress updateAddr : updateAddrs) {
// ensure the address points to the given IA
updateAddr.setIdentityAssocId(ia.getId());
if (ia.getIatype() == IdentityAssoc.PD_TYPE)
iaPrefixDao.update((IaPrefix)updateAddr);
else
iaAddrDao.update(updateAddr);
}
}
if (delAddrs != null) {
for (IaAddress delAddr : delAddrs) {
if (ia.getIatype() == IdentityAssoc.PD_TYPE)
iaPrefixDao.deleteById(delAddr.getId());
else
iaAddrDao.deleteById(delAddr.getId());
}
}
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#deleteIA(com.jagornet.dhcpv6.db.IdentityAssoc)
*/
public void deleteIA(IdentityAssoc ia)
{
if (ia != null) {
log.debug("Deleting: " + ia.toString());
// just delete the IdentityAssoc object, and allow the
// database constraints (cascade delete) take care of
// deleting any associated IaAddress, IaPrefix or DhcpOption objects,
// further cascading to delete any DhcpOption objects
// for any IaAddress or IaPrefix objects which are deleted
iaDao.deleteById(ia.getId());
}
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#getIA(long)
*/
public IdentityAssoc getIA(long id)
{
return iaDao.getById(id);
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#findIA(byte[], byte, long)
*/
public IdentityAssoc findIA(byte[] duid, byte iatype, long iaid)
{
IdentityAssoc ia = iaDao.getByKey(duid, iatype, iaid);
if (ia != null) {
List<? extends IaAddress> iaAddrs = null;
if (ia.getIatype() == IdentityAssoc.PD_TYPE)
iaAddrs = iaPrefixDao.findAllByIdentityAssocId(ia.getId());
else
iaAddrs = iaAddrDao.findAllByIdentityAssocId(ia.getId());
if (iaAddrs != null) {
for (IaAddress iaAddr : iaAddrs) {
List<DhcpOption> opts = null;
if (ia.getIatype() == IdentityAssoc.PD_TYPE)
opts = dhcpOptDao.findAllByIaPrefixId(iaAddr.getId());
else
opts = dhcpOptDao.findAllByIaAddressId(iaAddr.getId());
if (opts != null) {
iaAddr.setDhcpOptions(new ArrayList<DhcpOption>(opts));
}
}
ia.setIaAddresses(new ArrayList<IaAddress>(iaAddrs));
}
List<DhcpOption> opts = dhcpOptDao.findAllByIdentityAssocId(ia.getId());
if (opts != null) {
ia.setDhcpOptions(new ArrayList<DhcpOption>(opts));
}
}
return ia;
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#findIA(com.jagornet.dhcpv6.db.IaAddress)
*/
public IdentityAssoc findIA(IaAddress iaAddr)
{
IdentityAssoc ia = null;
if (iaAddr != null) {
try {
ia = iaDao.getById(iaAddr.getIdentityAssocId());
}
catch (EmptyResultDataAccessException ex) {
log.debug("No IdenityAssoc found for ID=" +iaAddr.getIdentityAssocId() +
": " + ex);
}
}
return ia;
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#findIA(java.net.InetAddress)
*/
public IdentityAssoc findIA(InetAddress inetAddr)
{
IdentityAssoc ia = null;
if (inetAddr != null) {
try {
IaAddress iaAddr = iaAddrDao.getByInetAddress(inetAddr);
if (iaAddr != null) {
ia = iaDao.getById(iaAddr.getIdentityAssocId());
}
}
catch (EmptyResultDataAccessException ex) {
log.debug("No IaAddress found for IP=" + inetAddr.getHostAddress() +
": " + ex);
}
}
return ia;
}
public List<IdentityAssoc> findExpiredIAs(byte iatype)
{
List<IdentityAssoc> expiredIAs = null;
//TODO: improve on this logic, see hack below
List<IaAddress> iaAddrs = iaAddrDao.findExpiredAddresses(iatype);
if (iaAddrs != null) {
expiredIAs = new ArrayList<IdentityAssoc>();
for (IaAddress iaAddr : iaAddrs) {
List<IaAddress> _iaAddrs = new ArrayList<IaAddress>();
// populate the IaAddress with the associated DhcpOptions
iaAddr.setDhcpOptions(dhcpOptDao.findAllByIaAddressId(iaAddr.getId()));
// find the IA for this expired IaAddress
IdentityAssoc ia = findIA(iaAddr);
// now add this one expired IaAddress to the
// fabricated expired IA for the returned list
_iaAddrs.add(iaAddr);
// this hack is likely to be a problem someday...
ia.setIaAddresses(_iaAddrs);
expiredIAs.add(ia);
}
}
return expiredIAs;
}
public void saveDhcpOption(IaAddress iaAddr,
com.jagornet.dhcp.option.base.BaseDhcpOption baseOption)
{
try {
byte[] newVal = baseOption.encode().array();
// don't store the option code, start with length to
// simplify decoding when retrieving from database
if (baseOption.isV4()) {
newVal = Arrays.copyOfRange(newVal, 1, newVal.length);
}
else {
newVal = Arrays.copyOfRange(newVal, 2, newVal.length);
}
// DhcpOption dbOption = iaAddr.getDhcpOption(baseOption.getCode());
DhcpOption dbOption = findIaAddressOption(iaAddr, baseOption);
if (dbOption == null) {
dbOption = new com.jagornet.dhcp.db.DhcpOption();
dbOption.setCode(baseOption.getCode());
dbOption.setValue(newVal);
addDhcpOption(iaAddr, dbOption);
}
else {
if(!Arrays.equals(dbOption.getValue(), newVal)) {
dbOption.setValue(newVal);
updateDhcpOption(dbOption);
}
}
}
catch (IOException ex) {
log.error("Failed to update binding with option", ex);
}
}
public void deleteDhcpOption(IaAddress iaAddr,
com.jagornet.dhcp.option.base.BaseDhcpOption baseOption)
{
DhcpOption dbOption = findIaAddressOption(iaAddr, baseOption);
if (dbOption != null) {
dhcpOptDao.deleteById(dbOption.getId());
}
}
protected DhcpOption findIaAddressOption(IaAddress iaAddr,
com.jagornet.dhcp.option.base.BaseDhcpOption baseOption)
{
DhcpOption dbOption = null;
List<DhcpOption> iaAddrOptions = dhcpOptDao.findAllByIaAddressId(iaAddr.getId());
if (iaAddrOptions != null) {
for (DhcpOption iaAddrOption : iaAddrOptions) {
if (iaAddrOption.getCode() == baseOption.getCode()) {
dbOption = iaAddrOption;
break;
}
}
}
return dbOption;
}
protected void addDhcpOption(IdentityAssoc ia, DhcpOption option)
{
// ensure the DhcpOption references this IA, and nothing else
option.setIdentityAssocId(ia.getId());
option.setIaAddressId(null);
option.setIaPrefixId(null);
dhcpOptDao.create(option);
}
protected void addDhcpOption(IaAddress iaAddr, DhcpOption option)
{
// ensure the DhcpOption references this IA_ADDR, and nothing else
option.setIdentityAssocId(null);
option.setIaAddressId(iaAddr.getId());
option.setIaPrefixId(null);
dhcpOptDao.create(option);
}
protected void addDhcpOption(IaPrefix iaPrefix, DhcpOption option)
{
// ensure the DhcpOption references this IA_PREFIX, and nothing else
option.setIdentityAssocId(null);
option.setIaAddressId(null);
option.setIaPrefixId(iaPrefix.getId());
dhcpOptDao.create(option);
}
protected void updateDhcpOption(DhcpOption option)
{
dhcpOptDao.update(option);
}
protected void deleteDhcpOption(DhcpOption option)
{
dhcpOptDao.deleteById(option.getId());
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#updateIaAddr(com.jagornet.dhcpv6.db.IaAddress)
*/
public void updateIaAddr(IaAddress iaAddr)
{
iaAddrDao.update(iaAddr);
expireIA(iaAddr.getIdentityAssocId());
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#deleteIaAddr(com.jagornet.dhcpv6.db.IaAddress)
*/
public void deleteIaAddr(IaAddress iaAddr)
{
iaAddrDao.deleteById(iaAddr.getId());
deleteExpiredIA(iaAddr.getIdentityAssocId());
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#updateIaPrefix(com.jagornet.dhcpv6.db.IaPrefix)
*/
public void updateIaPrefix(IaPrefix iaPrefix)
{
iaPrefixDao.update(iaPrefix);
expireIA(iaPrefix.getIdentityAssocId());
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#deleteIaPrefix(com.jagornet.dhcpv6.db.IaPrefix)
*/
public void deleteIaPrefix(IaPrefix iaPrefix)
{
iaPrefixDao.deleteById(iaPrefix.getId());
deleteExpiredIA(iaPrefix.getIdentityAssocId());
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#findExistingIPs(java.net.InetAddress, java.net.InetAddress)
*/
@Override
public List<InetAddress> findExistingIPs(InetAddress startAddr, InetAddress endAddr) {
return iaAddrDao.findExistingIPs(startAddr, endAddr);
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#findUnusedIaAddresses(java.net.InetAddress, java.net.InetAddress)
*/
@Override
public List<IaAddress> findUnusedIaAddresses(InetAddress startAddr, InetAddress endAddr) {
return iaAddrDao.findUnusedByRange(startAddr, endAddr);
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#findExpiredIaAddresses(byte)
*/
@Override
public List<IaAddress> findExpiredIaAddresses(byte iatype) {
return iaAddrDao.findExpiredAddresses(iatype);
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#findUnusedIaPrefixes(java.net.InetAddress, java.net.InetAddress)
*/
@Override
public List<IaPrefix> findUnusedIaPrefixes(InetAddress startAddr, InetAddress endAddr) {
return iaPrefixDao.findUnusedByRange(startAddr, endAddr);
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#findExpiredIaPrefixes()
*/
@Override
public List<IaPrefix> findExpiredIaPrefixes() {
return iaPrefixDao.findAllOlderThan(new Date());
}
/* (non-Javadoc)
* @see com.jagornet.dhcpv6.db.IaManager#reconcileIaAddresses(java.util.List)
*/
@Override
public void reconcileIaAddresses(List<Range> ranges) {
iaAddrDao.deleteNotInRanges(ranges);
}
/**
* Expire ia.
*
* @param id the id
*/
protected void expireIA(final Long id)
{
getJdbcTemplate().update(
"update identityassoc set state=" + IdentityAssoc.EXPIRED +
" where id=?" +
" and not exists" +
" (select 1 from iaaddress" +
" where identityassoc_id=identityassoc.id" +
" and validendtime>=?)",
new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps)
throws SQLException {
ps.setLong(1, id);
java.sql.Timestamp now = new java.sql.Timestamp((new Date()).getTime());
ps.setTimestamp(2, now, Util.GMT_CALENDAR);
}
});
}
/**
* Delete expired ia.
*
* @param id the id
*/
protected void deleteExpiredIA(final Long id)
{
getJdbcTemplate().update(
"delete from identityassoc" +
" where id=?" +
" and not exists (select 1 from iaaddress" +
" where identityassoc_id=identityassoc.id" +
" and validendtime is not null and validendtime>=?)",
new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps)
throws SQLException {
ps.setLong(1, id);
java.sql.Timestamp now = new java.sql.Timestamp((new Date()).getTime());
ps.setTimestamp(2, now, Util.GMT_CALENDAR);
}
});
}
/**
* Expire i as.
*/
protected void expireIAs()
{
getJdbcTemplate().update(
"update identityassoc set state=" + IdentityAssoc.EXPIRED +
" where exists (select 1 from iaaddress where identityassoc_id=identityassoc.id and validendtime<?)" +
" and not exists (select 1 from iaaddress where identityassoc_id=identityassoc.id and validendtime>=?)",
new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps)
throws SQLException {
java.sql.Timestamp now = new java.sql.Timestamp((new Date()).getTime());
ps.setTimestamp(1, now, Util.GMT_CALENDAR);
ps.setTimestamp(2, now, Util.GMT_CALENDAR);
}
});
}
/**
* Gets the ia dao.
*
* @return the ia dao
*/
public IdentityAssocDAO getIaDao() {
return iaDao;
}
/**
* Sets the ia dao.
*
* @param iaDao the new ia dao
*/
public void setIaDao(IdentityAssocDAO iaDao) {
this.iaDao = iaDao;
}
/**
* Gets the ia addr dao.
*
* @return the ia addr dao
*/
public IaAddressDAO getIaAddrDao() {
return iaAddrDao;
}
/**
* Sets the ia addr dao.
*
* @param iaAddrDao the new ia addr dao
*/
public void setIaAddrDao(IaAddressDAO iaAddrDao) {
this.iaAddrDao = iaAddrDao;
}
/**
* Gets the ia prefix dao.
*
* @return the ia prefix dao
*/
public IaPrefixDAO getIaPrefixDao() {
return iaPrefixDao;
}
/**
* Sets the ia prefix dao.
*
* @param iaPrefixDao the ia prefix dao
*/
public void setIaPrefixDao(IaPrefixDAO iaPrefixDao) {
this.iaPrefixDao = iaPrefixDao;
}
/**
* Gets the dhcp opt dao.
*
* @return the dhcp opt dao
*/
public DhcpOptionDAO getDhcpOptDao() {
return dhcpOptDao;
}
/**
* Sets the dhcp opt dao.
*
* @param dhcpOptDao the new dhcp opt dao
*/
public void setDhcpOptDao(DhcpOptionDAO dhcpOptDao) {
this.dhcpOptDao = dhcpOptDao;
}
@Override
public void deleteAllIAs() {
iaDao.deleteAll();
}
}