Revision 3b6b7f25

b/src/gr/ebs/gss/server/ejb/TransactionHelper.java
1
/*
2
 * Copyright 2009 Electronic Business Systems Ltd.
3
 *
4
 * This file is part of GSS.
5
 *
6
 * GSS is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * GSS is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with GSS.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
package gr.ebs.gss.server.ejb;
20

  
21
import java.util.concurrent.Callable;
22
import java.util.concurrent.ExecutionException;
23
import java.util.concurrent.Executors;
24
import java.util.concurrent.ScheduledExecutorService;
25
import java.util.concurrent.ScheduledFuture;
26
import java.util.concurrent.TimeUnit;
27

  
28
import javax.ejb.EJBTransactionRolledbackException;
29

  
30
import org.apache.commons.logging.Log;
31
import org.apache.commons.logging.LogFactory;
32

  
33
/**
34
 * A helper class that provides a method for repeatedly trying a transaction
35
 * that is rolled back due to optimistic locking exceptions.
36
 *
37
 * @author  past
38
 */
39
public class TransactionHelper<T> {
40
	/**
41
	 * The logger.
42
	 */
43
	private static Log logger = LogFactory.getLog(TransactionHelper.class);
44

  
45
	/**
46
	 * Number of times to retry a transaction that was rolled back due to
47
	 * optimistic locking.
48
	 */
49
	private static final int TRANSACTION_RETRIES = 10;
50

  
51
	/**
52
	 * The minimum retry timeout in milliseconds.
53
	 */
54
	private static final int MIN_TIMEOUT = 100;
55

  
56
	/**
57
	 * Execute the supplied command until it completes, ignoring transaction
58
	 * rollbacks. Try at least TRANSACTION_RETRIES times before giving up,
59
	 * each time waiting a random amount of time, using an exponential
60
	 * backoff scheme. See http://en.wikipedia.org/wiki/Exponential_backoff
61
	 * for the basic idea.
62
	 *
63
	 * @param command the command to execute
64
	 * @return the value returned by the command
65
	 * @throws Exception any other exception thrown by the command
66
	 */
67
	public T tryExecute(final Callable<T> command) throws Exception {
68
		T returnValue = null;
69
		// Schedule a Future task to call the command after delay milliseconds.
70
		int delay = 0;
71
		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
72
		for (int i = 0; i < TRANSACTION_RETRIES; i++) {
73
			final int retry = i;
74
			ScheduledFuture<T> future = executor.schedule(new Callable<T>() {
75

  
76
				@Override
77
				public T call() throws Exception {
78
					return command.call();
79
				}
80
			}, delay, TimeUnit.MILLISECONDS);
81

  
82
			try {
83
				returnValue = future.get();
84
				break;
85
			} catch (ExecutionException e) {
86
				Throwable cause = e.getCause();
87
				if (!(cause instanceof EJBTransactionRolledbackException) ||
88
							retry == TRANSACTION_RETRIES - 1) {
89
					executor.shutdownNow();
90
					throw new Exception(cause);
91
				}
92
				delay = MIN_TIMEOUT + (int) (MIN_TIMEOUT * Math.random() * (i + 1));
93
				String origCause = cause.getCause() == null ?
94
							cause.getClass().getName() :
95
							cause.getCause().getClass().getName();
96
				logger.info("Transaction retry #" + (i+1) + " scheduled in " + delay +
97
							" msec due to " + origCause);
98
			}
99

  
100
		}
101
		executor.shutdownNow();
102
		return returnValue;
103
	}
104
}
b/src/gr/ebs/gss/server/rest/FilesHandler.java
32 32
import gr.ebs.gss.server.domain.dto.GroupDTO;
33 33
import gr.ebs.gss.server.domain.dto.PermissionDTO;
34 34
import gr.ebs.gss.server.ejb.ExternalAPI;
35
import gr.ebs.gss.server.ejb.TransactionHelper;
35 36
import gr.ebs.gss.server.webdav.Range;
36 37

  
37 38
import java.io.BufferedReader;
b/src/gr/ebs/gss/server/rest/GroupsHandler.java
25 25
import gr.ebs.gss.server.domain.User;
26 26
import gr.ebs.gss.server.domain.dto.GroupDTO;
27 27
import gr.ebs.gss.server.domain.dto.UserDTO;
28
import gr.ebs.gss.server.ejb.TransactionHelper;
28 29

  
29 30
import java.io.IOException;
30 31
import java.net.URLDecoder;
/dev/null
1
/*
2
 * Copyright 2009 Electronic Business Systems Ltd.
3
 *
4
 * This file is part of GSS.
5
 *
6
 * GSS is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * GSS is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with GSS.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
package gr.ebs.gss.server.rest;
20

  
21
import java.util.concurrent.Callable;
22
import java.util.concurrent.ExecutionException;
23
import java.util.concurrent.Executors;
24
import java.util.concurrent.ScheduledExecutorService;
25
import java.util.concurrent.ScheduledFuture;
26
import java.util.concurrent.TimeUnit;
27

  
28
import javax.ejb.EJBTransactionRolledbackException;
29

  
30
import org.apache.commons.logging.Log;
31
import org.apache.commons.logging.LogFactory;
32

  
33
/**
34
 * A helper class that provides a method for repeatedly trying a transaction
35
 * that is rolled back due to optimistic locking exceptions.
36
 *
37
 * @author  past
38
 */
39
public class TransactionHelper<T> {
40
	/**
41
	 * The logger.
42
	 */
43
	private static Log logger = LogFactory.getLog(TransactionHelper.class);
44

  
45
	/**
46
	 * Number of times to retry a transaction that was rolled back due to
47
	 * optimistic locking.
48
	 */
49
	private static final int TRANSACTION_RETRIES = 10;
50

  
51
	/**
52
	 * The minimum retry timeout in milliseconds.
53
	 */
54
	private static final int MIN_TIMEOUT = 100;
55

  
56
	/**
57
	 * Execute the supplied command until it completes, ignoring transaction
58
	 * rollbacks. Try at least TRANSACTION_RETRIES times before giving up,
59
	 * each time waiting a random amount of time, using an exponential
60
	 * backoff scheme. See http://en.wikipedia.org/wiki/Exponential_backoff
61
	 * for the basic idea.
62
	 *
63
	 * @param command the command to execute
64
	 * @return the value returned by the command
65
	 * @throws Exception any other exception thrown by the command
66
	 */
67
	public T tryExecute(final Callable<T> command) throws Exception {
68
		T returnValue = null;
69
		// Schedule a Future task to call the command after delay milliseconds.
70
		int delay = 0;
71
		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
72
		for (int i = 0; i < TRANSACTION_RETRIES; i++) {
73
			final int retry = i;
74
			ScheduledFuture<T> future = executor.schedule(new Callable<T>() {
75

  
76
				@Override
77
				public T call() throws Exception {
78
					return command.call();
79
				}
80
			}, delay, TimeUnit.MILLISECONDS);
81

  
82
			try {
83
				returnValue = future.get();
84
				break;
85
			} catch (ExecutionException e) {
86
				Throwable cause = e.getCause();
87
				if (!(cause instanceof EJBTransactionRolledbackException) ||
88
							retry == TRANSACTION_RETRIES - 1) {
89
					executor.shutdownNow();
90
					throw new Exception(cause);
91
				}
92
				delay = MIN_TIMEOUT + (int) (MIN_TIMEOUT * Math.random() * (i + 1));
93
				String origCause = cause.getCause() == null ?
94
							cause.getClass().getName() :
95
							cause.getCause().getClass().getName();
96
				logger.info("Transaction retry #" + (i+1) + " scheduled in " + delay +
97
							" msec due to " + origCause);
98
			}
99

  
100
		}
101
		executor.shutdownNow();
102
		return returnValue;
103
	}
104
}
b/src/gr/ebs/gss/server/rest/TrashHandler.java
24 24
import gr.ebs.gss.server.domain.User;
25 25
import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
26 26
import gr.ebs.gss.server.domain.dto.FolderDTO;
27
import gr.ebs.gss.server.ejb.TransactionHelper;
27 28

  
28 29
import java.io.IOException;
29 30
import java.net.URLEncoder;
b/src/gr/ebs/gss/server/rest/UserHandler.java
25 25
import gr.ebs.gss.server.Login;
26 26
import gr.ebs.gss.server.domain.User;
27 27
import gr.ebs.gss.server.domain.dto.StatsDTO;
28
import gr.ebs.gss.server.ejb.TransactionHelper;
28 29

  
29 30
import java.io.IOException;
30 31
import java.util.concurrent.Callable;
b/src/gr/ebs/gss/server/webdav/Webdav.java
31 31
import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
32 32
import gr.ebs.gss.server.domain.dto.FolderDTO;
33 33
import gr.ebs.gss.server.ejb.ExternalAPI;
34
import gr.ebs.gss.server.rest.TransactionHelper;
34
import gr.ebs.gss.server.ejb.TransactionHelper;
35 35

  
36 36
import java.io.BufferedInputStream;
37 37
import java.io.ByteArrayInputStream;
......
689 689
			return;
690 690
		}
691 691

  
692
		User user = getUser(req);
692
		final User user = getUser(req);
693 693
		String path = getRelativePath(req);
694 694
		boolean exists = true;
695 695
		Object resource = null;
......
741 741
			resourceInputStream = req.getInputStream();
742 742

  
743 743
		try {
744
			FolderDTO folder = null;
745 744
			Object parent = getService().getResourceAtPath(user.getId(), getParentPath(path), true);
746 745
			if (!(parent instanceof FolderDTO)) {
747 746
				resp.sendError(HttpServletResponse.SC_CONFLICT);
748 747
				return;
749 748
			}
750
			folder = (FolderDTO) parent;
751
			String name = getLastElement(path);
752
			String mimeType = getServletContext().getMimeType(name);
749
			final FolderDTO folder = (FolderDTO) parent;
750
			final String name = getLastElement(path);
751
			final String mimeType = getServletContext().getMimeType(name);
753 752
        	File uploadedFile = null;
754 753
        	try {
755 754
				uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
......
758 757
			}
759 758
			// FIXME: Add attributes
760 759
			FileHeaderDTO fileDTO = null;
760
			final FileHeaderDTO f = file;
761
			final File uf = uploadedFile;
761 762
			if (exists)
762
				fileDTO = getService().updateFileContents(user.getId(), file.getId(), mimeType, uploadedFile.length(), uploadedFile.getAbsolutePath());
763
				fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
764
					@Override
765
					public FileHeaderDTO call() throws Exception {
766
						return getService().updateFileContents(user.getId(), f.getId(), mimeType, uf.length(), uf.getAbsolutePath());
767
					}
768
				});
763 769
			else
764
				fileDTO = getService().createFile(user.getId(), folder.getId(), name, mimeType, uploadedFile.length(), uploadedFile.getAbsolutePath());
770
				fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
771
					@Override
772
					public FileHeaderDTO call() throws Exception {
773
						return getService().createFile(user.getId(), folder.getId(), name, mimeType, uf.length(), uf.getAbsolutePath());
774
					}
775
				});
765 776
			updateAccounting(user, new Date(), fileDTO.getFileSize());
766 777
		} catch (ObjectNotFoundException e) {
767 778
			result = false;
......
780 791
		} catch (DuplicateNameException e) {
781 792
			resp.sendError(HttpServletResponse.SC_CONFLICT);
782 793
			return;
794
		} catch (Exception e) {
795
			resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
796
			return;
783 797
		}
784 798

  
785 799
		if (result) {
......
1080 1094
			resp.sendError(WebdavStatus.SC_LOCKED);
1081 1095
			return;
1082 1096
		}
1083
		String path = getRelativePath(req);
1097
		final String path = getRelativePath(req);
1084 1098
		if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
1085 1099
			resp.sendError(WebdavStatus.SC_FORBIDDEN);
1086 1100
			return;
1087 1101
		}
1088 1102

  
1089
		User user = getUser(req);
1103
		final User user = getUser(req);
1090 1104
		boolean exists = true;
1091 1105
		try {
1092 1106
			getService().getResourceAtPath(user.getId(), path, true);
......
1139 1153
		}
1140 1154
		try {
1141 1155
			if (parent instanceof FolderDTO) {
1142
				FolderDTO folder = (FolderDTO) parent;
1143
				getService().createFolder(user.getId(), folder.getId(), getLastElement(path));
1156
				final FolderDTO folder = (FolderDTO) parent;
1157
				new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1158
					@Override
1159
					public Void call() throws Exception {
1160
						getService().createFolder(user.getId(), folder.getId(), getLastElement(path));
1161
						return null;
1162
					}
1163
				});
1144 1164
			} else {
1145 1165
				resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1146 1166
				return;
......
1160 1180
		} catch (RpcException e) {
1161 1181
			resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1162 1182
			return;
1183
		} catch (Exception e) {
1184
			resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1185
			return;
1163 1186
		}
1164 1187
		resp.setStatus(WebdavStatus.SC_CREATED);
1165 1188
	}
......
2993 3016
		if (logger.isDebugEnabled())
2994 3017
			logger.debug("Copy: " + source + " To: " + dest);
2995 3018

  
2996
		User user = getUser(req);
3019
		final User user = getUser(req);
2997 3020
		Object object = null;
2998 3021
		try {
2999 3022
			object = getService().getResourceAtPath(user.getId(), source, true);
......
3001 3024
		}
3002 3025

  
3003 3026
		if (object instanceof FolderDTO) {
3004
			FolderDTO folder = (FolderDTO) object;
3027
			final FolderDTO folder = (FolderDTO) object;
3005 3028
			try {
3006
				getService().copyFolder(user.getId(), folder.getId(), dest);
3029
				final String des = dest;
3030
				new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3031
					@Override
3032
					public Void call() throws Exception {
3033
						getService().copyFolder(user.getId(), folder.getId(), des);
3034
						return null;
3035
					}
3036
				});
3007 3037
			} catch (ObjectNotFoundException e) {
3008 3038
				errorList.put(dest, new Integer(WebdavStatus.SC_CONFLICT));
3009 3039
				return false;
......
3013 3043
			} catch (InsufficientPermissionsException e) {
3014 3044
				errorList.put(dest, new Integer(WebdavStatus.SC_FORBIDDEN));
3015 3045
				return false;
3046
			} catch (Exception e) {
3047
				errorList.put(dest, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3048
				return false;
3016 3049
			}
3017 3050

  
3018 3051
			try {
......
3048 3081
			}
3049 3082

  
3050 3083
		} else if (object instanceof FileHeaderDTO) {
3051
			FileHeaderDTO file = (FileHeaderDTO) object;
3084
			final FileHeaderDTO file = (FileHeaderDTO) object;
3052 3085
			try {
3053
				getService().copyFile(user.getId(), file.getId(), dest);
3086
				final String des = dest;
3087
				new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3088
					@Override
3089
					public Void call() throws Exception {
3090
						getService().copyFile(user.getId(), file.getId(), des);
3091
						return null;
3092
					}
3093
				});
3054 3094
			} catch (ObjectNotFoundException e) {
3055 3095
				errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3056 3096
				return false;
......
3066 3106
			} catch (GSSIOException e) {
3067 3107
				errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3068 3108
				return false;
3109
			} catch (Exception e) {
3110
				errorList.put(dest, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3111
				return false;
3069 3112
			}
3070 3113
		} else {
3071 3114
			errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
......
3116 3159
			return false;
3117 3160
		}
3118 3161

  
3119
		User user = getUser(req);
3162
		final User user = getUser(req);
3120 3163
		boolean exists = true;
3121 3164
		Object object = null;
3122 3165
		try {
......
3142 3185

  
3143 3186
		if (file != null)
3144 3187
			try {
3145
				getService().deleteFile(user.getId(), file.getId());
3188
				final FileHeaderDTO f = file;
3189
				new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3190
					@Override
3191
					public Void call() throws Exception {
3192
						getService().deleteFile(user.getId(), f.getId());
3193
						return null;
3194
					}
3195
				});
3146 3196
			} catch (InsufficientPermissionsException e) {
3147 3197
				resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
3148 3198
				return false;
......
3154 3204
			} catch (RpcException e) {
3155 3205
				resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
3156 3206
				return false;
3207
			} catch (Exception e) {
3208
				resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
3209
				return false;
3157 3210
			}
3158 3211
		else if (folder != null) {
3159 3212
			Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
3160 3213
			deleteCollection(req, folder, path, errorList);
3161 3214
			try {
3162
				getService().deleteFolder(user.getId(), folder.getId());
3215
				final FolderDTO f = folder;
3216
				new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3217
					@Override
3218
					public Void call() throws Exception {
3219
						getService().deleteFolder(user.getId(), f.getId());
3220
						return null;
3221
					}
3222
				});
3163 3223
			} catch (InsufficientPermissionsException e) {
3164 3224
				errorList.put(path, new Integer(WebdavStatus.SC_METHOD_NOT_ALLOWED));
3165 3225
			} catch (ObjectNotFoundException e) {
3166 3226
				errorList.put(path, new Integer(WebdavStatus.SC_NOT_FOUND));
3167 3227
			} catch (RpcException e) {
3168 3228
				errorList.put(path, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3229
			} catch (Exception e) {
3230
				errorList.put(path, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3169 3231
			}
3170 3232

  
3171 3233
			if (!errorList.isEmpty()) {
......
3216 3278
				errorList.put(childName, new Integer(WebdavStatus.SC_LOCKED));
3217 3279
			else
3218 3280
				try {
3219
					User user = getUser(req);
3281
					final User user = getUser(req);
3220 3282
					Object object = getService().getResourceAtPath(user.getId(), childName, true);
3221 3283
					FolderDTO childFolder = null;
3222 3284
					FileHeaderDTO childFile = null;
......
3225 3287
					else
3226 3288
						childFile = (FileHeaderDTO) object;
3227 3289
					if (childFolder != null) {
3290
						final FolderDTO cf = childFolder;
3228 3291
						deleteCollection(req, childFolder, childName, errorList);
3229
						getService().deleteFolder(user.getId(), childFolder.getId());
3230
					} else if (childFile != null)
3231
						getService().deleteFile(user.getId(), childFile.getId());
3292
						new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3293
							@Override
3294
							public Void call() throws Exception {
3295
								getService().deleteFolder(user.getId(), cf.getId());
3296
								return null;
3297
							}
3298
						});
3299
					} else if (childFile != null) {
3300
						final FileHeaderDTO cf = childFile;
3301
						new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
3302
							@Override
3303
							public Void call() throws Exception {
3304
								getService().deleteFile(user.getId(), cf.getId());
3305
								return null;
3306
							}
3307
						});
3308
					}
3232 3309
				} catch (ObjectNotFoundException e) {
3233 3310
					errorList.put(childName, new Integer(WebdavStatus.SC_NOT_FOUND));
3234 3311
				} catch (InsufficientPermissionsException e) {
3235 3312
					errorList.put(childName, new Integer(WebdavStatus.SC_FORBIDDEN));
3236 3313
				} catch (RpcException e) {
3237 3314
					errorList.put(childName, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3315
				} catch (Exception e) {
3316
					errorList.put(childName, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3238 3317
				}
3239 3318
		}
3240 3319
	}

Also available in: Unified diff