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 }