Mercurial Hosting > nabble
comparison src/nabble/model/export/Export.java @ 0:7ecd1a4ef557
add content
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 21 Mar 2019 19:15:52 -0600 |
parents | |
children | 72765b66e2c3 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:7ecd1a4ef557 |
---|---|
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 } |