Исходный код вики DB2FS

Редактировал(а) Dmitriy Rybakov 05.08.2025, 13:07

Последние авторы
1 {{groovy}}
2 import com.xpn.xwiki.doc.XWikiAttachment;
3 import com.xpn.xwiki.doc.XWikiDocument;
4 import com.xpn.xwiki.doc.DeletedAttachment;
5 import com.xpn.xwiki.store.XWikiAttachmentStoreInterface;
6 import org.xwiki.store.legacy.store.internal.FilesystemAttachmentStore;
7 import com.xpn.xwiki.store.AttachmentVersioningStore;
8 import org.xwiki.store.legacy.store.internal.XWikiHibernateTransaction;
9 import com.xpn.xwiki.store.AttachmentRecycleBinStore;
10 import com.xpn.xwiki.web.Utils;
11 import com.xpn.xwiki.XWikiContext;
12 import com.xpn.xwiki.XWiki;
13 import com.xpn.xwiki.XWikiException;
14 import org.xwiki.store.filesystem.internal.FilesystemStoreTools;
15 import org.apache.commons.lang3.exception.ExceptionUtils;
16 import org.hibernate.ObjectNotFoundException;
17
18 private void printIntro(final XWiki wiki)
19 {
20 println("= Filesystem attachment storage porting script =");
21
22 println("== Step 1: Switch to Filesystem attachments. ==");
23
24 boolean ret = isFilesystemStore(wiki.getAttachmentStore()) \
25 && isFilesystemStore(wiki.getAttachmentVersioningStore()) \
26 && isFilesystemStore(wiki.getAttachmentRecycleBinStore());
27
28 println(getStoreLine(isFilesystemStore(wiki.getAttachmentStore()),
29 wiki.getAttachmentStore()));
30 println(getStoreLine(isFilesystemStore(wiki.getAttachmentVersioningStore()),
31 wiki.getAttachmentVersioningStore()));
32 println(getStoreLine(isFilesystemStore(wiki.getAttachmentRecycleBinStore()),
33 wiki.getAttachmentRecycleBinStore()));
34
35 if (!ret) {
36 println("Please edit your xwiki.cfg file by modifying "
37 + "the attachment store lines to read as follows:");
38 println("xwiki.store.attachment.hint = file");
39 println("xwiki.store.attachment.versioning.hint = file");
40 println("xwiki.store.attachment.recyclebin.hint = file");
41 println("Also make sure they are not commented out.");
42 }
43
44 println("== Step 2: Add this directory to your backup routine. ==");
45 println("This is your storage directory, "
46 + "when filesystem attachments are enabled you have to back this up "
47 + "as part of your data backup routine.\n");
48 println(Utils.getComponent(FilesystemStoreTools.class).storageDir.getAbsolutePath());
49
50 println("== Step 3: Copy attachments from database to filesystem. ==");
51 println("Now you are ready to copy the data over from your database to the filesystem. "
52 + "It is prudent to leave the attachments in the database since this is still experimental "
53 + "and in most situations the attachment data is not bothersome just sitting in the "
54 + "database. As such, this script contains no facility to delete entries from the database.");
55 println("If anything goes wrong in this function, it will fail with an error message, "
56 + "you should get the stack trace (probably in the log file) and keep it to "
57 + "confuse and humiliate the developers with. No harm should be done since this only loads "
58 + "from the database and only saves to the filesystem.");
59 }
60
61 private void start()
62 {
63 final XWikiContext xc = xcontext.getContext();
64 final XWiki wiki = xc.getWiki();
65
66 int startAt = 0;
67 int seconds = 20;
68 boolean dryRun = true;
69 boolean verbose = false;
70 boolean go = request.getMethod().equals("POST") && request.getParameter("doIt") != null;
71 boolean ajax = "plain".equals(request.getParameter("xpage"));
72 int endedAt = 0;
73
74 if (!ajax) {
75 this.printIntro(wiki);
76 }
77
78 if (go) {
79 startAt = Integer.parseInt(request.getParameter("startAt"));
80 seconds = Integer.parseInt(request.getParameter("seconds"));
81 dryRun = "on".equals(request.getParameter("dryRun"));
82 verbose = "on".equals(request.getParameter("verbose"));
83 endedAt = this.doIt(startAt, seconds, dryRun, verbose, ajax);
84 if (ajax) {
85 if (endedAt == -1) {
86 this.printFooter();
87 } else {
88 println("\n{{html clean=false}}<meta name='endedAt' content='"
89 + endedAt + "' />{{/html}}");
90 }
91 return;
92 }
93 }
94
95 this.printJs();
96
97 if (!go || endedAt > startAt) {
98 println("\n{{html clean=false}}");
99 println("<div id='javascriptSpace'></div>");
100 println("<form action='?time=" + System.nanoTime()
101 + "#doItForm' id='doItForm' method='POST'>");
102 println("<input type='hidden' name='seconds' value='" + seconds + "' />");
103 println("<input type='hidden' id='startAt' name='startAt' value='" + endedAt + "' />");
104 println("<dl>");
105 println("<dt><label for='dryRun'>Dry Run, don't save any files.</label></dt>");
106 println("<dd><input type='checkbox' name='dryRun' checked='"
107 + ((dryRun) ? "checked" : "") + "' /></dd>");
108
109 println("<dt><label for='verbose'>Verbose, show every attachment.</label></dt>");
110 println("<dd><input type='checkbox' name='verbose' checked='"
111 + ((verbose) ? "checked" : "") + "' /></dd>");
112 println("</dl>");
113 println("<input type='hidden' name='doIt' value='yes' />")
114 println("<input type='submit' name='start' value='"
115 + ((endedAt > startAt) ? "Continue" : "Start") + "' />");
116 println("</form>");
117 println("<style>#xwikidata { display:none }</style>")
118 println("{{/html}}\n");
119 return;
120 }
121
122 this.printFooter();
123 }
124
125 private void printFooter()
126 {
127 println("== Step 4: make sure everything is working. ==");
128 println("Check to make sure your attachments are still there, if an attachment is broken, "
129 + "it will appear to be there but on opening it will tell you the attachment does not exist. "
130 + "If something goes terribly wrong with filesystem attachments, you may have your old "
131 + "attachment system back simply by changing the lines in the xwiki.cfg file and restarting "
132 + "the container. HOWEVER: This will not preserve attachments which were uploaded **after** "
133 + "switching to filesystem attachments so as users edit the system you will become "
134 + "locked in to filesystem attachments unless a script is written to do the "
135 + "inverse of this one.");
136 println("Despite being experimental, the filesystem attachment storage is quite stable and the "
137 + "risk of actually losing something so that it is unrecoverable is very remote.");
138 println("NOTE: This must be run seperately in each subwiki.");
139 println("\n\nEnjoy and give feedback.");
140
141 }
142
143 private static boolean isFilesystemStore(final Object store)
144 {
145 return store.getClass().getName().contains("Filesystem");
146 }
147
148 private static String getStoreLine(final boolean isFilesystem, final Object store)
149 {
150 return "(%style='color:" + (isFilesystem ? "#080" : "#d00") + "'%)(((* " + store + ")))";
151 }
152
153 private void printJs()
154 {
155 println("""\n{{html}}<script>
156 var isAtBottom = function() {
157 var vp = document.viewport;
158 return vp.getScrollOffsets().top + vp.getHeight() + 30 > window.body.getHeight();
159 };
160
161 Event.observe(document, "dom:loaded", function() {
162 var doItForm = document.getElementById("doItForm");
163 var workspace = document.getElementById("javascriptSpace");
164 var params = {
165 xpage: "plain",
166 seconds: doItForm.seconds.value,
167 startAt: doItForm.startAt.value,
168 doIt: "yes"
169 };
170 var shouldContinue;
171
172 var doBlock = function(startAt) {
173 params.startAt = startAt;
174 new Ajax.Request(window.location.href, {
175 method: "POST",
176 parameters: params,
177 onSuccess: function(transport) {
178 var div = document.createElement("div");
179 div.innerHTML = transport.responseText;
180
181 var wasAtBottom = isAtBottom();
182 workspace.innerHTML += transport.responseText;
183 if (wasAtBottom || startAt == 0) {
184 doItForm.scrollTo();
185 }
186
187 var metas = div.getElementsByTagName("meta");
188 if (metas.length > 0 && shouldContinue) {
189 doBlock(metas[0].content);
190 return;
191 } else if (metas.length > 0) {
192 doItForm.startAt.value = metas[0].content;
193 doItForm.start.value = "Continue";
194 } else {
195 doItForm.parentNode.removeChild(doItForm);
196 }
197 reachedEnd();
198 }
199 });
200 };
201
202 var reachedEnd = function() {
203 Event.observe(doItForm, "submit", start);
204 Event.stopObserving(doItForm, "submit", stop);
205 };
206
207 var stop = function(ev) {
208 event.stop();
209 shouldContinue = false;
210 reachedEnd();
211 };
212
213 var start = function(ev) {
214 event.stop();
215 params.dryRun = (doItForm.dryRun.checked) ? "on" : undefined;
216 params.verbose = (doItForm.verbose.checked) ? "on" : undefined;
217 workspace.innerHTML = "";
218 shouldContinue = true;
219 doBlock(doItForm.startAt.value);
220 doItForm.start.value = "Abort";
221 Event.stopObserving(doItForm, "submit", start);
222 Event.observe(doItForm, "submit", stop);
223 };
224
225 Event.observe(doItForm, "submit", start);
226 });
227 </script>{{/html}}""");
228 }
229
230 /**
231 * Port all attachments from the original form to filesystem attachment store.
232 * Do one batch at a time so the user can see the progress and it won't time out.
233 *
234 * @param startAt the index of the first job to do, 0 to start at the beginning.
235 * @param seconds the number of seconds to run for before returning.
236 * @param dryRun don't save any files if this is true.
237 * @return the number of the first job which was not done. This can be startAt in the next call.
238 * -1 if the operation is complete.
239 */
240 private int doIt(int startAt, int seconds, boolean dryRun, boolean verbose, boolean ajax)
241 {
242 final XWikiContext xc = this.xcontext.getContext();
243 final XWiki wiki = xc.getWiki();
244 final FilesystemAttachmentStore fileAttachStore =
245 Utils.getComponent(XWikiAttachmentStoreInterface.class, "file");
246 final XWikiAttachmentStoreInterface defAttachStore =
247 Utils.getComponent(XWikiAttachmentStoreInterface.class, "hibernate");
248 final AttachmentVersioningStore defAttachmentVersioningStore =
249 Utils.getComponent(AttachmentVersioningStore.class, "hibernate");
250
251 int count = 0;
252 long stopTime = System.currentTimeMillis() + (1000 * seconds);
253
254 println("(%style='color:#00d'%)(((");
255 if (verbose && startAt == 0) {
256 println("\n== Attachments ==\n");
257 }
258 for (String docName : xwiki.searchDocuments("where 1=1")) {
259 if (count < startAt) {
260 count++;
261 continue;
262 } else if (stopTime < System.currentTimeMillis()) {
263 println(")))");
264 return count;
265 } else {
266 count++;
267 }
268 final XWikiDocument doc = xwiki.getDocument(docName).getDocument();
269 try {
270 final XWikiHibernateTransaction transaction = new XWikiHibernateTransaction(xc);
271
272 if (verbose) {
273 println("* [[" + doc.getFullName() + "]]");
274 }
275 // This is the equivilant of doc.getAttachmentList() but there is an issue which
276 // causes some attachments to be lost.
277 // http://jira.xwiki.org/browse/XWIKI-7936
278 def hql = "select att from XWikiAttachment att WHERE att.docId=?";
279 for (XWikiAttachment attach : xwiki.search(hql, [doc.getId()])) {
280 attach.setDoc(doc);
281 if (verbose) {
282 println("** [[attach:" + doc.getFullName() + "@" + attach.getFilename() + "]]");
283 }
284 try {
285 fileAttachStore.loadAttachmentContent(attach, xc, false);
286 continue;
287 } catch (XWikiException noAttachmentInFilesystem) {
288 // no such attachment, let's save it.
289 }
290 try {
291 try {
292 defAttachStore.loadAttachmentContent(attach, xc, true);
293 } catch (XWikiException notInHibernate) {
294 final Throwable wrapped = notInHibernate.getException();
295 if (wrapped && wrapped instanceof ObjectNotFoundException) {
296 println("(%style='color:#c80'%)(((");
297 println("Unable to find this attachment [[attach:" + attach.getFilename()
298 + "@" + doc.getFullName() + "]] in the database, "
299 + "it may be the result of database corruption from a failed "
300 + "upload prior to the fixing of "
301 + "[[http://jira.xwiki.org/browse/XWIKI-5055]]");
302 println("xwikiattachment_content.XWA_ID = " + attach.getId());
303 println(")))");
304 }
305 }
306 defAttachmentVersioningStore.loadArchive(attach, xc, true);
307 try {
308 // http://jira.xwiki.org/jira/browse/XWIKI-6199
309 attach.getAttachment_archive().getVersions();
310 } catch (NullPointerException e) {
311 attach.setAttachment_archive(new com.xpn.xwiki.internal.doc.ListAttachmentArchive([attach]));
312 }
313 // Setup the runnables to do the save but don't save just yet.
314 fileAttachStore.getAttachmentContentSaveRunnable(attach, false, xc)
315 .runIn(transaction);
316 } catch (Exception e) {
317 println("(%style='color:#d00'%)(((");
318 println("Error in [" + doc.getFullName() + ":" + attach.getFilename() + "]"
319 + e.getMessage());
320 println(ExceptionUtils.getStackTrace(e));
321 println(")))");
322 }
323 }
324 if (!dryRun) {
325 transaction.start();
326 }
327 } catch (Exception e) {
328 println("(%style='color:#d00'%)(((");
329 println("Failed to port attachments for document [" + doc.getFullName() + "]");
330 println(e.getMessage());
331 println(ExceptionUtils.getStackTrace(e));
332 println(")))");
333 }
334 }
335 println(")))");
336 if (startAt < count) {
337 int attachCount =
338 xwiki.search("select count(at) from XWikiDocument as doc, "
339 + "XWikiAttachment as at where at.docId = doc.id").get(0).intValue();
340 println("**[" + attachCount + "] attachments.**");
341 }
342
343
344 // And now for the deleted attachments.
345 final AttachmentRecycleBinStore fileBin =
346 Utils.getComponent(AttachmentRecycleBinStore.class, "file");
347 final AttachmentRecycleBinStore defaultBin =
348 Utils.getComponent(AttachmentRecycleBinStore.class, "hibernate");
349
350 if (verbose && startAt < count) {
351 println("\n== Deleted Attachments ==\n");
352 }
353 List<Object[]> entries =
354 xwiki.search("select da.id, da.docName, da.filename, da.date from DeletedAttachment as da");
355 if (verbose && entries.size() == 0) {
356 println("\nnone.");
357 }
358 int delAttachCount = 0;
359 for (Object[] o : entries) {
360 long delAttachId = o[0];
361 final String docName = o[1];
362 final String fileName = o[2];
363 final Date deleteDate = o[3];
364 delAttachCount++;
365 if (count < startAt) {
366 if (verbose && !ajax) {
367 println("* [[" + docName + "]] - " + fileName);
368 }
369 count++;
370 continue;
371 } else if (stopTime < System.currentTimeMillis()) {
372 return count;
373 }
374 count++;
375
376 try {
377 final DeletedAttachment delAttach =
378 defaultBin.getDeletedAttachment(delAttachId, xc, true);
379 // Can't use the transaction runnable here because (non-filesystem)
380 // deleted attachments hold all content in memory so they have to be
381 // handled one at a time.
382 final XWikiAttachment attach = delAttach.restoreAttachment(null, xc);
383 try {
384 // http://jira.xwiki.org/jira/browse/XWIKI-6199
385 attach.getAttachment_archive().getVersions();
386 } catch (NullPointerException e) {
387 attach.setAttachment_archive(new com.xpn.xwiki.internal.doc.ListAttachmentArchive([attach]));
388 }
389 if (!dryRun) {
390 fileBin.saveToRecycleBin(attach,
391 delAttach.getDeleter(),
392 delAttach.getDate(),
393 xc,
394 false);
395 }
396
397 if (verbose) {
398 println("* [[" + docName + "]]");
399 println("** " + fileName + " - Deleted: " + deleteDate);
400 }
401 } catch (Exception e) {
402 println("(%style='color:#d00'%)(((");
403 println("* [[" + docName + "]] - " + fileName + " - Deleted: " + deleteDate);
404 println(e.getMessage());
405 println(")))");
406 println(ExceptionUtils.getStackTrace(e));
407 }
408 }
409 println("\n**[" + delAttachCount + "] deleted attachments.**");
410 return -1;
411 }
412
413 def version910 = services.extension.parseVersion('9.10-rc-1')
414 def versionEnvironment = services.extension.core.repository.environmentExtension.id.version;
415
416 if (versionEnvironment.compareTo(version910) >= 0)
417 print '{{error}}This tool cannot be used in XWiki >= 9.11RC11{{/error}}'
418 else
419 start();
420 {{/groovy}}