0
|
1 package nabble.model.export;
|
|
2
|
|
3 import fschmidt.db.DbDatabase;
|
|
4 import fschmidt.util.java.IoUtils;
|
|
5 import fschmidt.util.mail.Mail;
|
|
6 import fschmidt.util.mail.MailAddress;
|
|
7 import fschmidt.util.mail.MailHome;
|
|
8 import fschmidt.util.mail.PlainTextContent;
|
|
9 import nabble.model.MailingList;
|
|
10 import nabble.model.ModelException;
|
|
11 import nabble.model.ModelHome;
|
|
12 import nabble.model.Executors;
|
|
13 import nabble.model.Node;
|
|
14 import nabble.model.NodeIterator;
|
|
15 import nabble.model.Person;
|
|
16 import nabble.model.User;
|
|
17 import org.slf4j.Logger;
|
|
18 import org.slf4j.LoggerFactory;
|
|
19
|
|
20 import java.io.IOException;
|
|
21 import java.net.MalformedURLException;
|
|
22 import java.rmi.Naming;
|
|
23 import java.rmi.NotBoundException;
|
|
24 import java.rmi.Remote;
|
|
25 import java.rmi.RemoteException;
|
|
26 import java.util.HashSet;
|
|
27 import java.util.Set;
|
|
28 import java.util.TreeSet;
|
|
29 import java.util.regex.Matcher;
|
|
30 import java.util.regex.Pattern;
|
|
31
|
|
32
|
|
33 public class Export implements Runnable {
|
|
34
|
|
35 private static final Logger logger = LoggerFactory.getLogger(Export.class);
|
|
36
|
|
37 protected static class ExportMessages {
|
|
38 public String getSuccessMessage(Node node) { return "Export finished successfully."; }
|
|
39 public String getErrorMessage(Node node, Exception e) { return "Export error: \n" + getStackTrace(e); }
|
|
40 public String getPermalinkNotFoundMessage(String permalink) { return "Export couldn't start because the link you provided is not a valid Nabble application: \n" + permalink; }
|
|
41 public String getEmailSubject(Node node) { return "Export of node " + node.getId() + " | " + node.getSubject(); }
|
|
42 }
|
|
43
|
|
44 protected static final class ShutdownException extends RuntimeException {}
|
|
45
|
|
46 private static final Pattern URL_BASE = Pattern.compile( "^\\w+://[^/]+/");
|
|
47 private static final Pattern SERVER_FORMAT = Pattern.compile( "(localhost|\\d+\\.\\d+\\.\\d+\\.\\d+):\\d+");
|
|
48 private final String email;
|
|
49 private final Node rootNode;
|
|
50 private final Import imp;
|
|
51 private final ExportMessages messages;
|
|
52 private final String permalink;
|
|
53 private boolean handledMailingList = false;
|
|
54 private Set<Long> movedUserIds = new TreeSet<Long>();
|
|
55
|
|
56 protected Export(Node rootNode,String permalink,String email, ImportServer is, ExportMessages messages)
|
|
57 throws IOException
|
|
58 {
|
|
59 logger.info("Exporting node ID = " + rootNode.getId());
|
|
60 setExporting(rootNode.getSite().getId(), true);
|
|
61 this.rootNode = rootNode;
|
|
62 imp = is.newImport(permalink,rootNode.getId());
|
|
63 this.email = email;
|
|
64 this.messages = messages;
|
|
65 this.permalink = permalink;
|
|
66 }
|
|
67
|
|
68 public Export(Node rootNode,String permalink,String email)
|
|
69 throws IOException
|
|
70 {
|
|
71 this(rootNode,permalink,email,getServer(permalink),new ExportMessages());
|
|
72 }
|
|
73
|
|
74 // Global Information
|
|
75 public static Set<Long> exportSiteIds = new HashSet<Long>();
|
|
76
|
|
77 private static synchronized void setExporting(long siteId, boolean start) {
|
|
78 if (start) {
|
|
79 exportSiteIds.add(siteId);
|
|
80 } else
|
|
81 exportSiteIds.remove(siteId);
|
|
82 }
|
|
83
|
|
84 private static String getServerAddress(String permalink) throws IOException {
|
|
85 Matcher m = URL_BASE.matcher(permalink);
|
|
86 if( !m.find() )
|
|
87 throw new IllegalArgumentException();
|
|
88 String base = m.group();
|
|
89 return IoUtils.readPage(base+"util/Rmi.jtp");
|
|
90 }
|
|
91
|
|
92 private static Remote lookup(String rmiServer,String name)
|
|
93 throws RemoteException
|
|
94 {
|
|
95 try {
|
|
96 return Naming.lookup("//"+rmiServer+"/"+name);
|
|
97 } catch(MalformedURLException e) {
|
|
98 throw new RuntimeException(e);
|
|
99 } catch(NotBoundException e) {
|
|
100 throw new RuntimeException(e);
|
|
101 }
|
|
102 }
|
|
103
|
|
104 private static ImportServer getServer(String permalink) throws IOException {
|
|
105 return (ImportServer) lookup(getServerAddress(permalink),"import");
|
|
106 }
|
|
107
|
|
108 public static boolean isValidExportServer(String permalink) {
|
|
109 try {
|
|
110 String server = getServerAddress(permalink);
|
|
111 return SERVER_FORMAT.matcher(server).matches();
|
|
112 } catch (IOException e) {
|
|
113 return false;
|
|
114 } catch (IllegalArgumentException e) {
|
|
115 return false;
|
|
116 }
|
|
117 }
|
|
118
|
|
119 public void run() {
|
|
120 logger.info("Started export thread for ID = " + rootNode.getId() + " / " + email);
|
|
121 PlainTextContent content = null;
|
|
122 ModelHome.beginImport();
|
|
123 long siteId = this.rootNode.getSite().getId();
|
|
124 boolean closed = false;
|
|
125 try {
|
|
126 Node rootNode = this.rootNode.getGoodCopy();
|
|
127 long parentId = imp.getNodeId(permalink);
|
|
128 export(rootNode.getGoodCopy(),parentId,null);
|
|
129 if( handledMailingList ) {
|
|
130 handledMailingList = false;
|
|
131 export(rootNode.getGoodCopy(),parentId,null);
|
|
132 if( handledMailingList )
|
|
133 throw new RuntimeException();
|
|
134 }
|
|
135 // The last step is to delete the old nodes, so there is no reason
|
|
136 // to keep this connection open. Let's close it.
|
|
137 imp.close();
|
|
138 closed = true;
|
|
139
|
|
140 // Delete the old nodes now
|
|
141 rootNode.deleteRecursively();
|
|
142 content = new PlainTextContent(messages.getSuccessMessage(rootNode));
|
|
143 } catch(ShutdownException e) {
|
|
144 throw e;
|
|
145 } catch(RuntimeException e) {
|
|
146 logger.error(rootNode.toString(),e);
|
|
147 content = new PlainTextContent(messages.getErrorMessage(rootNode, e));
|
|
148 } catch(RemoteException e) {
|
|
149 logger.error(rootNode.toString(),e);
|
|
150 content = new PlainTextContent(messages.getErrorMessage(rootNode, e));
|
|
151 } catch(Import.BadLink e) {
|
|
152 content = new PlainTextContent(messages.getPermalinkNotFoundMessage(permalink));
|
|
153 } finally {
|
|
154 ModelHome.endImport();
|
|
155 setExporting(siteId, false);
|
|
156 if (!closed) {
|
|
157 try {
|
|
158 imp.close();
|
|
159 } catch(Exception e) {
|
|
160 logger.error("imp.close",e);
|
|
161 if (content == null)
|
|
162 content = new PlainTextContent(messages.getErrorMessage(rootNode, e));
|
|
163 }
|
|
164 }
|
|
165 }
|
|
166 Mail mail = MailHome.newMail();
|
|
167 MailAddress to = new MailAddress(email);
|
|
168 mail.setFrom(new MailAddress(ModelHome.noReply, "Nabble"));
|
|
169 mail.setTo(to);
|
|
170 mail.setContent(content);
|
|
171 mail.setSubject(messages.getEmailSubject(rootNode));
|
|
172 ModelHome.send(mail);
|
|
173 }
|
|
174
|
|
175 private void export(Node node,long parentId,int[] pin)
|
|
176 throws RemoteException
|
|
177 {
|
|
178 if( Executors.isShuttingDown() )
|
|
179 throw new ShutdownException();
|
|
180 long id = node.getExportedNodeId();
|
|
181 try {
|
|
182 final MailingList ml = node.getMailingList();
|
|
183 Integer pinOrder = pin == null || !node.isPinned()? null : ++pin[0];
|
|
184 if( id==0L ) {
|
|
185 NodeData data = node.getData();
|
|
186 data.parentId = parentId;
|
|
187 data.pin = pinOrder;
|
|
188 String redirectUrl = imp.importNode(data);
|
|
189 try {
|
|
190 id = imp.getNodeId(redirectUrl);
|
|
191 } catch(Import.BadLink e) {
|
|
192 logger.error(""+node+" url = "+redirectUrl,e);
|
|
193 throw new RuntimeException("redirect url not found: "+redirectUrl);
|
|
194 }
|
|
195 setExportedNodeId(node, id);
|
|
196 if (ml != null) {
|
|
197 imp.setExportOwner(id,email);
|
|
198 imp.subscribe(id);
|
|
199 ml.setExportOwner(email);
|
|
200 ml.update();
|
|
201 ml.unsubscribe();
|
|
202 handledMailingList = true;
|
|
203 }
|
|
204 } else {
|
|
205 if (ml!=null) {
|
|
206 imp.setExportOwner(id,null);
|
|
207 ml.setExportOwner(null);
|
|
208 ml.update();
|
|
209 }
|
|
210 }
|
|
211
|
|
212 if (ml != null)
|
|
213 imp.setMailingList(id);
|
|
214
|
|
215 // Move author
|
|
216 Person author = node.getOwner();
|
|
217 if (author instanceof User) {
|
|
218 User user = (User) author;
|
|
219 if (user.isRegistered() && !movedUserIds.contains(user.getId())) {
|
|
220 String smallAvatarUrl = null;
|
|
221 String bigAvatarUrl = null;
|
|
222 if (user.hasAvatar()) {
|
|
223 String base = node.getSite().getBaseUrl() + "/file/a" + user.getId() + '/';
|
|
224 smallAvatarUrl = base + ModelHome.AVATAR_SMALL;
|
|
225 bigAvatarUrl = base + ModelHome.AVATAR_BIG;
|
|
226 }
|
|
227 imp.addUser(user.getName(), user.getEmail(), user.getPasswordDigest(), user.getRegistered(), smallAvatarUrl, bigAvatarUrl);
|
|
228 movedUserIds.add(user.getId());
|
|
229 }
|
|
230 }
|
|
231
|
|
232 pin = new int[1];
|
|
233
|
|
234 // Here we use an iterator because something may go wrong with the
|
|
235 // migration (e.g., the other server may go down or the network may fail)
|
|
236 // So we should make sure all DB connections are properly closed.
|
|
237 NodeIterator<? extends Node> iterator = node.getChildren();
|
|
238 try {
|
|
239 while (iterator.hasNext()) {
|
|
240 export(iterator.next(), id, pin );
|
|
241 }
|
|
242 } finally {
|
|
243 iterator.close();
|
|
244 }
|
|
245 if (ml != null)
|
|
246 imp.setMailingList(null);
|
|
247 } catch(ModelException e) {
|
|
248 logger.error(""+node,e);
|
|
249 throw new RuntimeException(e.toString());
|
|
250 }
|
|
251 }
|
|
252
|
|
253 protected void setExportedNodeId(Node node, long id) {
|
|
254 final DbDatabase db = rootNode.getSite().getDb();
|
|
255 db.beginTransaction();
|
|
256 try {
|
|
257 node.getGoodCopy().setExportedNodeId(id);
|
|
258 db.commitTransaction();
|
|
259 } finally {
|
|
260 db.endTransaction();
|
|
261 }
|
|
262 }
|
|
263
|
|
264 // Utilities --------------------------------------------------------------
|
|
265
|
|
266 protected static String getStackTrace(Throwable e) {
|
|
267 StackTraceElement[] arr = e.getStackTrace();
|
|
268 StringBuilder builder = new StringBuilder(e.toString());
|
|
269 builder.append('\n');
|
|
270 for (StackTraceElement elem : arr) {
|
|
271 builder.append('\t').append(elem).append('\n');
|
|
272 }
|
|
273 if (e.getCause() != null) {
|
|
274 builder.append("Caused by:\n");
|
|
275 builder.append(getStackTrace(e.getCause()));
|
|
276 }
|
|
277 return builder.toString();
|
|
278 }
|
|
279 }
|