1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. 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,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.util;
20
21 import java.io.ObjectOutputStream;
22 import java.io.OutputStream;
23 import java.io.PrintStream;
24 import java.io.PrintWriter;
25 import java.io.Serializable;
26 import java.util.Collection;
27 import java.util.Iterator;
28
29 import org.apache.openjpa.conf.OpenJPAVersion;
30 import org.apache.openjpa.enhance.PersistenceCapable;
31 import org.apache.openjpa.lib.util.J2DoPrivHelper;
32 import org.apache.openjpa.lib.util.JavaVersions;
33
34 /**
35 * Utility methods for externalizing and handling exceptions.
36 *
37 * @author Marc Prud'hommeaux
38 * @since 0.2.5
39 * @nojavadoc
40 */
41 public class Exceptions {
42
43 public static final Throwable[] EMPTY_THROWABLES = new Throwable[0];
44
45 static final String SEP = J2DoPrivHelper.getLineSeparator();
46
47 private static final OutputStream DEV_NULL = new OutputStream() {
48 public void write(int b) {
49 }
50 };
51
52 /**
53 * Test to see if the specified object will be able to be serialized. This
54 * will check if the object implements {@link Serializable}, and if so,
55 * will try to perform an actual serialization. This is in case the object
56 * has fields which, in turn, are not serializable.
57 *
58 * @param ob the object to test
59 * @return true if the object will be able to be serialized
60 */
61 private static boolean isSerializable(Object ob) {
62 if (!(ob instanceof Serializable))
63 return false;
64
65 // don't serialize persistent objects exceptions to prevent
66 // reading in all the state
67 if (!ImplHelper.isManagedType(null, ob.getClass()))
68 return false;
69
70 // now do an actual test to see if we will be
71 // able to perform the serialization
72 try {
73 new ObjectOutputStream(DEV_NULL).writeObject(ob);
74 return true;
75 } catch (Throwable t) {
76 return false;
77 }
78 }
79
80 /**
81 * Safely stringify the given object.
82 */
83 public static String toString(Object ob) {
84 if (ob == null)
85 return "null";
86
87 // don't take oid of new objects since it can cause a flush if auto-inc
88 // and the id is meaningless anyway
89 Object oid = getObjectId(ob);
90
91 if (oid != null) {
92 if (oid instanceof Id)
93 return oid.toString();
94 return ob.getClass().getName() + "-" + oid.toString();
95 }
96
97 if (ImplHelper.isManagedType(null, ob.getClass())) {
98 // never call toString() on a PersistenceCapable, since
99 // it may access persistent fields; fall-back to using
100 // the standard object stringification mechanism. New
101 // instances that use proxying (property-access instances,
102 // for example) that were created with the 'new' keyword
103 // will not end up in this code, which is ok since they
104 // don't do lazy loading anyways, so they will stringify
105 // safely.
106 return ob.getClass().getName() + "@"
107 + Integer.toHexString(System.identityHashCode(ob));
108 }
109
110 try {
111 String s = ob.toString();
112 if (s.indexOf(ob.getClass().getName()) == -1)
113 s += " [" + ob.getClass().getName() + "]";
114 return s;
115 } catch (Throwable t) {
116 return ob.getClass().getName();
117 }
118 }
119
120 /**
121 * Safely stringify the given objects.
122 */
123 public static String toString(Collection failed) {
124 StringBuffer buf = new StringBuffer();
125 buf.append("[");
126 for (Iterator itr = failed.iterator(); itr.hasNext();) {
127 buf.append(Exceptions.toString(itr.next()));
128 if (itr.hasNext())
129 buf.append(", ");
130 }
131 buf.append("]");
132 return buf.toString();
133 }
134
135 /**
136 * Stringify the given exception.
137 */
138 public static String toString(ExceptionInfo e) {
139 int type = e.getType();
140 StringBuffer buf = new StringBuffer();
141 buf.append("<").
142 append(OpenJPAVersion.VERSION_ID).
143 append(' ').
144 append(e.isFatal() ? "fatal " : "nonfatal ").
145 append (type == ExceptionInfo.GENERAL ? "general error" :
146 type == ExceptionInfo.INTERNAL ? "internal error" :
147 type == ExceptionInfo.STORE ? "store error" :
148 type == ExceptionInfo.UNSUPPORTED ? "unsupported error" :
149 type == ExceptionInfo.USER ? "user error" :
150 (type + " error")).
151 append("> ");
152 buf.append(e.getClass().getName()).append(": ").
153 append(e.getMessage());
154 Object failed = e.getFailedObject();
155 if (failed != null)
156 buf.append(SEP).append("FailedObject: ").
157 append(toString(failed));
158 return buf.toString();
159 }
160
161 /**
162 * Print the stack trace of the exception's nested throwables.
163 */
164 public static void printNestedThrowables(ExceptionInfo e, PrintStream out) {
165 // if this is Java 1.4 and there is exactly a single
166 // exception, then defer to 1.4's behavior of printing
167 // out the result of getCause(). This deferral happens in
168 // the calling code.
169 Throwable[] nested = e.getNestedThrowables();
170 int i = (JavaVersions.VERSION >= 4) ? 1 : 0;
171 if (i < nested.length) {
172 out.println("NestedThrowables:");
173 for (; i < nested.length; i++)
174 // guard against a nasty null in the array
175 if (nested[i] != null)
176 nested[i].printStackTrace(out);
177 }
178 }
179
180 /**
181 * Print the stack trace of the exception's nested throwables.
182 */
183 public static void printNestedThrowables(ExceptionInfo e, PrintWriter out) {
184 // if this is Java 1.4 and there is exactly a single
185 // exception, then defer to 1.4's behavior of printing
186 // out the result of getCause(). This deferral happens in
187 // the calling code.
188 Throwable[] nested = e.getNestedThrowables();
189 int i = (JavaVersions.VERSION >= 4) ? 1 : 0;
190 if (i < nested.length) {
191 out.println("NestedThrowables:");
192 for (; i < nested.length; i++)
193 // guard against a nasty null in the array
194 if (nested[i] != null)
195 nested[i].printStackTrace(out);
196 }
197 }
198
199 /**
200 * Convert the specified failed object into a serializable
201 * object for when we are serializing an Exception. It will
202 * try the following:
203 * <ul>
204 * <li>if the object can be serialized, return the object itself</li>
205 * <li>if the object has a serializable oid, return the oid</li>
206 * <li>if the object has a non-serializable oid, return the oid's
207 * toString and the object class</li>
208 * <li>return the object's toString</li>
209 * </ul>
210 *
211 * @param ob the object to convert
212 * @return some serialized representation of the object
213 */
214 public static Object replaceFailedObject(Object ob) {
215 if (ob == null)
216 return null;
217 if (isSerializable(ob))
218 return ob;
219
220 // don't take oid of new objects since it can cause a flush if auto-inc
221 // and the id is meaningless anyway
222 Object oid = getObjectId(ob);
223 if (oid != null && isSerializable(oid))
224 return oid;
225
226 // last ditch: stringify the object
227 return toString(ob);
228 }
229
230 /**
231 * Convert the specified throwables into a serialzable array. If
232 * any of the nested throwables cannot be serialized, they will
233 * be converted into a Exception with the original message.
234 */
235 public static Throwable[] replaceNestedThrowables(Throwable[] nested) {
236 if (nested == null || nested.length == 0)
237 return nested;
238 if (isSerializable(nested))
239 return nested;
240
241 Throwable[] newNested = new Throwable[nested.length];
242 for (int i = 0; i < nested.length; i++) {
243 if (isSerializable(nested[i]))
244 newNested[i] = nested[i];
245 else
246 // guard against a nasty null in the array by using valueOf
247 // instead of toString to prevent throwing yet another
248 // exception
249 newNested[i] = new Exception(String.valueOf(nested[i]));
250 }
251 return newNested;
252 }
253
254 /**
255 * Return the object id for <code>ob</code> if it has one, or
256 * <code>null</code> otherwise.
257 */
258 private static Object getObjectId(Object ob) {
259 if (!ImplHelper.isManageable(ob))
260 return null;
261
262 PersistenceCapable pc = ImplHelper.toPersistenceCapable(ob, null);
263 if (pc == null || pc.pcIsNew())
264 return null;
265 else
266 return pc.pcFetchObjectId();
267 }
268 }