KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > continuent > sequoia > controller > backup > BackupManager


1 /**
2  * Sequoia: Database clustering technology.
3  * Copyright (C) 2005 Emic Networks.
4  * Contact: sequoia@continuent.org
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * Initial developer(s): Emmanuel Cecchet.
19  * Contributor(s): ______________________.
20  */

21
22 package org.continuent.sequoia.controller.backup;
23
24 import java.io.BufferedInputStream JavaDoc;
25 import java.io.BufferedOutputStream JavaDoc;
26 import java.io.File JavaDoc;
27 import java.io.FileInputStream JavaDoc;
28 import java.io.FileOutputStream JavaDoc;
29 import java.io.IOException JavaDoc;
30 import java.io.InputStream JavaDoc;
31 import java.io.ObjectInputStream JavaDoc;
32 import java.io.ObjectOutputStream JavaDoc;
33 import java.io.OutputStream JavaDoc;
34 import java.net.InetAddress JavaDoc;
35 import java.net.InetSocketAddress JavaDoc;
36 import java.net.ServerSocket JavaDoc;
37 import java.net.Socket JavaDoc;
38 import java.util.HashMap JavaDoc;
39 import java.util.Iterator JavaDoc;
40 import java.util.Set JavaDoc;
41
42 import org.continuent.sequoia.common.exceptions.BackupException;
43 import org.continuent.sequoia.common.log.Trace;
44 import org.continuent.sequoia.common.xml.DatabasesXmlTags;
45 import org.continuent.sequoia.common.xml.XmlComponent;
46
47 /**
48  * This class defines a BackupManager that is responsible for registering
49  * backupers and retrieving them as needed for backup/restore operations.
50  *
51  * @author <a HREF="mailto:emmanuel.cecchet@emicnetworks.com">Emmanuel Cecchet</a>
52  * @version 1.0
53  */

54 public class BackupManager implements XmlComponent
55 {
56   static Trace logger = Trace.getLogger(BackupManager.class.getName());
57
58   /**
59    * This is a HashMap of backuperName -> Backuper HashMap<String,Backuper>
60    */

61   private HashMap JavaDoc backupers;
62
63   /**
64    * Creates a new <code>BackupManager</code> object
65    */

66   public BackupManager()
67   {
68     backupers = new HashMap JavaDoc();
69   }
70
71   /**
72    * Retrieve a backuper given its name. If the backuper has not been registered
73    * null is returned.
74    *
75    * @param name the backuper to look for
76    * @return the backuper or null if not found
77    */

78   public synchronized Backuper getBackuperByName(String JavaDoc name)
79   {
80     return (Backuper) backupers.get(name);
81   }
82
83   /**
84    * Get the names of the <code>Backupers</code> available from this
85    * <code>BackupManager</code>.
86    *
87    * @return an (possibly 0-sized) array of <code>String</code> representing
88    * the name of the <code>Backupers</code>
89    */

90   public synchronized String JavaDoc[] getBackuperNames()
91   {
92     Set JavaDoc backuperNames = backupers.keySet();
93     return (String JavaDoc[]) backuperNames.toArray(new String JavaDoc[backuperNames.size()]);
94   }
95
96   /**
97    * Get the first backuper that supports the given dump format. If no backuper
98    * supporting that format can be found, null is returned.
99    *
100    * @param format the dump format that the backuper must handle
101    * @return a backuper or null if not found
102    */

103   public synchronized Backuper getBackuperByFormat(String JavaDoc format)
104   {
105     if (format == null)
106       return null;
107     for (Iterator JavaDoc iter = backupers.values().iterator(); iter.hasNext();)
108     {
109       Backuper b = (Backuper) iter.next();
110       if (format.equals(b.getDumpFormat()))
111         return b;
112     }
113     return null;
114   }
115
116   /**
117    * Register a new backuper under a logical name.
118    *
119    * @param name backuper logical name
120    * @param backuper the backuper instance
121    * @throws BackupException if a backuper is null or a backuper has already
122    * been registered with the given name.
123    */

124   public synchronized void registerBackuper(String JavaDoc name, Backuper backuper)
125       throws BackupException
126   {
127     // Sanity checks
128
if (backupers.containsKey(name))
129       throw new BackupException(
130           "A backuper has already been registered with name " + name);
131     if (backuper == null)
132       throw new BackupException(
133           "Trying to register a null backuper under name " + name);
134     String JavaDoc dumpFormat = backuper.getDumpFormat();
135     if (dumpFormat == null)
136       throw new BackupException("Invalid null dump format for backuper " + name);
137
138     // Check that an already loaded backuper does no already handle that format
139
for (Iterator JavaDoc iter = backupers.values().iterator(); iter.hasNext();)
140     {
141       Backuper b = (Backuper) iter.next();
142       if (b.getDumpFormat().equals(dumpFormat))
143         throw new BackupException("Backuper " + b.getClass()
144             + " already handles " + dumpFormat + " dump format");
145     }
146
147     if (logger.isInfoEnabled())
148       logger.info("Registering backuper " + name + " to handle format "
149           + dumpFormat);
150
151     backupers.put(name, backuper);
152   }
153
154   /**
155    * Unregister a Backuper given its logical name.
156    *
157    * @param name the name of the backuper to unregister
158    * @return true if the backuper was removed successfully, false if it was not
159    * registered
160    */

161   public synchronized boolean unregisterBackuper(String JavaDoc name)
162   {
163     Object JavaDoc backuper = backupers.remove(name);
164
165     if (logger.isInfoEnabled() && (backuper != null))
166       logger.info("Unregistering backuper " + name + " that handled format "
167           + ((Backuper) backuper).getDumpFormat());
168
169     return backuper != null;
170   }
171
172   /**
173    * @see org.continuent.sequoia.common.xml.XmlComponent#getXml()
174    */

175   public synchronized String JavaDoc getXml()
176   {
177     StringBuffer JavaDoc sb = new StringBuffer JavaDoc("<" + DatabasesXmlTags.ELT_Backup + "> ");
178     for (Iterator JavaDoc iter = backupers.keySet().iterator(); iter.hasNext();)
179     {
180       String JavaDoc backuperName = (String JavaDoc) iter.next();
181       Backuper b = (Backuper) backupers.get(backuperName);
182       sb.append("<" + DatabasesXmlTags.ELT_Backuper + " "
183           + DatabasesXmlTags.ATT_backuperName + "=\"" + backuperName + "\" "
184           + DatabasesXmlTags.ATT_className + "=\"" + b.getClass().getName()
185           + "\" " + DatabasesXmlTags.ATT_options + "=\"" + b.getOptions()
186           + "\" />");
187     }
188     sb.append("</" + DatabasesXmlTags.ELT_Backup + ">");
189     return sb.toString();
190   }
191
192   /**
193    * Fetches a dumpFile from a remote dumpFileServer. The remote dump file to
194    * fetch is specified by its name and path. The connection to the remote
195    * dumpFileServer is initiated and authenticated using the specified
196    * DumpTransferInfo. The dump file is then fetched and stored locally at the
197    * same path as it was on the remote site.
198    *
199    * @param info the DumpTransferInfo specifying where to get the dump from.
200    * @param path the path where the dump is stored (both remote and local).
201    * @param dumpName the name of the remote dump to fetch.
202    * @throws IOException if a networking error occurs during the fetch process.
203    * @throws BackupException if an authentication error occurs, or if parameters
204    * are invalid.
205    */

206   public static void fetchDumpFile(DumpTransferInfo info, String JavaDoc path,
207       String JavaDoc dumpName) throws IOException JavaDoc, BackupException
208   {
209     //
210
// Phase 1: talk to dump server using it's very smart protocol
211
//
212
logger.info("Dump fetch starting from " + info.getBackuperServerAddress());
213
214     Socket JavaDoc soc = new Socket JavaDoc();
215
216     soc.connect(info.getBackuperServerAddress());
217
218     ObjectOutputStream JavaDoc oos = new ObjectOutputStream JavaDoc(soc.getOutputStream());
219
220     oos.writeLong(info.getSessionKey());
221     oos.writeObject(path);
222     oos.writeObject(dumpName);
223
224     // end of very smart protocol: read server response.
225
InputStream JavaDoc is = new BufferedInputStream JavaDoc(soc.getInputStream());
226     int response = is.read();
227     if (response != 0xEC) // server replies "EC" to say it's happy to carry on.
228
throw new BackupException("bad response from dump server");
229
230     //
231
// Phase 2: protocolar ablutions ok, go copy the stream into a local file
232
//
233
logger.info("Dump fetch authentication ok. Fetching " + path);
234
235     File JavaDoc thePath = new File JavaDoc(path);
236     if (!thePath.exists())
237       thePath.mkdirs();
238
239     File JavaDoc theFile = new File JavaDoc(path + File.separator + dumpName);
240     theFile.createNewFile();
241
242     OutputStream JavaDoc os = new BufferedOutputStream JavaDoc(new FileOutputStream JavaDoc(theFile));
243     int c = is.read();
244     while (c != -1)
245     {
246       os.write(c);
247       c = is.read();
248     }
249     os.flush();
250     os.close();
251
252     logger.info("Dump fetch done.");
253   }
254
255   /**
256    * Sets up a DumpFileServer for a remote client to use with fetchDumpFile.
257    *
258    * @param dumpServerIpAddress IP address of the dump server
259    * @return a DumpTransferInfo to be used by the client to connect and
260    * authenticate to this dumpFileServer.
261    * @throws IOException if the server socket can not be created.
262    */

263   public static DumpTransferInfo setupDumpFileServer(String JavaDoc dumpServerIpAddress)
264       throws IOException JavaDoc
265   {
266     ServerSocket JavaDoc soc = new ServerSocket JavaDoc();
267     soc.bind(null);
268
269     InetSocketAddress JavaDoc dumpServerAddress;
270     if (dumpServerIpAddress != null)
271     {
272       logger.info("Using provided dump-server address: " + dumpServerIpAddress);
273       dumpServerAddress = new InetSocketAddress JavaDoc(dumpServerIpAddress, soc
274           .getLocalPort());
275     }
276     else
277     {
278       logger.info("Using InetAddress.getLocalHost() as dump-server address: "
279           + InetAddress.getLocalHost());
280       dumpServerAddress = new InetSocketAddress JavaDoc(InetAddress.getLocalHost(), soc
281           .getLocalPort());
282     }
283
284     if (dumpServerAddress.getAddress() == null)
285       throw new IOException JavaDoc(
286           "Cannot resolve provided IP address for dump server ("
287               + dumpServerAddress + ")");
288     else if (dumpServerAddress.getAddress().isLoopbackAddress())
289       throw new IOException JavaDoc(
290           "NOT setting-up a dump server on a loopback address.\n"
291               + "Please update your network configuration "
292               + "or specify option dumpServer in vdb.xml <Backuper ... option=/>");
293
294     long sessionKey = soc.hashCode();
295     new DumpTransferServerThread(soc, sessionKey).start();
296
297     // FIXME: sending the address we are listening to is dirty because:
298
// - it prevents us from listening to any interface/address
299
// - we may listen on some wrong interface/address!
300
// The Right Way to do this would be to bootstrap this socket from the
301
// existing inter-controller communication/addresses, this way we would be
302
// 100% sure to succeed.
303
return new DumpTransferInfo(dumpServerAddress, sessionKey);
304   }
305
306   /**
307    * Sets up a DumpFileServer for a remote client to use with fetchDumpFile.
308    *
309    * @return a DumpTransferInfo to be used by the client to connect and
310    * authenticate to this dumpFileServer.
311    * @throws IOException if the server socket can not be created.
312    */

313   public static DumpTransferInfo setupDumpFileServer() throws IOException JavaDoc
314   {
315     return setupDumpFileServer(null);
316   }
317
318   static class DumpTransferServerThread extends Thread JavaDoc
319   {
320     private ServerSocket JavaDoc serverSocket;
321     private long sessionKey;
322
323     public void run()
324     {
325       try
326       {
327         logger.info("Dump server started @ "
328             + serverSocket.getLocalSocketAddress());
329         //
330
// Wait for client to connect
331
//
332
Socket JavaDoc soc = serverSocket.accept();
333
334         logger.info("Client connected to dump server from "
335             + soc.getRemoteSocketAddress());
336
337         ObjectInputStream JavaDoc ois = new ObjectInputStream JavaDoc(soc.getInputStream());
338
339         //
340
// Phase 1: server side very smart protocol to authenticate client
341
//
342
long key = ois.readLong();
343
344         if (key != this.sessionKey)
345         {
346           logger.error("Bad session key from client: "
347               + soc.getRemoteSocketAddress());
348           soc.close();
349           return; // read will fail on client side
350
}
351
352         String JavaDoc path = (String JavaDoc) ois.readObject();
353         String JavaDoc dumpName = (String JavaDoc) ois.readObject();
354
355         File JavaDoc theFile = new File JavaDoc(path + File.separator + dumpName);
356
357         if (!theFile.exists())
358         {
359           logger.error("Requested dump does not exist: " + theFile.getPath());
360           soc.close();
361           return;
362         }
363
364         InputStream JavaDoc is = new BufferedInputStream JavaDoc(new FileInputStream JavaDoc(theFile));
365         OutputStream JavaDoc os = new BufferedOutputStream JavaDoc(soc.getOutputStream());
366
367         // end of very smart protocol: return "EC" to client to say it's ok to
368
// fetch the dump
369
os.write(0xEC);
370
371         //
372
// Phase 2: burst the dump file over the wire.
373
//
374
int c = is.read();
375         while (c != -1)
376         {
377           os.write(c);
378           c = is.read();
379         }
380         os.flush();
381         os.close();
382
383         logger.info("Dump server terminated.");
384       }
385       catch (Exception JavaDoc e)
386       {
387         logger.error(e);
388       }
389     }
390
391     DumpTransferServerThread(ServerSocket JavaDoc serverSocket, long sessionKey)
392     {
393       setName("DumpTransfer server thread");
394       this.serverSocket = serverSocket;
395       this.sessionKey = sessionKey;
396     }
397   }
398
399 }
Popular Tags