View Javadoc
1   package de.fhdw.wtf.persistence.facade;
2   
3   import java.io.File;
4   import java.io.FileInputStream;
5   import java.io.FileNotFoundException;
6   import java.io.FileOutputStream;
7   import java.io.IOException;
8   import java.sql.CallableStatement;
9   import java.sql.ResultSet;
10  import java.sql.SQLException;
11  import java.util.Iterator;
12  import java.util.Properties;
13  
14  import oracle.jdbc.OracleCallableStatement;
15  import oracle.jdbc.OracleTypes;
16  import de.fhdw.wtf.persistence.exception.IDContractViolationException;
17  import de.fhdw.wtf.persistence.exception.IDNotFoundForNameException;
18  import de.fhdw.wtf.persistence.exception.IDPersistenceFilesNotFound;
19  import de.fhdw.wtf.persistence.exception.OtherSQLException;
20  import de.fhdw.wtf.persistence.exception.PersistenceException;
21  import de.fhdw.wtf.persistence.meta.IntegerType;
22  import de.fhdw.wtf.persistence.meta.StringType;
23  
24  /**
25   * TODO generate the greatest id for associations and types dynamically from a set of the singleton base type classes.
26   * 
27   * Description: - This class manages the id distribution for types and associations. - If a new type or association is
28   * created, the id for this new item should be pulled from this manager. - if you want to know the id for a model item
29   * (association or type) ask this manager. - This class is also able to save the information about the current relation
30   * (name -> id) to a file. See persistIDRelationsToFile. This should be called everytime you want to close the process
31   * which has the idmanager instance. - On creation of this manager the (name -> id) relation will be loaded from a file
32   * if it exists. Otherwise the database will be requested for the ids.
33   */
34  public final class IDManager {
35  	/**
36  	 * The Id which was the highest given ID for types during the initialization, it must match the contract. Ids must
37  	 * be given in successive manner therefore a value of zero means there are no types initialized. This value is also
38  	 * contracted by the pl sql script for the classfacade. See initialize operation.
39  	 */
40  	private static final long MAX_BASE_TYPES = 2; // 2 because of 2 base types
41  													// {String, Integer}
42  	
43  	/**
44  	 * Because after the creation of the Base Types there must not be any association, the contracted value is zero.
45  	 */
46  	private static final long MAX_CONTRACT_ASSOCIATION_ID = 0;
47  	
48  	/** Specifies the filename of the file for the type name -> id relation. */
49  	public static final String TYPE_IDS_FILENAME = "types.properties";
50  	
51  	/**
52  	 * Specifies the filename of the file for the association name -> id relation.
53  	 */
54  	public static final String ASSOCIATION_IDS_FILENAME = "associations.properties";
55  	
56  	private static IDManager instance = null;
57  	private final Properties typeNameIdRelation;
58  	private final Properties associationNameIdRelation;
59  	private long currentlyUsedTypeMaxId;
60  	private long currentlyUsedAssociationMaxId;
61  	
62  	private IDManager() {
63  		this.typeNameIdRelation = new Properties();
64  		this.associationNameIdRelation = new Properties();
65  		this.currentlyUsedAssociationMaxId = getMaxAssociationContractID();
66  		this.currentlyUsedTypeMaxId = getMaxBaseTypeID();
67  	}
68  	
69  	/**
70  	 * This operation searches in the property the highest type id and sets the member currentlyUsedTypeMaxId to it.
71  	 * This is necessary to generate the next id unused it properly after the initialization of this class.
72  	 */
73  	private void recalibrateMaxTypeID() {
74  		final Iterator<String> it = this.typeNameIdRelation.stringPropertyNames().iterator();
75  		while (it.hasNext()) {
76  			final String currentKey = it.next();
77  			final Long currentId = Long.parseLong(this.typeNameIdRelation.getProperty(currentKey));
78  			if (this.currentlyUsedTypeMaxId < currentId) {
79  				this.currentlyUsedTypeMaxId = currentId;
80  			}
81  		}
82  		
83  	}
84  	
85  	/**
86  	 * This operation searches for the highest association id and sets the member currentlyUsedAssociationMaxId to it.
87  	 * This is necessary to generate the next id unused it properly after the initialization of this class.
88  	 */
89  	private void recalibrateMaxAssociationID() {
90  		final Iterator<String> it = this.associationNameIdRelation.stringPropertyNames().iterator();
91  		while (it.hasNext()) {
92  			final String currentKey = it.next();
93  			final Long currentId = Long.parseLong(this.associationNameIdRelation.getProperty(currentKey));
94  			if (this.currentlyUsedAssociationMaxId < currentId) {
95  				this.currentlyUsedAssociationMaxId = currentId;
96  			}
97  		}
98  	}
99  	
100 	/**
101 	 * Checks if the files with the id informations are available and initializes the internal data structures with
102 	 * these values.
103 	 * 
104 	 * @param typeIdsFilename
105 	 *            the filename to the file contains the typeIds
106 	 * @param associationIdsFilename
107 	 *            the filename to the file contains the assoctiationIds
108 	 * @throws IOException
109 	 * @throws FileNotFoundException
110 	 */
111 	public void initializeRelationsFromFile(final String typeIdsFilename, final String associationIdsFilename)
112 			throws FileNotFoundException, IOException {
113 		final File fileType = new File(typeIdsFilename);
114 		final File fileAssociation = new File(associationIdsFilename);
115 		
116 		if (fileType.exists() && !fileType.isDirectory() && fileAssociation.exists() && !fileAssociation.isDirectory()) {
117 			try (final FileInputStream fileTypeInputStream = new FileInputStream(fileType);
118 					final FileInputStream fileAssociationInputStream = new FileInputStream(fileAssociation)) {
119 				this.typeNameIdRelation.load(fileTypeInputStream);
120 				this.associationNameIdRelation.load(fileAssociationInputStream);
121 			}
122 		} else {
123 			throw new IDPersistenceFilesNotFound();
124 		}
125 		this.typeNameIdRelation.setProperty(StringType.STRING_NAME, Long.toString(StringType.String_ID));
126 		this.typeNameIdRelation.setProperty(IntegerType.INTEGER_NAME, Long.toString(IntegerType.Integer_ID));
127 		this.recalibrateMaxTypeID();
128 		this.recalibrateMaxAssociationID();
129 		
130 	}
131 	
132 	/**
133 	 * Initializes the name->id relations from the database. TODO create some operations in the classfacade which
134 	 * provide the types and associations directly from the database. This is not part of the id-manager.
135 	 * 
136 	 * @throws PersistenceException
137 	 * @throws SQLException
138 	 */
139 	public void initializeRelationsFromDatabase() throws PersistenceException, SQLException {
140 		final OracleDatabaseManager database = OracleDatabaseManager.getInstance();
141 		try {
142 			database.getConnection();
143 		} catch (final PersistenceException e) {
144 			database.connect();
145 		}
146 		
147 		try (final CallableStatement call =
148 				database.getConnection().prepareCall(
149 						"begin ?:= " + database.getSchemaName() + ".classfacade.getAllTypes; end;")) {
150 			call.registerOutParameter(1, OracleTypes.CURSOR);
151 			call.execute();
152 			
153 			try (final ResultSet result = ((OracleCallableStatement) call).getCursor(1)) {
154 				while (result.next()) {
155 					this.typeNameIdRelation.setProperty(result.getString(2), Long.toString(result.getLong(1)));
156 				}
157 			}
158 		} catch (final SQLException e) {
159 			throw new OtherSQLException(e);
160 		}
161 		
162 		try (final CallableStatement call =
163 				database.getConnection().prepareCall(
164 						"begin ?:= " + database.getSchemaName() + ".classfacade.getAllUnidirAssociations; end;")) {
165 			call.registerOutParameter(1, OracleTypes.CURSOR);
166 			call.execute();
167 			
168 			try (final ResultSet result = ((OracleCallableStatement) call).getCursor(1)) {
169 				while (result.next()) {
170 					this.associationNameIdRelation.setProperty(result.getString(2), Long.toString(result.getLong(1)));
171 				}
172 			}
173 		} catch (final SQLException e) {
174 			throw new OtherSQLException(e);
175 		}
176 		
177 		try (final CallableStatement call =
178 				database.getConnection().prepareCall(
179 						"begin ?:= " + database.getSchemaName() + ".classfacade.getAllMapAssociations; end;")) {
180 			call.registerOutParameter(1, OracleTypes.CURSOR);
181 			call.execute();
182 			
183 			try (final ResultSet result = ((OracleCallableStatement) call).getCursor(1)) {
184 				while (result.next()) {
185 					this.associationNameIdRelation.setProperty(result.getString(2), Long.toString(result.getLong(1)));
186 				}
187 			}
188 		} catch (final SQLException e) {
189 			throw new OtherSQLException(e);
190 		}
191 		
192 		this.typeNameIdRelation.setProperty(StringType.STRING_NAME, Long.toString(StringType.String_ID));
193 		this.typeNameIdRelation.setProperty(IntegerType.INTEGER_NAME, Long.toString(IntegerType.Integer_ID));
194 		this.recalibrateMaxTypeID();
195 		this.recalibrateMaxAssociationID();
196 	}
197 	
198 	/**
199 	 * returns the Instance of the IDManager.
200 	 * 
201 	 * @return the instance
202 	 */
203 	public static synchronized IDManager instance() {
204 		if (instance == null) {
205 			instance = new IDManager();
206 		}
207 		return instance;
208 	}
209 	
210 	/**
211 	 * Warning: Use it for Test-Cases only! This operations forces a recreation of the singleton for the next time and
212 	 * frees the old one for garbage collection.
213 	 */
214 	public void clearInformation() {
215 		this.typeNameIdRelation.clear();
216 		this.associationNameIdRelation.clear();
217 		this.currentlyUsedAssociationMaxId = getMaxAssociationContractID();
218 		this.currentlyUsedTypeMaxId = getMaxBaseTypeID();
219 		
220 	}
221 	
222 	/**
223 	 * 
224 	 * @return the highest id for base types.
225 	 */
226 	public static long getMaxBaseTypeID() {
227 		return MAX_BASE_TYPES;
228 	}
229 	
230 	/**
231 	 * @return the highest association id which will be initialized by the pl-sql script at initialization. The highest
232 	 *         id is contracted.
233 	 */
234 	public static long getMaxAssociationContractID() {
235 		return MAX_CONTRACT_ASSOCIATION_ID;
236 	}
237 	
238 	/**
239 	 * This operation persists all (name -> id) informations to files.
240 	 * 
241 	 * @param typeIdsFilename
242 	 *            the filename to the file contains the typeIds
243 	 * @param associationIdsFilename
244 	 *            the filename to the file contains the associationIds
245 	 * @throws IOException
246 	 */
247 	public void persistIDRelationsToFile(final String typeIdsFilename, final String associationIdsFilename)
248 			throws IOException {
249 		try (FileOutputStream file = new FileOutputStream(typeIdsFilename)) {
250 			this.typeNameIdRelation.store(file, "");
251 			file.flush();
252 		}
253 		
254 		try (FileOutputStream file = new FileOutputStream(associationIdsFilename)) {
255 			this.associationNameIdRelation.store(file, "");
256 			file.flush();
257 		}
258 	}
259 	
260 	/**
261 	 * This operation returns the next unused id for a new type.
262 	 * 
263 	 * @param typeName
264 	 *            is the name of the type for which a new id will be registered.
265 	 * @return the id
266 	 */
267 	public synchronized long pullNextUnusedTypeID(final String typeName) {
268 		if (typeName.equals(StringType.STRING_NAME) || typeName.equals(IntegerType.INTEGER_NAME)) {
269 			throw new IDContractViolationException();
270 		}
271 		
272 		this.currentlyUsedTypeMaxId += 1;
273 		this.typeNameIdRelation.setProperty(typeName, Long.toString(this.currentlyUsedTypeMaxId));
274 		return this.currentlyUsedTypeMaxId;
275 	}
276 	
277 	/**
278 	 * This operation returns the next unused id for a new association.
279 	 * 
280 	 * @param associationName
281 	 *            is the name of the association for which a new id will be registered.
282 	 * @return the id
283 	 */
284 	public synchronized long pullNextUnusedAssociationID(final String associationName) {
285 		this.currentlyUsedAssociationMaxId += 1;
286 		this.associationNameIdRelation.setProperty(associationName, Long.toString(this.currentlyUsedAssociationMaxId));
287 		return this.currentlyUsedAssociationMaxId;
288 	}
289 	
290 	/**
291 	 * This operations delivers the id for a type by the name of the parameter.
292 	 * 
293 	 * @param typeName
294 	 *            the name of the type to look for
295 	 * @return the Id of the type when found
296 	 * @exception IDNotFoundForNameException
297 	 *                is thrown if no id was found.
298 	 */
299 	public long findIdForType(final String typeName) throws IDNotFoundForNameException {
300 		if (!this.typeNameIdRelation.containsKey(typeName)) {
301 			throw new IDNotFoundForNameException();
302 		}
303 		return Long.parseLong(this.typeNameIdRelation.getProperty(typeName));
304 	}
305 	
306 	/**
307 	 * This operations delivers the id for a association by the name of the parameter.
308 	 * 
309 	 * @param associationName
310 	 *            the name of the association to look for
311 	 * @return the id of the association if found
312 	 * @exception IDNotFoundForNameException
313 	 *                is thrown if no id was found.
314 	 */
315 	public long findIdForAssociation(final String associationName) throws IDNotFoundForNameException {
316 		if (!this.associationNameIdRelation.containsKey(associationName)) {
317 			throw new IDNotFoundForNameException();
318 		}
319 		return Long.parseLong(this.associationNameIdRelation.getProperty(associationName));
320 	}
321 }