001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.execute.JarUtil
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.sql.execute;
023:
024: import org.apache.derby.iapi.reference.Property;
025: import org.apache.derby.iapi.util.IdUtil;
026: import org.apache.derby.impl.sql.execute.JarDDL;
027: import org.apache.derby.iapi.services.property.PropertyUtil;
028: import org.apache.derby.iapi.services.loader.ClassFactory;
029: import org.apache.derby.iapi.services.context.ContextService;
030: import org.apache.derby.iapi.services.sanity.SanityManager;
031: import org.apache.derby.iapi.error.StandardException;
032: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
033: import org.apache.derby.iapi.sql.dictionary.DataDescriptorGenerator;
034: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
035: import org.apache.derby.iapi.sql.dictionary.FileInfoDescriptor;
036: import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
037:
038: import org.apache.derby.iapi.sql.depend.DependencyManager;
039: import org.apache.derby.iapi.reference.SQLState;
040: import org.apache.derby.iapi.store.access.FileResource;
041: import org.apache.derby.catalog.UUID;
042: import org.apache.derby.iapi.services.io.FileUtil;
043: import org.apache.derby.io.StorageFile;
044:
045: import java.io.IOException;
046: import java.io.InputStream;
047: import java.sql.CallableStatement;
048: import java.sql.Connection;
049: import java.sql.SQLException;
050:
051: public class JarUtil {
052: public static final String ADD_JAR_DDL = "ADD JAR";
053: public static final String DROP_JAR_DDL = "DROP JAR";
054: public static final String REPLACE_JAR_DDL = "REPLACE JAR";
055: public static final String READ_JAR = "READ JAR";
056: //
057: //State passed in by the caller
058: private UUID id; //For add null means create a new id.
059: private String schemaName;
060: private String sqlName;
061:
062: //Derived state
063: private LanguageConnectionContext lcc;
064: private FileResource fr;
065: private DataDictionary dd;
066: private DataDescriptorGenerator ddg;
067:
068: //
069: //State derived from the caller's context
070: public JarUtil(UUID id, String schemaName, String sqlName)
071: throws StandardException {
072: this .id = id;
073: this .schemaName = schemaName;
074: this .sqlName = sqlName;
075:
076: lcc = (LanguageConnectionContext) ContextService
077: .getContext(LanguageConnectionContext.CONTEXT_ID);
078: fr = lcc.getTransactionExecute().getFileHandler();
079: dd = lcc.getDataDictionary();
080: ddg = dd.getDataDescriptorGenerator();
081: }
082:
083: /**
084: Add a jar file to the current connection's database.
085:
086: @param id The id for the jar file we add. If null this makes up a new id.
087: @param schemaName the name for the schema that holds the jar file.
088: @param sqlName the sql name for the jar file.
089: @param externalPath the path for the jar file to add.
090: @return The generationId for the jar file we add.
091:
092: @exception StandardException Opps
093: */
094: static public long add(UUID id, String schemaName, String sqlName,
095: String externalPath) throws StandardException {
096: JarUtil jutil = new JarUtil(id, schemaName, sqlName);
097: InputStream is = null;
098:
099: try {
100: is = FileUtil.getInputStream(externalPath, 0);
101: return jutil.add(is);
102: } catch (java.io.IOException fnfe) {
103: throw StandardException.newException(
104: SQLState.SQLJ_INVALID_JAR, fnfe, externalPath);
105: } finally {
106: try {
107: if (is != null)
108: is.close();
109: } catch (IOException ioe) {
110: }
111: }
112: }
113:
114: /**
115: Add a jar file to the current connection's database.
116:
117: <P> The reason for adding the jar file in this private instance
118: method is that it allows us to share set up logic with drop and
119: replace.
120: @param is A stream for reading the content of the file to add.
121: @exception StandardException Opps
122: */
123: public long add(InputStream is) throws StandardException {
124: //
125: //Like create table we say we are writing before we read the dd
126: dd.startWriting(lcc);
127: FileInfoDescriptor fid = getInfo();
128: if (fid != null)
129: throw StandardException.newException(
130: SQLState.LANG_OBJECT_ALREADY_EXISTS_IN_OBJECT, fid
131: .getDescriptorType(), sqlName, fid
132: .getSchemaDescriptor().getDescriptorType(),
133: schemaName);
134:
135: try {
136: notifyLoader(false);
137: dd.invalidateAllSPSPlans();
138: long generationId = fr.add(JarDDL.mkExternalName(
139: schemaName, sqlName, fr.getSeparatorChar()), is);
140:
141: SchemaDescriptor sd = dd.getSchemaDescriptor(schemaName,
142: null, true);
143:
144: fid = ddg.newFileInfoDescriptor(id, sd, sqlName,
145: generationId);
146: dd.addDescriptor(fid, sd,
147: DataDictionary.SYSFILES_CATALOG_NUM, false, lcc
148: .getTransactionExecute());
149: return generationId;
150: } finally {
151: notifyLoader(true);
152: }
153: }
154:
155: /**
156: Drop a jar file from the current connection's database.
157:
158: @param id The id for the jar file we drop. Ignored if null.
159: @param schemaName the name for the schema that holds the jar file.
160: @param sqlName the sql name for the jar file.
161: @param purgeOnCommit True means purge the old jar file on commit. False
162: means leave it around for use by replication.
163:
164: @exception StandardException Opps
165: */
166: static public void drop(UUID id, String schemaName, String sqlName,
167: boolean purgeOnCommit) throws StandardException {
168: JarUtil jutil = new JarUtil(id, schemaName, sqlName);
169: jutil.drop(purgeOnCommit);
170: }
171:
172: /**
173: Drop a jar file from the current connection's database.
174:
175: <P> The reason for dropping the jar file in this private instance
176: method is that it allows us to share set up logic with add and
177: replace.
178: @param purgeOnCommit True means purge the old jar file on commit. False
179: means leave it around for use by replication.
180:
181: @exception StandardException Opps
182: */
183: public void drop(boolean purgeOnCommit) throws StandardException {
184: //
185: //Like create table we say we are writing before we read the dd
186: dd.startWriting(lcc);
187: FileInfoDescriptor fid = getInfo();
188: if (fid == null)
189: throw StandardException.newException(
190: SQLState.LANG_FILE_DOES_NOT_EXIST, sqlName,
191: schemaName);
192:
193: if (SanityManager.DEBUG) {
194: if (id != null && !fid.getUUID().equals(id)) {
195: SanityManager.THROWASSERT("Drop id mismatch want=" + id
196: + " have " + fid.getUUID());
197: }
198: }
199:
200: String dbcp_s = PropertyUtil.getServiceProperty(lcc
201: .getTransactionExecute(), Property.DATABASE_CLASSPATH);
202: if (dbcp_s != null) {
203: String[][] dbcp = IdUtil.parseDbClassPath(dbcp_s, lcc
204: .getIdentifierCasing() != lcc.ANTI_ANSI_CASING);
205: boolean found = false;
206: //
207: //Look for the jar we are dropping on our database classpath.
208: //We don't concern ourselves with 3 part names since they may
209: //refer to a jar file in another database and may not occur in
210: //a database classpath that is stored in the propert congomerate.
211: for (int ix = 0; ix < dbcp.length; ix++)
212: if (dbcp.length == 2 && dbcp[ix][0].equals(schemaName)
213: && dbcp[ix][1].equals(sqlName))
214: found = true;
215: if (found)
216: throw StandardException
217: .newException(
218: SQLState.LANG_CANT_DROP_JAR_ON_DB_CLASS_PATH_DURING_EXECUTION,
219: IdUtil.mkQualifiedName(schemaName,
220: sqlName), dbcp_s);
221: }
222:
223: try {
224:
225: notifyLoader(false);
226: dd.invalidateAllSPSPlans();
227: DependencyManager dm = dd.getDependencyManager();
228: dm.invalidateFor(fid, DependencyManager.DROP_JAR, lcc);
229:
230: dd.dropFileInfoDescriptor(fid);
231:
232: fr
233: .remove(JarDDL.mkExternalName(schemaName, sqlName,
234: fr.getSeparatorChar()), fid
235: .getGenerationId(), true /*purgeOnCommit*/);
236: } finally {
237: notifyLoader(true);
238: }
239: }
240:
241: /**
242: Replace a jar file from the current connection's database with the content of an
243: external file.
244:
245:
246: @param id The id for the jar file we add. Ignored if null.
247: @param schemaName the name for the schema that holds the jar file.
248: @param sqlName the sql name for the jar file.
249: @param externalPath the path for the jar file to add.
250: @param purgeOnCommit True means purge the old jar file on commit. False
251: means leave it around for use by replication.
252: @return The new generationId for the jar file we replace.
253:
254: @exception StandardException Opps
255: */
256: static public long replace(UUID id, String schemaName,
257: String sqlName, String externalPath, boolean purgeOnCommit)
258: throws StandardException {
259: JarUtil jutil = new JarUtil(id, schemaName, sqlName);
260: InputStream is = null;
261:
262: try {
263: is = FileUtil.getInputStream(externalPath, 0);
264:
265: return jutil.replace(is, purgeOnCommit);
266: } catch (java.io.IOException fnfe) {
267: throw StandardException.newException(
268: SQLState.SQLJ_INVALID_JAR, fnfe, externalPath);
269: } finally {
270: try {
271: if (is != null)
272: is.close();
273: } catch (IOException ioe) {
274: }
275: }
276: }
277:
278: /**
279: Replace a jar file in the current connection's database with the
280: content of an external file.
281:
282: <P> The reason for adding the jar file in this private instance
283: method is that it allows us to share set up logic with add and
284: drop.
285: @param is An input stream for reading the new content of the jar file.
286: @param purgeOnCommit True means purge the old jar file on commit. False
287: means leave it around for use by replication.
288: @exception StandardException Opps
289: */
290: public long replace(InputStream is, boolean purgeOnCommit)
291: throws StandardException {
292: //
293: //Like create table we say we are writing before we read the dd
294: dd.startWriting(lcc);
295:
296: //
297: //Temporarily drop the FileInfoDescriptor from the data dictionary.
298: FileInfoDescriptor fid = getInfo();
299: if (fid == null)
300: throw StandardException.newException(
301: SQLState.LANG_FILE_DOES_NOT_EXIST, sqlName,
302: schemaName);
303:
304: if (SanityManager.DEBUG) {
305: if (id != null && !fid.getUUID().equals(id)) {
306: SanityManager.THROWASSERT("Replace id mismatch want="
307: + id + " have " + fid.getUUID());
308: }
309: }
310:
311: try {
312: // disable loads from this jar
313: notifyLoader(false);
314: dd.invalidateAllSPSPlans();
315: dd.dropFileInfoDescriptor(fid);
316:
317: //
318: //Replace the file.
319: long generationId = fr.replace(JarDDL.mkExternalName(
320: schemaName, sqlName, fr.getSeparatorChar()), fid
321: .getGenerationId(), is, purgeOnCommit);
322:
323: //
324: //Re-add the descriptor to the data dictionary.
325: FileInfoDescriptor fid2 = ddg.newFileInfoDescriptor(fid
326: .getUUID(), fid.getSchemaDescriptor(), sqlName,
327: generationId);
328: dd.addDescriptor(fid2, fid.getSchemaDescriptor(),
329: DataDictionary.SYSFILES_CATALOG_NUM, false, lcc
330: .getTransactionExecute());
331: return generationId;
332:
333: } finally {
334:
335: // reenable class loading from this jar
336: notifyLoader(true);
337: }
338: }
339:
340: /**
341: Get the FileInfoDescriptor for a jar file from the current connection's database or
342: null if it does not exist.
343:
344: @param schemaName the name for the schema that holds the jar file.
345: @param sqlName the sql name for the jar file.
346: @return The FileInfoDescriptor.
347: @exception StandardException Opps
348: */
349: public static FileInfoDescriptor getInfo(String schemaName,
350: String sqlName, String statementType)
351: throws StandardException {
352: JarUtil jUtil = new JarUtil(null, schemaName, sqlName);
353: return jUtil.getInfo();
354: }
355:
356: /**
357: Get the FileInfoDescriptor for the Jar file or null if it does not exist.
358: @exception StandardException Ooops
359: */
360: private FileInfoDescriptor getInfo() throws StandardException {
361: SchemaDescriptor sd = dd.getSchemaDescriptor(schemaName, null,
362: true);
363: return dd.getFileInfoDescriptor(sd, sqlName);
364: }
365:
366: // get the current version of the jar file as a File or InputStream
367: public static Object getAsObject(String schemaName, String sqlName)
368: throws StandardException {
369: JarUtil jUtil = new JarUtil(null, schemaName, sqlName);
370:
371: FileInfoDescriptor fid = jUtil.getInfo();
372: if (fid == null)
373: throw StandardException.newException(
374: SQLState.LANG_FILE_DOES_NOT_EXIST, sqlName,
375: schemaName);
376:
377: long generationId = fid.getGenerationId();
378:
379: StorageFile f = jUtil.getAsFile(generationId);
380: if (f != null)
381: return f;
382:
383: return jUtil.getAsStream(generationId);
384: }
385:
386: private StorageFile getAsFile(long generationId) {
387: return fr.getAsFile(JarDDL.mkExternalName(schemaName, sqlName,
388: fr.getSeparatorChar()), generationId);
389: }
390:
391: public static InputStream getAsStream(String schemaName,
392: String sqlName, long generationId) throws StandardException {
393: JarUtil jUtil = new JarUtil(null, schemaName, sqlName);
394:
395: return jUtil.getAsStream(generationId);
396: }
397:
398: private InputStream getAsStream(long generationId)
399: throws StandardException {
400: try {
401: return fr.getAsStream(JarDDL.mkExternalName(schemaName,
402: sqlName, fr.getSeparatorChar()), generationId);
403: } catch (IOException ioe) {
404: throw StandardException.newException(
405: SQLState.LANG_FILE_ERROR, ioe, ioe.toString());
406: }
407: }
408:
409: private void notifyLoader(boolean reload) throws StandardException {
410: ClassFactory cf = lcc.getLanguageConnectionFactory()
411: .getClassFactory();
412: cf.notifyModifyJar(reload);
413: }
414: }
|