001: /**
002: *
003: * Licensed to the Apache Software Foundation (ASF) under one or more
004: * contributor license agreements. See the NOTICE file distributed with
005: * this work for additional information regarding copyright ownership.
006: * The ASF licenses this file to You under the Apache License, Version 2.0
007: * (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */package org.apache.openejb.tomcat.installer;
018:
019: import org.codehaus.swizzle.stream.DelimitedTokenReplacementInputStream;
020: import org.codehaus.swizzle.stream.StringTokenHandler;
021: import org.apache.openejb.loader.SystemInstance;
022:
023: import java.io.ByteArrayInputStream;
024: import java.io.Closeable;
025: import java.io.File;
026: import java.io.FileInputStream;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.OutputStream;
031: import java.lang.reflect.Method;
032: import java.util.ArrayList;
033: import java.util.LinkedList;
034: import java.util.List;
035: import java.util.zip.ZipEntry;
036: import java.util.jar.JarFile;
037:
038: public class Installer {
039: public enum Status {
040: NONE, INSTALLED, REBOOT_REQUIRED
041: }
042:
043: private static final boolean listenerInstalled;
044: private static final boolean agentInstalled;
045:
046: static {
047: // is the OpenEJB listener installed
048: listenerInstalled = "OpenEJBListener".equals(SystemInstance
049: .get().getProperty("openejb.embedder.source"));
050:
051: // is the OpenEJB javaagent installed
052: agentInstalled = invokeStaticNoArgMethod(
053: "org.apache.openejb.javaagent.Agent",
054: "getInstrumentation") != null;
055: }
056:
057: public static boolean isListenerInstalled() {
058: return listenerInstalled;
059: }
060:
061: public static boolean isAgentInstalled() {
062: return agentInstalled;
063: }
064:
065: private final Paths paths;
066: private Status status = Status.NONE;
067:
068: // Thi may need to be redesigned but the goal is to provide some feedback on what happened
069: private final List<String> errors = new ArrayList<String>();
070: private final List<String> warnings = new ArrayList<String>();
071: private final List<String> infos = new ArrayList<String>();
072:
073: public Installer(Paths paths) {
074: this .paths = paths;
075:
076: if (listenerInstalled && agentInstalled) {
077: status = Status.INSTALLED;
078: }
079: }
080:
081: public Status getStatus() {
082: return status;
083: }
084:
085: public void installAll() {
086: installListener();
087:
088: installJavaagent();
089:
090: installConfigFiles();
091:
092: if (!hasErrors()) {
093: status = Status.REBOOT_REQUIRED;
094: }
095: }
096:
097: public void installListener() {
098: if (listenerInstalled) {
099: // addInfo("OpenEJB Listener already installed");
100: return;
101: }
102:
103: boolean copyOpenEJBLoader = true;
104:
105: // copy loader jar to lib
106: File destination = new File(paths.getCatalinaLibDir(), paths
107: .getOpenEJBTomcatLoaderJar().getName());
108: if (destination.exists()) {
109: if (paths.getOpenEJBTomcatLoaderJar().length() != destination
110: .length()) {
111: // md5 diff the files
112: } else {
113: // addInfo("OpenEJB loader jar already installed in Tomcat lib directory.");
114: copyOpenEJBLoader = false;
115: }
116: }
117:
118: if (copyOpenEJBLoader) {
119: try {
120: copyFile(paths.getOpenEJBTomcatLoaderJar(), destination);
121: addInfo("Copy "
122: + paths.getOpenEJBTomcatLoaderJar().getName()
123: + " to lib");
124: } catch (IOException e) {
125: addError(
126: "Unable to copy OpenEJB Tomcat loader jar to Tomcat lib directory. This will need to be performed manually.",
127: e);
128: }
129: }
130:
131: // read server.xml
132: String serverXmlOriginal = readAll(paths.getServerXmlFile());
133:
134: // server xml will be null if we couldn't read the file
135: if (serverXmlOriginal == null) {
136: return;
137: }
138:
139: // does the server.xml contain our listener name... it is possible that they commented out our listener, but that would be a PITA to detect
140: if (serverXmlOriginal
141: .contains("org.apache.openejb.tomcat.loader.OpenEJBListener")) {
142: addWarning("OpenEJB Listener already declared in Tomcat server.xml file.");
143: return;
144: }
145:
146: // if we can't backup the file, do not modify it
147: if (!backup(paths.getServerXmlFile())) {
148: return;
149: }
150:
151: // add our listener
152: String newServerXml = null;
153: try {
154: newServerXml = replace(
155: serverXmlOriginal,
156: "<Server",
157: "<Server",
158: ">",
159: ">\r\n"
160: + " <!-- OpenEJB plugin for Tomcat -->\r\n"
161: + " <Listener className=\"org.apache.openejb.tomcat.loader.OpenEJBListener\" />");
162: } catch (IOException e) {
163: addError("Error while adding listener to server.xml file",
164: e);
165: }
166:
167: // overwrite server.xml
168: if (writeAll(paths.getServerXmlFile(), newServerXml)) {
169: addInfo("Add OpenEJB listener to server.xml");
170: }
171: }
172:
173: public void installJavaagent() {
174: if (agentInstalled) {
175: // addInfo("OpenEJB Agent already installed");
176: return;
177: }
178:
179: //
180: // Copy openejb-javaagent.jar to lib
181: //
182: boolean copyJavaagentJar = true;
183: File javaagentJar = new File(paths.getCatalinaLibDir(),
184: "openejb-javaagent.jar");
185: if (javaagentJar.exists()) {
186: if (paths.getOpenEJBJavaagentJar().length() != javaagentJar
187: .length()) {
188: // md5 diff the files
189: } else {
190: // addInfo("OpenEJB javaagent jar already installed in Tomcat lib directory.");
191: copyJavaagentJar = false;
192: }
193: }
194:
195: if (copyJavaagentJar) {
196: try {
197: copyFile(paths.getOpenEJBJavaagentJar(), javaagentJar);
198: addInfo("Copy "
199: + paths.getOpenEJBJavaagentJar().getName()
200: + " to lib");
201: } catch (IOException e) {
202: addError(
203: "Unable to copy OpenEJB javaagent jar to Tomcat lib directory. This will need to be performed manually.",
204: e);
205: }
206: }
207:
208: //
209: // bin/catalina.sh
210: //
211:
212: // read the catalina sh file
213: String catalinaShOriginal = readAll(paths.getCatalinaShFile());
214:
215: // catalina sh will be null if we couldn't read the file
216: if (catalinaShOriginal == null) {
217: return;
218: }
219:
220: // does the catalina sh contain our comment... it is possible that they commented out the magic script code, but there is no way to detect that
221: if (catalinaShOriginal.contains("Add OpenEJB javaagent")) {
222: addWarning("OpenEJB javaagent already declared in Tomcat catalina.sh file.");
223: return;
224: }
225:
226: // if we can't backup the file, do not modify it
227: if (!backup(paths.getCatalinaShFile())) {
228: return;
229: }
230:
231: // add our magic bits to the catalina sh file
232: String openejbJavaagentPath = paths.getCatalinaBaseDir()
233: .toURI().relativize(javaagentJar.toURI()).getPath();
234: String newCatalinaSh = catalinaShOriginal.replace(
235: "# ----- Execute The Requested Command",
236: "# Add OpenEJB javaagent\n"
237: + "if [ -r \"$CATALINA_BASE\"/"
238: + openejbJavaagentPath + " ]; then\n"
239: + " JAVA_OPTS=\"\"-javaagent:$CATALINA_BASE/"
240: + openejbJavaagentPath + "\" $JAVA_OPTS\"\n"
241: + "fi\n" + "\n"
242: + "# ----- Execute The Requested Command");
243:
244: // overwrite the catalina.sh file
245: if (writeAll(paths.getCatalinaShFile(), newCatalinaSh)) {
246: addInfo("Add OpenEJB JavaAgent to catalina.sh");
247: }
248:
249: //
250: // bin/catalina.bat
251: //
252:
253: // read the catalina bat file
254: String catalinaBatOriginal = readAll(paths.getCatalinaBatFile());
255:
256: // catalina bat will be null if we couldn't read the file
257: if (catalinaBatOriginal == null) {
258: return;
259: }
260:
261: // does the catalina bat contain our comment... it is possible that they commented out the magic script code, but there is no way to detect that
262: if (catalinaBatOriginal.contains("Add OpenEJB javaagent")) {
263: addWarning("OpenEJB javaagent already declared in Tomcat catalina.bat file.");
264: return;
265: }
266:
267: // if we can't backup the file, do not modify it
268: if (!backup(paths.getCatalinaBatFile())) {
269: return;
270: }
271:
272: // add our magic bits to the catalina bat file
273: openejbJavaagentPath = openejbJavaagentPath.replace('/', '\\');
274: String newCatalinaBat = catalinaBatOriginal
275: .replace(
276: "rem ----- Execute The Requested Command",
277: "rem Add OpenEJB javaagent\r\n"
278: + "if not exist \"%CATALINA_BASE%\\"
279: + openejbJavaagentPath
280: + "\" goto noOpenEJBJavaagent\r\n"
281: + "set JAVA_OPTS=\"-javaagent:%CATALINA_BASE%\\"
282: + openejbJavaagentPath
283: + "\" %JAVA_OPTS%\r\n"
284: + ":noOpenEJBJavaagent\r\n"
285: + "\r\n"
286: + "rem ----- Execute The Requested Command");
287:
288: // overwrite the catalina.bat file
289: if (writeAll(paths.getCatalinaBatFile(), newCatalinaBat)) {
290: addInfo("Add OpenEJB JavaAgent to catalina.bat");
291: }
292: }
293:
294: public void installConfigFiles() {
295: if (paths.getOpenEJBCoreJar() == null) {
296: // the core jar contains the config files
297: return;
298: }
299: JarFile coreJar;
300: try {
301: coreJar = new JarFile(paths.getOpenEJBCoreJar());
302: } catch (IOException e) {
303: return;
304: }
305:
306: //
307: // conf/openejb.xml
308: //
309: File openEjbXmlFile = new File(paths.getCatalinaConfDir(),
310: "openejb.xml");
311: if (!openEjbXmlFile.exists()) {
312: // read in the openejb.xml file from the openejb core jar
313: String openEjbXml = readEntry(coreJar,
314: "default.openejb.conf");
315: if (openEjbXml != null) {
316: if (writeAll(openEjbXmlFile, openEjbXml)) {
317: addInfo("Copy openejb.xml to conf");
318: }
319: }
320: }
321:
322: //
323: // conf/logging.properties
324: //
325: String openejbLoggingProps = readEntry(coreJar,
326: "logging.properties");
327: if (openejbLoggingProps != null) {
328: File loggingPropsFile = new File(
329: paths.getCatalinaConfDir(), "logging.properties");
330: String newLoggingProps = null;
331: if (!loggingPropsFile.exists()) {
332: newLoggingProps = openejbLoggingProps;
333: } else {
334: String loggingPropsOriginal = readAll(loggingPropsFile);
335: if (!loggingPropsOriginal.toLowerCase().contains(
336: "openejb")) {
337: // strip off license header
338: String[] strings = openejbLoggingProps.split(
339: "## --*", 3);
340: if (strings.length == 3) {
341: openejbLoggingProps = strings[2];
342: }
343: // append our properties
344: newLoggingProps = loggingPropsOriginal
345: + "\r\n"
346: + "############################################################\r\n"
347: + "# OpenEJB Logging Configuration.\r\n"
348: + "############################################################\r\n"
349: + openejbLoggingProps + "\r\n";
350: }
351: }
352: if (newLoggingProps != null) {
353: if (writeAll(loggingPropsFile, newLoggingProps)) {
354: addInfo("Append OpenEJB config to logging.properties");
355: }
356: }
357: }
358: }
359:
360: private String readEntry(JarFile jarFile, String name) {
361: ZipEntry entry = jarFile.getEntry(name);
362: if (entry == null)
363: return null;
364: InputStream in = null;
365: try {
366: in = jarFile.getInputStream(entry);
367: String text = readAll(in);
368: return text;
369: } catch (Exception e) {
370: addError("Unable to read " + name + " from "
371: + jarFile.getName());
372: return null;
373: } finally {
374: close(in);
375: }
376: }
377:
378: private String replace(String inputText, String begin,
379: String newBegin, String end, String newEnd)
380: throws IOException {
381: BeginEndTokenHandler tokenHandler = new BeginEndTokenHandler(
382: newBegin, newEnd);
383:
384: ByteArrayInputStream in = new ByteArrayInputStream(inputText
385: .getBytes());
386:
387: InputStream replacementStream = new DelimitedTokenReplacementInputStream(
388: in, begin, end, tokenHandler, true);
389: String newServerXml = readAll(replacementStream);
390: close(replacementStream);
391: return newServerXml;
392: }
393:
394: private boolean backup(File source) {
395: try {
396: File backupFile = new File(source.getParent(), source
397: .getName()
398: + ".original");
399: if (!backupFile.exists()) {
400: copyFile(source, backupFile);
401: }
402: return true;
403: } catch (IOException e) {
404: addError("Unable to backup " + source.getAbsolutePath()
405: + "; No changes will be made to this file");
406: return false;
407: }
408: }
409:
410: private void copyFile(File source, File destination)
411: throws IOException {
412: File destinationDir = destination.getParentFile();
413: if (!destinationDir.exists() && !destinationDir.mkdirs()) {
414: throw new java.io.IOException("Cannot create directory : "
415: + destinationDir);
416: }
417:
418: InputStream in = null;
419: OutputStream out = null;
420: try {
421: in = new FileInputStream(source);
422: out = new FileOutputStream(destination);
423: writeAll(in, out);
424: } finally {
425: close(in);
426: close(out);
427: }
428: }
429:
430: private boolean writeAll(File file, String text) {
431: FileOutputStream fileOutputStream = null;
432: try {
433: fileOutputStream = new FileOutputStream(file);
434: writeAll(new ByteArrayInputStream(text.getBytes()),
435: fileOutputStream);
436: return true;
437: } catch (Exception e) {
438: addError("Unable to write to " + file.getAbsolutePath(), e);
439: return false;
440: } finally {
441: close(fileOutputStream);
442: }
443: }
444:
445: private void writeAll(InputStream in, OutputStream out)
446: throws IOException {
447: byte[] buffer = new byte[4096];
448: int count;
449: while ((count = in.read(buffer)) > 0) {
450: out.write(buffer, 0, count);
451: }
452: out.flush();
453: }
454:
455: private String readAll(File file) {
456: FileInputStream in = null;
457: try {
458: in = new FileInputStream(file);
459: String text = readAll(in);
460: return text;
461: } catch (Exception e) {
462: addError("Unable to read " + file.getAbsolutePath());
463: return null;
464: } finally {
465: close(in);
466: }
467: }
468:
469: private String readAll(InputStream in) throws IOException {
470: // SwizzleStream block read methods are broken so read byte at a time
471: StringBuilder sb = new StringBuilder();
472: int i = in.read();
473: while (i != -1) {
474: sb.append((char) i);
475: i = in.read();
476: }
477: return sb.toString();
478: }
479:
480: private static Object invokeStaticNoArgMethod(String className,
481: String propertyName) {
482: try {
483: Class<?> clazz = loadClass(className, Installer.class
484: .getClassLoader());
485: Method method = clazz.getMethod(propertyName);
486: Object result = method.invoke(null, (Object[]) null);
487: return result;
488: } catch (Throwable e) {
489: return null;
490: }
491: }
492:
493: private static Class<?> loadClass(String className,
494: ClassLoader classLoader) throws ClassNotFoundException {
495: LinkedList<ClassLoader> loaders = new LinkedList<ClassLoader>();
496: for (ClassLoader loader = classLoader; loader != null; loader = loader
497: .getParent()) {
498: loaders.addFirst(loader);
499: }
500: for (ClassLoader loader : loaders) {
501: try {
502: Class<?> clazz = Class.forName(className, true, loader);
503: return clazz;
504: } catch (ClassNotFoundException e) {
505: }
506: }
507: return null;
508: }
509:
510: private void close(Closeable thing) {
511: if (thing != null) {
512: try {
513: thing.close();
514: } catch (Exception ignored) {
515: }
516: }
517: }
518:
519: private static class BeginEndTokenHandler extends
520: StringTokenHandler {
521: private final String begin;
522: private final String end;
523:
524: public BeginEndTokenHandler(String begin, String end) {
525: this .begin = begin;
526: this .end = end;
527: }
528:
529: public String handleToken(String token) throws IOException {
530: String result = begin + token + end;
531: return result;
532: }
533: }
534:
535: public void reset() {
536: errors.clear();
537: warnings.clear();
538: infos.clear();
539: }
540:
541: public boolean hasErrors() {
542: return !errors.isEmpty();
543: }
544:
545: public List<String> getErrors() {
546: return errors;
547: }
548:
549: private void addError(String message) {
550: errors.add(message);
551: }
552:
553: @SuppressWarnings({"UnusedDeclaration"})
554: private void addError(String message, Exception e) {
555: // todo add exception somehow
556: System.out.println(message);
557: }
558:
559: public boolean hasWarnings() {
560: return !warnings.isEmpty();
561: }
562:
563: public List<String> getWarnings() {
564: return warnings;
565: }
566:
567: @SuppressWarnings({"UnusedDeclaration"})
568: private void addWarning(String message) {
569: System.out.println(message);
570: }
571:
572: public boolean hasInfos() {
573: return !infos.isEmpty();
574: }
575:
576: public List<String> getInfos() {
577: return infos;
578: }
579:
580: private void addInfo(String message) {
581: infos.add(message);
582: }
583: }
|