001/* 002 * HA-JDBC: High-Availability JDBC 003 * Copyright (C) 2013 Paul Ferraro 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Lesser General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package net.sf.hajdbc.sql; 019 020import java.util.Collections; 021import java.util.HashSet; 022import java.util.Map; 023import java.util.Set; 024import java.util.WeakHashMap; 025 026import net.sf.hajdbc.Database; 027import net.sf.hajdbc.DatabaseCluster; 028import net.sf.hajdbc.ExceptionFactory; 029import net.sf.hajdbc.ExceptionType; 030import net.sf.hajdbc.Messages; 031import net.sf.hajdbc.invocation.Invoker; 032import net.sf.hajdbc.logging.Level; 033import net.sf.hajdbc.logging.Logger; 034import net.sf.hajdbc.logging.LoggerFactory; 035 036/** 037 * 038 * @author Paul Ferraro 039 */ 040public abstract class AbstractProxyFactory<Z, D extends Database<Z>, TE extends Exception, T, E extends Exception> implements ProxyFactory<Z, D, T, E> 041{ 042 protected Logger logger = LoggerFactory.getLogger(this.getClass()); 043 044 private final DatabaseCluster<Z, D> cluster; 045 private final Map<D, T> map; 046 private final Set<ChildProxyFactory<Z, D, T, E, ?, ? extends Exception>> children = Collections.newSetFromMap(new WeakHashMap<ChildProxyFactory<Z, D, T, E, ?, ? extends Exception>, Boolean>()); 047 private final Set<Invoker<Z, D, T, ?, E>> invokers = new HashSet<Invoker<Z, D, T, ?, E>>(); 048 private final ExceptionFactory<E> exceptionFactory; 049 050 /** 051 * Constructs a new proxy to a set of objects 052 * @param map a map of database to sql object. 053 * @param exceptionClass the class for exceptions thrown by this object 054 */ 055 protected AbstractProxyFactory(DatabaseCluster<Z, D> cluster, Map<D, T> map, Class<E> exceptionClass) 056 { 057 this.cluster = cluster; 058 this.map = Collections.synchronizedMap(map); 059 this.exceptionFactory = ExceptionType.valueOf(exceptionClass).getExceptionFactory(); 060 } 061 062 @Override 063 public DatabaseCluster<Z, D> getDatabaseCluster() 064 { 065 return this.cluster; 066 } 067 068 protected T remove(D database) 069 { 070 return this.map.remove(database); 071 } 072 073 /** 074 * {@inheritDoc} 075 */ 076 @Override 077 public Set<Map.Entry<D, T>> entries() 078 { 079 return this.map.entrySet(); 080 } 081 082 protected synchronized Iterable<ChildProxyFactory<Z, D, T, E, ?, ? extends Exception>> children() 083 { 084 return this.children; 085 } 086 087 @Override 088 public synchronized void addChild(ChildProxyFactory<Z, D, T, E, ?, ? extends Exception> child) 089 { 090 this.children.add(child); 091 } 092 093 @Override 094 public synchronized void removeChild(ChildProxyFactory<Z, D, T, E, ?, ? extends Exception> child) 095 { 096 this.children.remove(child); 097 } 098 099 @Override 100 public synchronized final void removeChildren() 101 { 102 this.children.clear(); 103 } 104 105 /** 106 * Returns the underlying SQL object for the specified database. 107 * If the sql object does not exist (this might be the case if the database was newly activated), it will be created from the stored operation. 108 * Any recorded operations are also executed. If the object could not be created, or if any of the executed operations failed, then the specified database is deactivated. 109 * @param database a database descriptor. 110 * @return an underlying SQL object 111 */ 112 @Override 113 public T get(D database) 114 { 115 synchronized (this.map) 116 { 117 T object = this.map.get(database); 118 119 if (object == null) 120 { 121 try 122 { 123 object = this.create(database); 124 125 this.replay(database, object); 126 127 this.map.put(database, object); 128 } 129 catch (Throwable e) 130 { 131 if (!this.map.isEmpty() && this.cluster.deactivate(database, this.cluster.getStateManager())) 132 { 133 this.logger.log(Level.WARN, e, Messages.SQL_OBJECT_INIT_FAILED.getMessage(), this.getClass().getName(), database); 134 } 135 } 136 } 137 138 return object; 139 } 140 } 141 142 protected abstract T create(D database) throws TE; 143 144 @Override 145 public void record(Invoker<Z, D, T, ?, E> invoker) 146 { 147 // Record only the last invocation of a given set*(...) method 148 synchronized (this.invokers) 149 { 150 this.invokers.remove(invoker); 151 this.invokers.add(invoker); 152 } 153 } 154 155 /** 156 * @throws E 157 */ 158 @Override 159 public void replay(D database, T object) throws E 160 { 161 synchronized (this.invokers) 162 { 163 for (Invoker<Z, D, T, ?, E> invoker: this.invokers) 164 { 165 this.logger.log(Level.TRACE, "Replaying {1}.{2} against database {0}", database, object.getClass().getName(), invoker); 166 167 try 168 { 169 invoker.invoke(database, object); 170 } 171 catch (Throwable e) 172 { 173 this.exceptionFactory.createException(e); 174 } 175 } 176 } 177 } 178 179 /** 180 * {@inheritDoc} 181 */ 182 @Override 183 public final ExceptionFactory<E> getExceptionFactory() 184 { 185 return this.exceptionFactory; 186 } 187}