Line 0
Link Here
|
|
|
1 |
/* |
2 |
* Licensed to the Apache Software Foundation (ASF) under one or more |
3 |
* contributor license agreements. See the NOTICE file distributed with |
4 |
* this work for additional information regarding copyright ownership. |
5 |
* The ASF licenses this file to You under the Apache License, Version 2.0 |
6 |
* (the "License"); you may not use this file except in compliance with |
7 |
* the License. You may obtain a copy of the License at |
8 |
* |
9 |
* http://www.apache.org/licenses/LICENSE-2.0 |
10 |
* |
11 |
* Unless required by applicable law or agreed to in writing, software |
12 |
* distributed under the License is distributed on an "AS IS" BASIS, |
13 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 |
* See the License for the specific language governing permissions and |
15 |
* limitations under the License. |
16 |
*/ |
17 |
|
18 |
package org.apache.catalina.manager.host; |
19 |
|
20 |
import java.io.File; |
21 |
import java.io.FileInputStream; |
22 |
import java.io.FileOutputStream; |
23 |
import java.io.IOException; |
24 |
import java.nio.channels.FileChannel; |
25 |
import java.util.Date; |
26 |
|
27 |
import javax.xml.parsers.DocumentBuilder; |
28 |
import javax.xml.parsers.DocumentBuilderFactory; |
29 |
import javax.xml.parsers.ParserConfigurationException; |
30 |
import javax.xml.transform.OutputKeys; |
31 |
import javax.xml.transform.Result; |
32 |
import javax.xml.transform.Source; |
33 |
import javax.xml.transform.Transformer; |
34 |
import javax.xml.transform.TransformerConfigurationException; |
35 |
import javax.xml.transform.TransformerException; |
36 |
import javax.xml.transform.TransformerFactory; |
37 |
import javax.xml.transform.TransformerFactoryConfigurationError; |
38 |
import javax.xml.transform.dom.DOMSource; |
39 |
import javax.xml.transform.stream.StreamResult; |
40 |
|
41 |
import org.apache.catalina.Engine; |
42 |
import org.apache.catalina.Host; |
43 |
import org.apache.catalina.Service; |
44 |
import org.apache.catalina.core.StandardHost; |
45 |
import org.apache.catalina.startup.Catalina; |
46 |
import org.w3c.dom.Comment; |
47 |
import org.w3c.dom.Document; |
48 |
import org.w3c.dom.Element; |
49 |
import org.w3c.dom.NodeList; |
50 |
import org.xml.sax.SAXException; |
51 |
|
52 |
/** |
53 |
* Responsible for adding and removing hosts from server.xml. |
54 |
* |
55 |
* @author Wesley |
56 |
* |
57 |
*/ |
58 |
public class HostXMLUpdater { |
59 |
|
60 |
/** |
61 |
* The directory where tomcat resides. |
62 |
*/ |
63 |
private String baseFileName; |
64 |
/** |
65 |
* The name of the configuration file that has the hosts defined in it. This |
66 |
* is usually conf/server.xml. |
67 |
*/ |
68 |
private String confFileName; |
69 |
/** |
70 |
* The name of the file where the contents will be initially written. |
71 |
*/ |
72 |
private String tempFileName; |
73 |
/** |
74 |
* The name of the file which will contain a backup of the old settings. |
75 |
*/ |
76 |
private String backupFileName; |
77 |
|
78 |
public HostXMLUpdater() { |
79 |
init(); |
80 |
} |
81 |
|
82 |
synchronized private void init() { |
83 |
confFileName = System.getProperty(Catalina.CONFIG_FILE_PROPERTY); |
84 |
baseFileName = System.getProperty("catalina.base"); |
85 |
} |
86 |
|
87 |
/** |
88 |
* Saves a host to the xml configuration file. |
89 |
* |
90 |
* @param host |
91 |
* The host to save as xml. |
92 |
*/ |
93 |
public void addHost(StandardHost host) { |
94 |
|
95 |
try { |
96 |
FileReferences ref = createBackupAndTempFiles(); |
97 |
|
98 |
Document document = createDocumentFromFile(ref.getTempFile()); |
99 |
|
100 |
HostParents hostParents = findParents(host); |
101 |
Element engineNode = findHostParentNode(hostParents, document); |
102 |
Element hostNode = createHostNode(document, host); |
103 |
engineNode.appendChild(hostNode); |
104 |
// Following is debug code |
105 |
Transformer aTransformer = createTransformer(); |
106 |
writeToTempFile(ref, document, aTransformer); |
107 |
replaceCurrentConfFileWithTempFile(ref); |
108 |
} catch (Exception e) { |
109 |
// FIXME Need to decide what to do on a failure. |
110 |
e.printStackTrace(); |
111 |
} finally { |
112 |
// FIXME cleanup files |
113 |
} |
114 |
|
115 |
} |
116 |
|
117 |
/** |
118 |
* Deletes the current file and replaces it with the temp file. |
119 |
* |
120 |
* @param ref |
121 |
* References to the temp and current files. |
122 |
*/ |
123 |
private void replaceCurrentConfFileWithTempFile(FileReferences ref) { |
124 |
ref.currentFile.delete(); |
125 |
|
126 |
ref.tempFile.renameTo(ref.currentFile); |
127 |
} |
128 |
|
129 |
/** |
130 |
* Writes output to tempory file. |
131 |
* |
132 |
* @param ref |
133 |
* A reference to the current and temp and backup files. |
134 |
* @param document |
135 |
* The document to be written to the temp file. |
136 |
* @param transformer |
137 |
* The transformer which writes to the temp file. |
138 |
* @throws TransformerException |
139 |
* If an unrecoverable error occurs during the course of the |
140 |
* transformation. |
141 |
*/ |
142 |
private void writeToTempFile(FileReferences ref, Document document, |
143 |
Transformer transformer) throws TransformerException { |
144 |
Source src = new DOMSource(document); |
145 |
Result dest = new StreamResult(ref.tempFile); |
146 |
transformer.transform(src, dest); |
147 |
} |
148 |
|
149 |
/** |
150 |
* @return A Transformer to transform the document to file. |
151 |
* @throws TransformerFactoryConfigurationError |
152 |
* Thrown if the TransformerFactory implementation is not |
153 |
* available or cannot be instantiated. |
154 |
* @throws TransformerConfigurationException |
155 |
* When it is not possible to create a transformer. |
156 |
*/ |
157 |
private Transformer createTransformer() |
158 |
throws TransformerFactoryConfigurationError, |
159 |
TransformerConfigurationException { |
160 |
TransformerFactory tranFactory = TransformerFactory.newInstance(); |
161 |
Transformer transformer = tranFactory.newTransformer(); |
162 |
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); |
163 |
return transformer; |
164 |
} |
165 |
|
166 |
/** |
167 |
* Removes a host from the configuration file. |
168 |
* |
169 |
* @param host |
170 |
* The host to remove. |
171 |
*/ |
172 |
public void removeHost(Host host) { |
173 |
|
174 |
try { |
175 |
FileReferences ref = createBackupAndTempFiles(); |
176 |
|
177 |
Document document = createDocumentFromFile(ref.getTempFile()); |
178 |
|
179 |
HostParents hostParents = findParents(host); |
180 |
Element engineNode = findHostParentNode(hostParents, document); |
181 |
deleteHostNode(document, engineNode, host); |
182 |
Transformer aTransformer = createTransformer(); |
183 |
writeToTempFile(ref, document, aTransformer); |
184 |
replaceCurrentConfFileWithTempFile(ref); |
185 |
} catch (Exception e) { |
186 |
e.printStackTrace(); |
187 |
} finally { |
188 |
// FIXME cleanup files |
189 |
} |
190 |
|
191 |
} |
192 |
|
193 |
/** |
194 |
* Finds a host by name and removes it from a document. |
195 |
* |
196 |
* @param document |
197 |
* The document to remove the host from. |
198 |
* @param engineNode |
199 |
* The node to remove the host from. |
200 |
* @param hostThe |
201 |
* host to remove. |
202 |
*/ |
203 |
private void deleteHostNode(Document document, Element engineNode, Host host) { |
204 |
NodeList hostNodes = engineNode.getElementsByTagName("Host"); |
205 |
for (int i = 0; i < hostNodes.getLength(); i++) { |
206 |
Element currentElement = (Element) hostNodes.item(i); |
207 |
String name = currentElement.getAttribute("name"); |
208 |
if (name.equals(host.getName())) { |
209 |
engineNode.removeChild(currentElement); |
210 |
} |
211 |
} |
212 |
|
213 |
} |
214 |
|
215 |
/** |
216 |
* Creates a host node. |
217 |
* |
218 |
* @param document |
219 |
* The document to create the node on. |
220 |
* @param host |
221 |
* The host to represent as XML. |
222 |
* @return A newly created XML element representing the host. |
223 |
*/ |
224 |
private Element createHostNode(Document document, StandardHost host) { |
225 |
Element hostNode = document.createElement("Host"); |
226 |
|
227 |
hostNode.setAttribute("name", host.getName()); |
228 |
|
229 |
hostNode.setAttribute("appBase", host.getAppBase()); |
230 |
hostNode.setAttribute("autoDeploy", Boolean.toString(host |
231 |
.getAutoDeploy())); |
232 |
hostNode.setAttribute("deployOnStartup", Boolean.toString(host |
233 |
.getDeployOnStartup())); |
234 |
hostNode |
235 |
.setAttribute("deployXML", Boolean.toString(host.isDeployXML())); |
236 |
String[] aliases = host.getAliases(); |
237 |
|
238 |
Comment creationComment = document |
239 |
.createComment("Automatically created host on " |
240 |
+ (new Date().toString())); |
241 |
hostNode.appendChild(creationComment); |
242 |
for (int i = 0; i < aliases.length; i++) { |
243 |
Element alias = document.createElement("Alias"); |
244 |
alias.setTextContent(aliases[i]); |
245 |
hostNode.appendChild(alias); |
246 |
} |
247 |
return hostNode; |
248 |
} |
249 |
|
250 |
/** |
251 |
* Finds the parent nodes of a host node. |
252 |
* |
253 |
* First finds the service by name then the engine. |
254 |
* |
255 |
* @param hostParents |
256 |
* The parents of the node to find. |
257 |
* @param document |
258 |
* The document to find the XML representation of the parents on. |
259 |
* @return An element representing the engine the host is attached to. |
260 |
*/ |
261 |
private Element findHostParentNode(HostParents hostParents, |
262 |
Document document) { |
263 |
try { |
264 |
NodeList serviceNodeList = document.getElementsByTagName("Service"); |
265 |
Element serviceNode = (Element) serviceNodeList.item(0); |
266 |
for (int i = 0; i < serviceNodeList.getLength(); i++) { |
267 |
serviceNode = (Element) serviceNodeList.item(i); |
268 |
String name = serviceNode.getAttribute("name"); |
269 |
if (name.equals(hostParents.getService().getName())) { |
270 |
break; |
271 |
} |
272 |
} |
273 |
NodeList engineNodeList = serviceNode |
274 |
.getElementsByTagName("Engine"); |
275 |
if (engineNodeList.getLength() != 1) { |
276 |
throw new ServiceParseException( |
277 |
"Unable to parse server.xml. There should only be one engine associated with a given service."); |
278 |
} |
279 |
return ((Element) engineNodeList.item(0)); |
280 |
} catch (RuntimeException e) { |
281 |
throw new ServiceParseException(e); |
282 |
} |
283 |
} |
284 |
|
285 |
/** |
286 |
* Finds the service and engine associated with a host. |
287 |
* |
288 |
* @param host |
289 |
* The host which we need too find the parents of. |
290 |
* @return An object representing the service and the engine that parent the |
291 |
* host. |
292 |
*/ |
293 |
private HostParents findParents(Host host) { |
294 |
Engine engine = (Engine) host.getParent(); |
295 |
|
296 |
Service service = engine.getService(); |
297 |
HostParents parents = new HostParents(); |
298 |
parents.setEngine(engine); |
299 |
parents.setService(service); |
300 |
return parents; |
301 |
} |
302 |
|
303 |
private FileReferences createBackupAndTempFiles() throws IOException { |
304 |
FileReferences ref = createFileRefrences(); |
305 |
|
306 |
copyFile(ref.getCurrentFile(), ref.getBackupFile()); |
307 |
copyFile(ref.getCurrentFile(), ref.getTempFile()); |
308 |
return ref; |
309 |
} |
310 |
|
311 |
/** |
312 |
* Creates a document from a file. |
313 |
* |
314 |
* @param file |
315 |
* The file to parse to create a document. |
316 |
* @return The document instance that is within the file. |
317 |
* @throws ParserConfigurationException |
318 |
* if a DocumentBuilder cannot be created which satisfies the |
319 |
* configuration requested. |
320 |
* @throws SAXException |
321 |
* If there is an error parsing. |
322 |
* @throws IOException |
323 |
* If there is any IO errors. |
324 |
*/ |
325 |
private Document createDocumentFromFile(File file) |
326 |
throws ParserConfigurationException, SAXException, IOException { |
327 |
DocumentBuilderFactory builderFactory = DocumentBuilderFactory |
328 |
.newInstance(); |
329 |
DocumentBuilder parser = builderFactory.newDocumentBuilder(); |
330 |
Document document = parser.parse(file); |
331 |
return document; |
332 |
} |
333 |
|
334 |
/** |
335 |
* Creates references to the current configuration file, the backup file and |
336 |
* a temporary file. |
337 |
* |
338 |
* @return A reference to the temporary configuration file, the backup file |
339 |
* and the current configuration file. |
340 |
*/ |
341 |
private FileReferences createFileRefrences() { |
342 |
File currentFile = createFileReference(confFileName); |
343 |
// Make two copies of the current file. |
344 |
String tempFileName = confFileName + ".tmp"; |
345 |
File tempFile = createFileReference(tempFileName); |
346 |
|
347 |
String backupFileName = confFileName + ".bak"; |
348 |
File backupFile = createFileReference(backupFileName); |
349 |
FileReferences ref = new FileReferences(); |
350 |
ref.setBackupFile(backupFile); |
351 |
ref.setCurrentFile(currentFile); |
352 |
ref.setTempFile(tempFile); |
353 |
return ref; |
354 |
} |
355 |
|
356 |
/** |
357 |
* Creates a file reference representing the filename passed in. Creates all |
358 |
* directories that are needed to create the file. |
359 |
* |
360 |
* @param fileName |
361 |
* @return |
362 |
*/ |
363 |
private File createFileReference(String fileName) { |
364 |
File currentFile = new File(fileName); |
365 |
if (!currentFile.isAbsolute()) { |
366 |
currentFile = new File(baseFileName, fileName); |
367 |
} |
368 |
currentFile.getParentFile().mkdirs(); |
369 |
return currentFile; |
370 |
} |
371 |
|
372 |
/** |
373 |
* @param confFileName |
374 |
* The name of the current configuration file. Usually |
375 |
* "conf/server.xml". |
376 |
*/ |
377 |
public void setConfFileName(String confFileName) { |
378 |
this.confFileName = confFileName; |
379 |
} |
380 |
|
381 |
/** |
382 |
* @return The name of the current configuration file. Usually |
383 |
* "conf/server.xml". |
384 |
*/ |
385 |
public String getConfFileName() { |
386 |
return confFileName; |
387 |
} |
388 |
|
389 |
/** |
390 |
* @param tempFileName |
391 |
* The file where the temporary contents will be written before |
392 |
* Overwriting the main configuration file. |
393 |
*/ |
394 |
public void setTempFileName(String tempFileName) { |
395 |
this.tempFileName = tempFileName; |
396 |
} |
397 |
|
398 |
/** |
399 |
* |
400 |
* @return The file where the temporary contents will be written before |
401 |
* Overwriting the main configuration file. |
402 |
*/ |
403 |
public String getTempFileName() { |
404 |
return tempFileName; |
405 |
} |
406 |
|
407 |
/** |
408 |
* @param backupFileName |
409 |
* The name of the backup file. |
410 |
*/ |
411 |
public void setBackupFileName(String backupFileName) { |
412 |
this.backupFileName = backupFileName; |
413 |
} |
414 |
|
415 |
/** |
416 |
* @return The name of the backup file. |
417 |
*/ |
418 |
public String getBackupFileName() { |
419 |
return backupFileName; |
420 |
} |
421 |
|
422 |
/** |
423 |
* @param baseFileName |
424 |
* The location of catalina.base. |
425 |
*/ |
426 |
public void setBaseFileName(String baseFileName) { |
427 |
this.baseFileName = baseFileName; |
428 |
} |
429 |
|
430 |
/** |
431 |
* @return The location of catalina.base. |
432 |
*/ |
433 |
public String getBaseFileName() { |
434 |
return baseFileName; |
435 |
} |
436 |
|
437 |
/** |
438 |
* Copies a file to another file. |
439 |
* |
440 |
* @param sourceFile |
441 |
* The file to copy. |
442 |
* @param destFile |
443 |
* The file to write. |
444 |
* @throws IOException |
445 |
* If sourceFile cannot be read or destFile cannot be written. |
446 |
* |
447 |
* @see http://www.javalobby.org/java/forums/t17036.html |
448 |
*/ |
449 |
void copyFile(File sourceFile, File destFile) throws IOException { |
450 |
if (!destFile.exists()) { |
451 |
destFile.createNewFile(); |
452 |
} |
453 |
|
454 |
FileChannel source = null; |
455 |
FileChannel destination = null; |
456 |
try { |
457 |
source = new FileInputStream(sourceFile).getChannel(); |
458 |
destination = new FileOutputStream(destFile).getChannel(); |
459 |
destination.transferFrom(source, 0, source.size()); |
460 |
} finally { |
461 |
if (source != null) { |
462 |
source.close(); |
463 |
} |
464 |
} |
465 |
if (destination != null) { |
466 |
destination.close(); |
467 |
} |
468 |
} |
469 |
|
470 |
/** |
471 |
* Simply used as a private return value. |
472 |
* |
473 |
* Represents the three files the current file, the backup file and a |
474 |
* Temporary file. |
475 |
* |
476 |
* @author Wesley |
477 |
* |
478 |
*/ |
479 |
private class FileReferences { |
480 |
private File currentFile; |
481 |
private File tempFile; |
482 |
private File backupFile; |
483 |
|
484 |
/** |
485 |
* Constructor |
486 |
*/ |
487 |
public FileReferences() { |
488 |
} |
489 |
|
490 |
/** |
491 |
* @return The current file. |
492 |
*/ |
493 |
public File getCurrentFile() { |
494 |
return currentFile; |
495 |
} |
496 |
|
497 |
/** |
498 |
* @param The current file. |
499 |
*/ |
500 |
public void setCurrentFile(File currentFile) { |
501 |
this.currentFile = currentFile; |
502 |
} |
503 |
|
504 |
/** |
505 |
* @return A temporary file that will be used to update the current file. |
506 |
*/ |
507 |
public File getTempFile() { |
508 |
return tempFile; |
509 |
} |
510 |
|
511 |
/** |
512 |
* @param tempFile A temporary file that will be used to update the current file. |
513 |
*/ |
514 |
public void setTempFile(File tempFile) { |
515 |
this.tempFile = tempFile; |
516 |
} |
517 |
|
518 |
/** |
519 |
* @return A backup file of the current file. |
520 |
*/ |
521 |
public File getBackupFile() { |
522 |
return backupFile; |
523 |
} |
524 |
|
525 |
/** |
526 |
* @param backupFile A backup file of the current file. |
527 |
*/ |
528 |
public void setBackupFile(File backupFile) { |
529 |
this.backupFile = backupFile; |
530 |
} |
531 |
} |
532 |
|
533 |
/** |
534 |
* Represents the service and engine associated with an individual host. |
535 |
* @author Wesley |
536 |
* |
537 |
*/ |
538 |
private class HostParents { |
539 |
|
540 |
private Engine engine; |
541 |
private Service service; |
542 |
|
543 |
/** |
544 |
* @return The engine associated with the host. |
545 |
*/ |
546 |
public Engine getEngine() { |
547 |
return engine; |
548 |
} |
549 |
|
550 |
/** |
551 |
* @param engine The engine associated with the host. |
552 |
*/ |
553 |
public void setEngine(Engine engine) { |
554 |
this.engine = engine; |
555 |
|
556 |
} |
557 |
|
558 |
/** |
559 |
* @return The service associated with the host. |
560 |
*/ |
561 |
public Service getService() { |
562 |
return service; |
563 |
} |
564 |
|
565 |
/** |
566 |
* @param service The service associated with the host. |
567 |
*/ |
568 |
public void setService(Service service) { |
569 |
this.service = service; |
570 |
|
571 |
} |
572 |
|
573 |
} |
574 |
} |