Upgraded solr to 1.4 and refactored things a bit so that index rebuild is done synchr...
[pithos] / src / gr / ebs / gss / server / ejb / ExternalAPIBean.java
1 /*
2  * Copyright 2007, 2008, 2009, 2010  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 static gr.ebs.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22 import gr.ebs.gss.client.exceptions.DuplicateNameException;
23 import gr.ebs.gss.client.exceptions.GSSIOException;
24 import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
25 import gr.ebs.gss.client.exceptions.InvitationUsedException;
26 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
27 import gr.ebs.gss.client.exceptions.QuotaExceededException;
28 import gr.ebs.gss.server.domain.AuditInfo;
29 import gr.ebs.gss.server.domain.FileBody;
30 import gr.ebs.gss.server.domain.FileHeader;
31 import gr.ebs.gss.server.domain.FileTag;
32 import gr.ebs.gss.server.domain.FileUploadStatus;
33 import gr.ebs.gss.server.domain.Folder;
34 import gr.ebs.gss.server.domain.Group;
35 import gr.ebs.gss.server.domain.Invitation;
36 import gr.ebs.gss.server.domain.Nonce;
37 import gr.ebs.gss.server.domain.Permission;
38 import gr.ebs.gss.server.domain.User;
39 import gr.ebs.gss.server.domain.UserClass;
40 import gr.ebs.gss.server.domain.dto.FileBodyDTO;
41 import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
42 import gr.ebs.gss.server.domain.dto.FolderDTO;
43 import gr.ebs.gss.server.domain.dto.GroupDTO;
44 import gr.ebs.gss.server.domain.dto.PermissionDTO;
45 import gr.ebs.gss.server.domain.dto.StatsDTO;
46 import gr.ebs.gss.server.domain.dto.UserDTO;
47
48 import java.io.File;
49 import java.io.FileInputStream;
50 import java.io.FileNotFoundException;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.io.StringWriter;
55 import java.io.UnsupportedEncodingException;
56 import java.net.MalformedURLException;
57 import java.util.ArrayList;
58 import java.util.Date;
59 import java.util.Iterator;
60 import java.util.LinkedHashSet;
61 import java.util.LinkedList;
62 import java.util.List;
63 import java.util.Locale;
64 import java.util.Random;
65 import java.util.Set;
66 import java.util.StringTokenizer;
67
68 import javax.ejb.EJB;
69 import javax.ejb.EJBException;
70 import javax.ejb.EJBTransactionRolledbackException;
71 import javax.ejb.Stateless;
72 import javax.ejb.TransactionAttribute;
73 import javax.ejb.TransactionAttributeType;
74 import javax.jms.Connection;
75 import javax.jms.ConnectionFactory;
76 import javax.jms.JMSException;
77 import javax.jms.MapMessage;
78 import javax.jms.MessageProducer;
79 import javax.jms.Queue;
80 import javax.jms.QueueConnectionFactory;
81 import javax.jms.Session;
82 import javax.naming.Context;
83 import javax.naming.InitialContext;
84 import javax.naming.NamingException;
85 import javax.persistence.PersistenceException;
86 import javax.xml.parsers.DocumentBuilder;
87 import javax.xml.parsers.DocumentBuilderFactory;
88 import javax.xml.parsers.ParserConfigurationException;
89 import javax.xml.transform.OutputKeys;
90 import javax.xml.transform.Transformer;
91 import javax.xml.transform.TransformerConfigurationException;
92 import javax.xml.transform.TransformerException;
93 import javax.xml.transform.TransformerFactory;
94 import javax.xml.transform.dom.DOMSource;
95 import javax.xml.transform.stream.StreamResult;
96
97 import org.apache.commons.configuration.Configuration;
98 import org.apache.commons.httpclient.HttpClient;
99 import org.apache.commons.httpclient.HttpException;
100 import org.apache.commons.httpclient.NameValuePair;
101 import org.apache.commons.httpclient.methods.GetMethod;
102 import org.apache.commons.httpclient.methods.PostMethod;
103 import org.apache.commons.httpclient.methods.StringRequestEntity;
104 import org.apache.commons.lang.StringUtils;
105 import org.apache.commons.logging.Log;
106 import org.apache.commons.logging.LogFactory;
107 import org.apache.solr.client.solrj.SolrServerException;
108 import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
109 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
110 import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
111 import org.apache.solr.common.SolrException;
112 import org.apache.solr.common.SolrInputDocument;
113 import org.hibernate.exception.ConstraintViolationException;
114 import org.w3c.dom.DOMException;
115 import org.w3c.dom.Document;
116 import org.w3c.dom.Node;
117 import org.w3c.dom.NodeList;
118 import org.xml.sax.SAXException;
119
120 import com.novell.ldap.LDAPAttribute;
121 import com.novell.ldap.LDAPAttributeSet;
122 import com.novell.ldap.LDAPConnection;
123 import com.novell.ldap.LDAPEntry;
124 import com.novell.ldap.LDAPException;
125
126 /**
127  * The concrete implementation of the ExternalAPI interface.
128  *
129  * @author past
130  */
131 @Stateless
132 public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
133         /**
134          * The default MIME type for files without an explicit one.
135          */
136         private static final String DEFAULT_MIME_TYPE = "application/octet-stream";
137
138         /**
139          * The size of the buffer that is used to temporarily store chunks of
140          * uploaded files, while storing them to the file repository.
141          */
142         private static final int UPLOAD_BUFFER_SIZE = 1024 * 4;
143
144         /**
145          * The logger.
146          */
147         private static Log logger = LogFactory.getLog(ExternalAPIBean.class);
148
149         /**
150          * Injected reference to the GSSDAO data access facade.
151          */
152         @EJB
153         private GSSDAO dao;
154
155
156         /**
157          * A cached random number generator for creating unique filenames.
158          */
159         private static Random random = new Random();
160
161         /**
162          * Mark the folder and all of its parent folders as modified from the specified user.
163          */
164         private void touchParentFolders(Folder folder, User user, Date date) {
165                 Folder f = folder;
166                 while (f != null) {
167                         AuditInfo ai = f.getAuditInfo();
168                         ai.setModifiedBy(user);
169                         ai.setModificationDate(date);
170                         f.setAuditInfo(ai);
171                         f = f.getParent();
172                 }
173         }
174
175         @Override
176         public FolderDTO getRootFolder(Long userId) throws ObjectNotFoundException {
177                 if (userId == null)
178                         throw new ObjectNotFoundException("No user specified");
179                 Folder folder = dao.getRootFolder(userId);
180                 return folder.getDTO();
181         }
182
183         @Override
184         public FolderDTO getFolder(final Long userId, final Long folderId) throws ObjectNotFoundException, InsufficientPermissionsException {
185                 if (userId == null)
186                         throw new ObjectNotFoundException("No user specified");
187                 if (folderId == null)
188                         throw new ObjectNotFoundException("No folder specified");
189                 final User user = dao.getEntityById(User.class, userId);
190                 final Folder folder = dao.getEntityById(Folder.class, folderId);
191                 // Check permissions
192                 if (!folder.hasReadPermission(user))
193                         throw new InsufficientPermissionsException("You don't have the permissions to read this folder");
194                 return folder.getDTO();
195         }
196
197         @Override
198         public User getUser(Long userId) throws ObjectNotFoundException {
199                 if (userId == null)
200                         throw new ObjectNotFoundException("No user specified");
201                 return dao.getEntityById(User.class, userId);
202         }
203
204         @Override
205         public UserDTO getUserDTO(final Long userId) throws ObjectNotFoundException {
206                 return getUser(userId).getDTO();
207         }
208
209         @Override
210         public GroupDTO getGroup(final Long groupId) throws ObjectNotFoundException {
211                 if (groupId == null)
212                         throw new ObjectNotFoundException("No group specified");
213                 final Group group = dao.getEntityById(Group.class, groupId);
214                 return group.getDTO();
215         }
216
217         @Override
218         public GroupDTO getGroup(Long userId, String name) throws ObjectNotFoundException {
219                 if (userId == null)
220                         throw new ObjectNotFoundException("No user specified");
221                 if (name == null)
222                         throw new ObjectNotFoundException("No group specified");
223                 User user = dao.getEntityById(User.class, userId);
224                 List<Group> groups = user.getGroupsSpecified();
225                 for (Group group: groups)
226                         if (group.getName().equals(name))
227                                 return group.getDTO();
228                 throw new ObjectNotFoundException("Group " + name + " not found");
229         }
230
231         @Override
232         public List<GroupDTO> getGroups(final Long userId) throws ObjectNotFoundException {
233                 if (userId == null)
234                         throw new ObjectNotFoundException("No user specified");
235                 final List<Group> groups = dao.getGroups(userId);
236                 final List<GroupDTO> result = new ArrayList<GroupDTO>();
237                 for (final Group g : groups)
238                         result.add(g.getDTO());
239                 return result;
240         }
241
242         @Override
243         public List<FileHeaderDTO> getFiles(Long userId, Long folderId, boolean ignoreDeleted)
244                         throws ObjectNotFoundException, InsufficientPermissionsException {
245                 // Validate.
246                 if (userId == null)
247                         throw new ObjectNotFoundException("No user specified");
248                 if (folderId == null)
249                         throw new ObjectNotFoundException("No folder specified");
250                 User user = dao.getEntityById(User.class, userId);
251                 Folder folder = dao.getEntityById(Folder.class, folderId);
252                 if (!folder.hasReadPermission(user))
253                         throw new InsufficientPermissionsException("You don't have the permissions to read this folder");
254                 // Do the actual work.
255                 List<FileHeaderDTO> result = new ArrayList<FileHeaderDTO>();
256                 List<FileHeader> files = dao.getFiles(folderId, userId, ignoreDeleted);
257                 for (FileHeader f : files)
258                         result.add(f.getDTO());
259                 return result;
260         }
261
262         @Override
263         public List<UserDTO> getUsers(final Long userId, final Long groupId) throws ObjectNotFoundException {
264                 // Validate.
265                 if (userId == null)
266                         throw new ObjectNotFoundException("No user specified");
267                 if (groupId == null)
268                         throw new ObjectNotFoundException("No group specified");
269
270                 // Do the actual work.
271                 final List<User> users = dao.getUsers(groupId);
272                 final List<UserDTO> result = new ArrayList<UserDTO>();
273                 for (final User u : users)
274                         result.add(u.getDTO());
275                 return result;
276         }
277
278         @Override
279         public FolderDTO createFolder(Long userId, Long parentId, String name)
280                         throws DuplicateNameException, ObjectNotFoundException, InsufficientPermissionsException {
281                 // Validate.
282                 if (userId == null)
283                         throw new ObjectNotFoundException("No user specified");
284                 if (StringUtils.isEmpty(name))
285                         throw new ObjectNotFoundException("New folder name is empty");
286                 if (parentId == null)
287                         throw new ObjectNotFoundException("No parent specified");
288                 if (dao.existsFolderOrFile(parentId, name))
289                         throw new DuplicateNameException("A folder or file with the name '" +
290                                                 name + "' already exists at this level");
291
292                 User creator = dao.getEntityById(User.class, userId);
293
294                 Folder parent = null;
295                 try {
296                         parent = dao.getEntityById(Folder.class, parentId);
297                 } catch (ObjectNotFoundException onfe) {
298                         // Supply a more accurate problem description.
299                         throw new ObjectNotFoundException("Parent folder not found");
300                 }
301                 if (!parent.hasWritePermission(creator))
302                         throw new InsufficientPermissionsException("You don't have the permissions" +
303                                         " to write to this folder");
304
305                 // Do the actual work.
306                 return createFolder(name, parent, creator);
307         }
308
309         /**
310          * Create a new folder with the provided name, parent and owner.
311          *
312          * @param name
313          * @param parent
314          * @param creator
315          * @return the new folder
316          */
317         private FolderDTO createFolder(String name, Folder parent, User creator) {
318                 Folder folder = new Folder();
319                 folder.setName(name);
320                 if (parent != null) {
321                         parent.addSubfolder(folder);
322                         folder.setOwner(parent.getOwner());
323                 } else
324                         folder.setOwner(creator);
325
326                 Date now = new Date();
327                 AuditInfo auditInfo = new AuditInfo();
328                 auditInfo.setCreatedBy(creator);
329                 auditInfo.setCreationDate(now);
330                 auditInfo.setModifiedBy(creator);
331                 auditInfo.setModificationDate(now);
332                 folder.setAuditInfo(auditInfo);
333                 touchParentFolders(folder, auditInfo.getModifiedBy(), auditInfo.getModificationDate());
334
335                 if (parent != null)
336                         for (Permission p : parent.getPermissions()) {
337                                 Permission permission = new Permission();
338                                 permission.setGroup(p.getGroup());
339                                 permission.setUser(p.getUser());
340                                 permission.setRead(p.getRead());
341                                 permission.setWrite(p.getWrite());
342                                 permission.setModifyACL(p.getModifyACL());
343                                 folder.addPermission(permission);
344                         }
345                 else {
346                         Permission permission = new Permission();
347                         permission.setUser(creator);
348                         permission.setRead(true);
349                         permission.setWrite(true);
350                         permission.setModifyACL(true);
351                         folder.addPermission(permission);
352                 }
353
354                 if(parent != null)
355                         folder.setReadForAll(parent.isReadForAll());
356
357                 dao.create(folder);
358                 return folder.getDTO();
359         }
360
361         @Override
362         public void deleteFolder(final Long userId, final Long folderId) throws InsufficientPermissionsException, ObjectNotFoundException {
363                 // Validate.
364                 if (userId == null)
365                         throw new ObjectNotFoundException("No user specified");
366                 if (folderId == null)
367                         throw new ObjectNotFoundException("No folder specified");
368
369                 // Do the actual work.
370                 final Folder folder = dao.getEntityById(Folder.class, folderId);
371                 final Folder parent = folder.getParent();
372                 if (parent == null)
373                         throw new ObjectNotFoundException("Deleting the root folder is not allowed");
374                 final User user = dao.getEntityById(User.class, userId);
375                 if (!folder.hasDeletePermission(user)) {
376                         logger.info("User " + user.getId() + " cannot delete folder " + folder.getName() + "(" + folder.getId() + ")");
377                         throw new InsufficientPermissionsException("User " + user.getId() + " cannot delete folder " + folder.getName() + "(" + folder.getId() + ")");
378                 }
379                 removeSubfolderFiles(folder);
380                 parent.removeSubfolder(folder);
381                 dao.delete(folder);
382                 touchParentFolders(parent, user, new Date());
383         }
384
385         /**
386          * Traverses the folder and deletes all actual files (file system)
387          * regardless of permissions
388          *
389          * @param folder
390          */
391         private void removeSubfolderFiles(Folder folder) {
392                 //remove files for all subfolders
393                 for (Folder subfolder:folder.getSubfolders())
394                         removeSubfolderFiles(subfolder);
395                 //remove this folder's file bodies (actual files)
396                 for (FileHeader file:folder.getFiles()) {
397                         for (FileBody body:file.getBodies())
398                                 deleteActualFile(body.getStoredFilePath());
399                         indexFile(file.getId(), true);
400                 }
401         }
402
403         @Override
404         @SuppressWarnings("unchecked")
405         public List<FolderDTO> getSubfolders(Long userId, Long folderId)
406                         throws ObjectNotFoundException, InsufficientPermissionsException {
407                 if (userId == null)
408                         throw new ObjectNotFoundException("No user specified");
409                 if (folderId == null)
410                         throw new ObjectNotFoundException("No folder specified");
411                 User user = dao.getEntityById(User.class, userId);
412                 Folder folder = dao.getEntityById(Folder.class, folderId);
413                 if (!folder.hasReadPermission(user))
414                         throw new InsufficientPermissionsException("You don't have the permissions to read this folder");
415                 List<FolderDTO> result = new ArrayList<FolderDTO>();
416                 if (folder.hasReadPermission(user))
417                         for (Folder f : folder.getSubfolders())
418                                 if (f.hasReadPermission(user) && !f.isDeleted())
419                                         result.add(f.getDTO());
420                 return result;
421         }
422
423         @Override
424         public FolderDTO updateFolder(Long userId, Long folderId, String folderName,
425                                 Boolean readForAll,
426                                 Set<PermissionDTO> permissions)
427                         throws InsufficientPermissionsException, ObjectNotFoundException,
428                         DuplicateNameException {
429
430                 // Validate.
431                 if (userId == null)
432                         throw new ObjectNotFoundException("No user specified");
433                 if (folderId == null)
434                         throw new ObjectNotFoundException("No folder specified");
435
436                 Folder folder = dao.getEntityById(Folder.class, folderId);
437                 User user = dao.getEntityById(User.class, userId);
438                 if (folderName != null && !folder.hasWritePermission(user))
439                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
440                 if(permissions != null && !permissions.isEmpty() && !folder.hasModifyACLPermission(user))
441                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
442                 // Check permissions for making file public.
443                 if (readForAll != null && !user.equals(folder.getOwner()))
444                                 throw new InsufficientPermissionsException("Only the owner can make a folder public or not public");
445
446                 Folder parent = folder.getParent();
447                 if (folderName != null) {
448                         if (parent != null)
449                                 if (!folder.getName().equals(folderName) && dao.existsFolderOrFile(parent.getId(), folderName))
450                                         throw new DuplicateNameException("A folder or file with the name '" + folderName + "' already exists at this level");
451
452                         // Do the actual modification.
453                         folder.setName(folderName);
454                 }
455                 if (permissions != null)
456                         setFolderPermissions(user, folder, permissions);
457                 if (readForAll != null)
458                         setFolderReadForAll(user, folder, readForAll);
459                 folder.getAuditInfo().setModificationDate(new Date());
460                 folder.getAuditInfo().setModifiedBy(user);
461                 dao.update(folder);
462                 touchParentFolders(folder, user, new Date());
463                 return folder.getDTO();
464         }
465
466         @Override
467         public void createGroup(final Long userId, final String name) throws ObjectNotFoundException, DuplicateNameException {
468                 // Validate.
469                 if (userId == null)
470                         throw new ObjectNotFoundException("No user specified");
471                 if (StringUtils.isEmpty(name))
472                         throw new ObjectNotFoundException("New group name is empty");
473                 if (name.indexOf('/')>=0)
474                         throw new IllegalArgumentException("Character '/' is not allowed in group name");
475                 if (dao.existsGroup(userId, name))
476                         throw new DuplicateNameException("A group with the name '" + name + "' already exists");
477
478                 // TODO: Check permissions
479
480                 final User owner = dao.getEntityById(User.class, userId);
481
482                 // Do the actual work.
483                 owner.createGroup(name);
484         }
485
486         @Override
487         public void deleteGroup(final Long userId, final Long groupId) throws ObjectNotFoundException, InsufficientPermissionsException {
488                 // Validate.
489                 if (userId == null)
490                         throw new ObjectNotFoundException("No user specified");
491                 if (groupId == null)
492                         throw new ObjectNotFoundException("No group specified");
493
494                 // Do the actual work.
495                 final User owner = dao.getEntityById(User.class, userId);
496                 final Group group = dao.getEntityById(Group.class, groupId);
497                 final Date now = new Date();
498                 // Only delete the group if actually owned by the user.
499                 if (group.getOwner().equals(owner)) {
500                         List<Folder> folders = dao.getFoldersPermittedForGroup(userId, groupId);
501                         for (Folder f : folders){
502                                 f.getPermissions().removeAll(group.getPermissions());
503                                 touchFolder(f, owner, now);
504                                 for(FileHeader file : f.getFiles()){
505                                         file.getPermissions().removeAll(group.getPermissions());
506                                         touchFile(file, owner, now);
507                                 }
508                         }
509                         List<FileHeader> files = dao.getFilesPermittedForGroup(userId, groupId);
510                         for(FileHeader h : files){
511                                 h.getPermissions().removeAll(group.getPermissions());
512                                 touchFile(h, owner, now);
513                         }
514                         owner.removeSpecifiedGroup(group);
515                         dao.delete(group);
516                 }
517                 else throw new InsufficientPermissionsException("You are not the owner of this group");
518         }
519
520         @Override
521         public FileHeaderDTO createFile(Long userId, Long folderId, String name, String mimeType, InputStream stream)
522                         throws DuplicateNameException, ObjectNotFoundException, GSSIOException,
523                         InsufficientPermissionsException, QuotaExceededException {
524                 File file = null;
525                 try {
526                         file = uploadFile(stream, userId);
527                 } catch ( IOException ioe) {
528                         // Supply a more accurate problem description.
529                         throw new GSSIOException("Problem creating file",ioe);
530                 }
531                 return createFile(userId, folderId, name, mimeType, file.length(), file.getAbsolutePath());
532         }
533
534         /* (non-Javadoc)
535          * @see gr.ebs.gss.server.ejb.ExternalAPIRemote#indexFile(java.lang.Long, boolean)
536          */
537         @Override
538         public void indexFile(Long fileId, boolean delete) {
539                 Connection qConn = null;
540                 Session session = null;
541                 MessageProducer sender = null;
542                 try {
543                         Context jndiCtx = new InitialContext();
544                         ConnectionFactory factory = (QueueConnectionFactory) jndiCtx.lookup("java:/JmsXA");
545                         Queue queue = (Queue) jndiCtx.lookup("queue/gss-indexingQueue");
546                         qConn = factory.createConnection();
547                         session = qConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
548                         sender = session.createProducer(queue);
549
550                         MapMessage map = session.createMapMessage();
551                         map.setObject("id", fileId);
552                         map.setBoolean("delete", delete);
553                         sender.send(map);
554                 }
555                 catch (NamingException e) {
556                         logger.error("Index was not updated: ", e);
557                 }
558                 catch (JMSException e) {
559                         logger.error("Index was not updated: ", e);
560                 }
561                 finally {
562                         try {
563                                 if (sender != null)
564                                         sender.close();
565                                 if (session != null)
566                                         session.close();
567                                 if (qConn != null)
568                                         qConn.close();
569                         }
570                         catch (JMSException e) {
571                                 logger.warn(e);
572                         }
573                 }
574         }
575
576
577
578         /**
579          * A helper method that generates a unique file path for a stored file. The
580          * files are stored using random hash names that are distributed evenly in
581          * a 2-level tree of subdirectories named after the first two hex characters
582          * in the name. For example, file ab1234cd5769f will be stored in the path
583          * /file-repository-root/a/b/ab1234cd5769f. The directories will be created
584          * if they don't already exist.
585          *
586          * @return a unique new file path
587          */
588         private String generateRepositoryFilePath() {
589                 String filename = Long.toHexString(random.nextLong());
590                 String fileRepositoryPath = getConfiguration().getString("fileRepositoryPath","/tmp");
591                 File root = new File(fileRepositoryPath);
592                 if (!root.exists())
593                         root.mkdirs();
594                 File firstFolder = new File(root + File.separator + filename.substring(0, 1));
595                 if (!firstFolder.exists())
596                         firstFolder.mkdir();
597                 File secondFolder = new File(firstFolder + File.separator + filename.substring(1, 2));
598                 if (!secondFolder.exists())
599                         secondFolder.mkdir();
600                 return secondFolder + File.separator + filename;
601         }
602
603         @Override
604         public void deleteFile(final Long userId, final Long fileId) throws ObjectNotFoundException, InsufficientPermissionsException {
605                 // Validate.
606                 if (userId == null)
607                         throw new ObjectNotFoundException("No user specified");
608                 if (fileId == null)
609                         throw new ObjectNotFoundException("No file specified");
610
611                 // Do the actual work.
612                 final FileHeader file = dao.getEntityById(FileHeader.class, fileId);
613                 final Folder parent = file.getFolder();
614                 if (parent == null)
615                         throw new ObjectNotFoundException("The specified file has no parent folder");
616                 final User user = dao.getEntityById(User.class, userId);
617                 if (!file.hasDeletePermission(user))
618                         throw new InsufficientPermissionsException("User " + user.getId() + " cannot delete file " + file.getName() + "(" + file.getId() + ")");
619                 for (final FileBody body : file.getBodies())
620                         deleteActualFile(body.getStoredFilePath());
621                 dao.delete(file);
622                 touchParentFolders(parent, user, new Date());
623                 indexFile(fileId, true);
624         }
625
626         @Override
627         public void deleteActualFile(String path) {
628                 if (path == null)
629                         return;
630                 File file = new File(path);
631                 if (!file.delete())
632                         logger.error("Could not delete file " + path);
633         }
634
635         @Override
636         public void createTag(final Long userId, final Long fileHeaderId, final String tag) throws ObjectNotFoundException {
637                 if (userId == null)
638                         throw new ObjectNotFoundException("No user specified");
639                 if (fileHeaderId == null)
640                         throw new ObjectNotFoundException("No file specified");
641                 if (StringUtils.isEmpty(tag))
642                         throw new ObjectNotFoundException("Tag is empty");
643
644                 final User user = dao.getEntityById(User.class, userId);
645                 final FileHeader fh = dao.getEntityById(FileHeader.class, fileHeaderId);
646                 final Folder parent = fh.getFolder();
647                 if (parent == null)
648                         throw new ObjectNotFoundException("The specified file has no parent folder");
649                 user.addTag(fh, tag);
650                 touchParentFolders(parent, user, new Date());
651         }
652
653         @Override
654         public Set<String> getUserTags(final Long userId) throws ObjectNotFoundException {
655                 return dao.getUserTags(userId);
656         }
657
658         @Override
659         public void updateFile(Long userId, Long fileId, String name,
660                                 String tagSet, Date modificationDate, Boolean versioned,
661                                 Boolean readForAll,     Set<PermissionDTO> permissions)
662                         throws DuplicateNameException, ObjectNotFoundException, InsufficientPermissionsException {
663                 if (userId == null)
664                         throw new ObjectNotFoundException("No user specified");
665                 if (fileId == null)
666                         throw new ObjectNotFoundException("No file specified");
667                 FileHeader file = dao.getEntityById(FileHeader.class, fileId);
668                 final Folder parent = file.getFolder();
669                 if (parent == null)
670                         throw new ObjectNotFoundException("The specified file has no parent folder");
671
672                 User user = dao.getEntityById(User.class, userId);
673                 // Check permissions for modifying the file metadata.
674                 if ((name != null || tagSet != null || modificationDate != null || versioned != null) && !file.hasWritePermission(user))
675                         throw new InsufficientPermissionsException("User " + user.getId() +     " cannot update file " + file.getName() + "(" + file.getId() + ")");
676                 // Check permissions for making file public.
677                 if (readForAll != null && !user.equals(file.getOwner()))
678                                 throw new InsufficientPermissionsException("Only the owner can make a file public or not public");
679                 // Check permissions for modifying the ACL.
680                 if(permissions != null && !permissions.isEmpty() &&     !file.hasModifyACLPermission(user))
681                         throw new InsufficientPermissionsException("User " + user.getId() +     " cannot update the permissions on file " +     file.getName() + "(" + file.getId() + ")");
682
683                 if (name != null) {
684                         // Do plain check for file already exists.
685                         // Extreme concurrency case should be caught by constraint violation later.
686                         if (dao.existsFolderOrFile(parent.getId(), name)) throw new DuplicateNameException("A file or folder with the name '" + name + "' already exists");
687                         file.setName(name);
688                 }
689
690                 if (modificationDate != null)
691                         file.getAuditInfo().setModificationDate(modificationDate);
692                 else
693                         file.getAuditInfo().setModificationDate(new Date());
694                 file.getAuditInfo().setModifiedBy(user);
695
696                 List<FileTag> tags = file.getFileTags();
697                 if (tagSet != null) {
698                         Iterator<FileTag> i = tags.iterator();
699                         while (i.hasNext()) {
700                                 FileTag tag = i.next();
701                                 i.remove();
702                                 tag.setFile(null);
703                                 user.removeTag(tag);
704                                 dao.delete(tag);
705                         }
706                         dao.flush();
707                         StringTokenizer st = new StringTokenizer(tagSet, ",");
708                         while (st.hasMoreTokens())
709                                 new FileTag(user, file, st.nextToken().trim());
710                 }
711                 if (versioned != null && !file.isVersioned() == versioned) {
712                         if (file.isVersioned())
713                                 removeOldVersions(userId, fileId);
714                         file.setVersioned(versioned);
715                 }
716                 if (readForAll != null && user.equals(file.getOwner()))
717                         file.setReadForAll(readForAll);
718                 if (permissions != null && !permissions.isEmpty())
719                         setFilePermissions(file, permissions);
720
721                 /*
722                  * Force constraint violation to manifest itself here.
723                  * This should cover extreme concurrency cases that the simple check
724                  * above hasn't caught.
725                  */
726                 try {
727                         dao.flush();
728                 }
729                 catch (EJBTransactionRolledbackException e) {
730                         Throwable cause = e.getCause();
731                         if (cause instanceof PersistenceException && cause.getCause() instanceof ConstraintViolationException)
732                                 throw new DuplicateNameException("A file or folder with the name '" + name + "' already exists");
733                         throw e;
734                 }
735
736                 touchParentFolders(parent, user, new Date());
737
738                 // Re-index the file if it was modified.
739                 if (name != null || tagSet != null)
740                         indexFile(fileId, false);
741         }
742
743         @Override
744         public InputStream getFileContents(Long userId, Long fileId)
745                         throws ObjectNotFoundException, InsufficientPermissionsException {
746                 if (userId == null)
747                         throw new ObjectNotFoundException("No user specified");
748                 if (fileId == null)
749                         throw new ObjectNotFoundException("No file specified");
750
751                 FileHeader header = dao.getEntityById(FileHeader.class, fileId);
752                 User user = dao.getEntityById(User.class, userId);
753                 if (!header.hasReadPermission(user)) {
754                         logger.info("User " + user.getId() + " cannot read file " + header.getName() + "(" + fileId + ")");
755                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
756                 }
757
758                 File f = new File(header.getCurrentBody().getStoredFilePath());
759                 try {
760                         return new FileInputStream(f);
761                 } catch (FileNotFoundException e) {
762                         logger.error("Could not locate the contents of file " + f.getAbsolutePath());
763                         throw new ObjectNotFoundException("The file contents could not be located");
764                 }
765         }
766
767         /* (non-Javadoc)
768          * @see gr.ebs.gss.server.ejb.ExternalAPI#getFileContents(java.lang.Long, java.lang.Long, java.lang.Long)
769          */
770         @Override
771         public InputStream getFileContents(Long userId, Long fileId, Long bodyId) throws ObjectNotFoundException, InsufficientPermissionsException {
772                 if (userId == null)
773                         throw new ObjectNotFoundException("No user specified");
774                 if (fileId == null)
775                         throw new ObjectNotFoundException("No file specified");
776                 if (bodyId == null)
777                         throw new ObjectNotFoundException("No file specified");
778
779                 final FileHeader header = dao.getEntityById(FileHeader.class, fileId);
780                 final FileBody body = dao.getEntityById(FileBody.class, bodyId);
781                 final User user = dao.getEntityById(User.class, userId);
782                 if (!header.hasReadPermission(user)) {
783                         logger.info("User " + user.getId() + " cannot read file " + header.getName() + "(" + fileId + ")");
784                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
785                 }
786
787                 File f = new File(body.getStoredFilePath());
788                 try {
789                         return new FileInputStream(f);
790                 } catch (FileNotFoundException e) {
791                         logger.error("Could not locate the contents of file " + f.getAbsolutePath());
792                         throw new ObjectNotFoundException("The file contents could not be located");
793                 }
794         }
795
796         @Override
797         public FileHeaderDTO getFile(Long userId, Long fileId) throws ObjectNotFoundException, InsufficientPermissionsException {
798                 if (userId == null)
799                         throw new ObjectNotFoundException("No user specified");
800                 if (fileId == null)
801                         throw new ObjectNotFoundException("No file specified");
802                 final User user = dao.getEntityById(User.class, userId);
803                 final FileHeader file = dao.getEntityById(FileHeader.class, fileId);
804                 if (!file.hasReadPermission(user) && !file.getFolder().hasReadPermission(user))
805                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
806                 return file.getDTO();
807         }
808
809         @Override
810         public FileBodyDTO getFileBody(Long userId, Long fileId, Long bodyId) throws ObjectNotFoundException, InsufficientPermissionsException {
811                 if (userId == null)
812                         throw new ObjectNotFoundException("No user specified");
813                 if (fileId == null)
814                         throw new ObjectNotFoundException("No file specified");
815                 User user = dao.getEntityById(User.class, userId);
816                 FileHeader file = dao.getEntityById(FileHeader.class, fileId);
817                 if (!file.hasReadPermission(user) && !file.getFolder().hasReadPermission(user))
818                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
819                 FileBody body = dao.getEntityById(FileBody.class, bodyId);
820                 return body.getDTO();
821         }
822
823         @Override
824         public Object getResourceAtPath(Long ownerId, String path, boolean ignoreDeleted)
825                         throws ObjectNotFoundException {
826                 if (ownerId == null)
827                         throw new ObjectNotFoundException("No user specified");
828                 if (StringUtils.isEmpty(path))
829                         throw new ObjectNotFoundException("No path specified");
830
831                 User owner = dao.getEntityById(User.class, ownerId);
832                 List<String> pathElements = new ArrayList<String>();
833                 StringTokenizer st = new StringTokenizer(path, "/");
834                 while (st.hasMoreTokens())
835                         pathElements.add(st.nextToken());
836                 if (pathElements.size() < 1)
837                         return getRootFolder(owner.getId());
838                 // Store the last element, since it requires special handling.
839                 String lastElement = pathElements.remove(pathElements.size() - 1);
840                 FolderDTO cursor = getRootFolder(owner.getId());
841                 // Traverse and verify the specified folder path.
842                 for (String pathElement : pathElements) {
843                         cursor = getFolder(cursor.getId(), pathElement);
844                         if (cursor.isDeleted())
845                                 throw new ObjectNotFoundException("Folder " + cursor.getPath() + " not found");
846                 }
847
848                 // Use the lastElement to retrieve the actual resource.
849                 Object resource = null;
850                 try {
851                         FileHeaderDTO file = getFile(cursor.getId(), lastElement);
852                         if (ignoreDeleted && file.isDeleted())
853                                 throw new ObjectNotFoundException("Resource not found");
854                         resource = file;
855                 } catch (ObjectNotFoundException e) {
856                         // Perhaps the requested resource is not a file, so
857                         // check for folders as well.
858                         FolderDTO folder = getFolder(cursor.getId(), lastElement);
859                         if (ignoreDeleted && folder.isDeleted())
860                                 throw new ObjectNotFoundException("Resource not found");
861                         resource = folder;
862                 }
863                 return resource;
864         }
865
866         /**
867          * Retrieve a file for the specified user that has the specified name and
868          * its parent folder has id equal to folderId.
869          *
870          * @param userId the ID of the current user
871          * @param folderId the ID of the parent folder
872          * @param name the name of the requested file
873          * @return the file found
874          * @throws ObjectNotFoundException if the specified folder or file was not
875          *             found, with the exception message mentioning the precise
876          *             problem
877          */
878         private FileHeaderDTO getFile(Long folderId, String name) throws ObjectNotFoundException {
879                 if (folderId == null)
880                         throw new ObjectNotFoundException("No parent folder specified");
881                 if (StringUtils.isEmpty(name))
882                         throw new ObjectNotFoundException("No file specified");
883
884                 FileHeader file = dao.getFile(folderId, name);
885                 return file.getDTO();
886         }
887
888         /**
889          * Retrieve a folder for the specified user that has the specified name and
890          * its parent folder has id equal to parentId.
891          *
892          * @param parentId the ID of the parent folder
893          * @param name the name of the requested folder
894          * @return the folder found
895          * @throws ObjectNotFoundException if the specified folder or parent was not
896          *             found, with the exception message mentioning the precise
897          *             problem
898          */
899         private FolderDTO getFolder(Long parentId, String name) throws ObjectNotFoundException {
900                 if (parentId == null)
901                         throw new ObjectNotFoundException("No parent folder specified");
902                 if (StringUtils.isEmpty(name))
903                         throw new ObjectNotFoundException("No folder specified");
904
905                 Folder folder = dao.getFolder(parentId, name);
906                 return folder.getDTO();
907         }
908
909         private FileHeaderDTO updateFileContents(Long userId, Long fileId, String mimeType, InputStream resourceInputStream) throws ObjectNotFoundException, GSSIOException, InsufficientPermissionsException, QuotaExceededException {
910                 File file = null;
911                 try {
912                         file = uploadFile(resourceInputStream, userId);
913                 } catch ( IOException ioe) {
914                         // Supply a more accurate problem description.
915                         throw new GSSIOException("Problem creating file",ioe);
916                 }
917                 return updateFileContents(userId, fileId, mimeType, file.length(), file.getAbsolutePath());
918         }
919
920         @Override
921         public void copyFile(Long userId, Long fileId, String dest) throws ObjectNotFoundException, DuplicateNameException, GSSIOException, InsufficientPermissionsException, QuotaExceededException {
922                 if (userId == null)
923                         throw new ObjectNotFoundException("No user specified");
924                 if (fileId == null)
925                         throw new ObjectNotFoundException("No file specified");
926                 if (StringUtils.isEmpty(dest))
927                         throw new ObjectNotFoundException("No destination specified");
928
929                 Object destination = getResourceAtPath(userId, getParentPath(dest), true);
930                 if (!(destination instanceof FolderDTO))
931                         throw new ObjectNotFoundException("Destination parent folder not found");
932                 FolderDTO parent = (FolderDTO) destination;
933                 copyFile(userId, fileId, parent.getId(), getLastElement(dest));
934         }
935
936         @Override
937         public void copyFileToPath(Long userId, Long ownerId, Long fileId, String dest) throws ObjectNotFoundException, DuplicateNameException, GSSIOException, InsufficientPermissionsException, QuotaExceededException {
938                 if (userId == null)
939                         throw new ObjectNotFoundException("No user specified");
940                 if (ownerId == null)
941                         throw new ObjectNotFoundException("No owner specified");
942                 if (fileId == null)
943                         throw new ObjectNotFoundException("No file specified");
944                 if (StringUtils.isEmpty(dest))
945                         throw new ObjectNotFoundException("No destination specified");
946
947                 Object destination = getResourceAtPath(ownerId, getParentPath(dest), true);
948                 if (!(destination instanceof FolderDTO))
949                         throw new ObjectNotFoundException("Destination parent folder not found");
950                 FolderDTO parent = (FolderDTO) destination;
951                 copyFile(userId, fileId, parent.getId(), getLastElement(dest));
952         }
953
954         @Override
955         public void copyFile(Long userId, Long fileId, Long destId, String destName) throws ObjectNotFoundException, DuplicateNameException, GSSIOException, InsufficientPermissionsException, QuotaExceededException {
956                 if (userId == null)
957                         throw new ObjectNotFoundException("No user specified");
958                 if (fileId == null)
959                         throw new ObjectNotFoundException("No file specified");
960                 if (destId == null)
961                         throw new ObjectNotFoundException("No destination specified");
962                 if (StringUtils.isEmpty(destName))
963                         throw new ObjectNotFoundException("No destination file name specified");
964
965                 FileHeader file = dao.getEntityById(FileHeader.class, fileId);
966                 Folder destination = dao.getEntityById(Folder.class, destId);
967                 User user = dao.getEntityById(User.class, userId);
968                 if (!file.hasReadPermission(user) || !destination.hasWritePermission(user))
969                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
970                 boolean versioned = file.isVersioned();
971                 int versionsNumber = file.getBodies().size();
972                 FileBody oldestBody = file.getBodies().get(0);
973                 assert oldestBody != null;
974                 File contents = new File(oldestBody.getStoredFilePath());
975                 try {
976                         createFile(user.getId(), destination.getId(), destName, oldestBody.getMimeType(), new FileInputStream(contents));
977                         FileHeader copiedFile = dao.getFile(destination.getId(), destName);
978                         copiedFile.setVersioned(versioned);
979                         dao.flush();
980                         if (versionsNumber > 1)
981                                 for (int i = 1; i < versionsNumber; i++) {
982                                         FileBody body = file.getBodies().get(i);
983                                         assert body != null;
984                                         contents = new File(body.getStoredFilePath());
985                                         updateFileContents(user.getId(), copiedFile.getId(), body.getMimeType(), new FileInputStream(contents));
986                                 }
987                         List<FileTag> tags = file.getFileTags();
988                         for (FileTag tag : tags)
989                                 createTag(userId, copiedFile.getId(), tag.getTag());
990
991                 } catch (FileNotFoundException e) {
992                         throw new ObjectNotFoundException("File contents not found for file " + contents.getAbsolutePath());
993                 }
994
995         }
996
997         @Override
998         public void copyFolder(Long userId, Long folderId, String dest) throws ObjectNotFoundException, DuplicateNameException, InsufficientPermissionsException {
999                 if (userId == null)
1000                         throw new ObjectNotFoundException("No user specified");
1001                 if (folderId == null)
1002                         throw new ObjectNotFoundException("No folder specified");
1003                 if (StringUtils.isEmpty(dest))
1004                         throw new ObjectNotFoundException("No destination specified");
1005
1006                 Object destination = getResourceAtPath(userId, getParentPath(dest), true);
1007                 if (!(destination instanceof FolderDTO))
1008                         throw new ObjectNotFoundException("Destination folder not found");
1009                 FolderDTO parent = (FolderDTO) destination;
1010                 copyFolder(userId, folderId, parent.getId(), getLastElement(dest));
1011         }
1012
1013         @Override
1014         public void copyFolder(Long userId, Long folderId, Long destId, String destName) throws ObjectNotFoundException, DuplicateNameException, InsufficientPermissionsException {
1015                 if (userId == null)
1016                         throw new ObjectNotFoundException("No user specified");
1017                 if (folderId == null)
1018                         throw new ObjectNotFoundException("No folder specified");
1019                 if (destId == null)
1020                         throw new ObjectNotFoundException("No destination specified");
1021                 if (StringUtils.isEmpty(destName))
1022                         throw new ObjectNotFoundException("No destination folder name specified");
1023                 Folder folder = dao.getEntityById(Folder.class, folderId);
1024                 Folder destination = dao.getEntityById(Folder.class, destId);
1025                 User user = dao.getEntityById(User.class, userId);
1026                 if (!destination.hasWritePermission(user) || !folder.hasReadPermission(user))
1027                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
1028                 createFolder(user.getId(), destination.getId(), destName);
1029         }
1030
1031         @Override
1032         public void copyFolderStructureToPath(Long userId, Long ownerId, Long folderId, String dest) throws ObjectNotFoundException, DuplicateNameException, InsufficientPermissionsException, GSSIOException, QuotaExceededException {
1033                 if (userId == null)
1034                         throw new ObjectNotFoundException("No user specified");
1035                 if (ownerId == null)
1036                         throw new ObjectNotFoundException("No owner specified");
1037                 if (folderId == null)
1038                         throw new ObjectNotFoundException("No folder specified");
1039                 if (StringUtils.isEmpty(dest))
1040                         throw new ObjectNotFoundException("No destination specified");
1041
1042                 Object destination = getResourceAtPath(ownerId, getParentPath(dest), true);
1043                 if (!(destination instanceof FolderDTO))
1044                         throw new ObjectNotFoundException("Destination folder not found");
1045                 FolderDTO parent = (FolderDTO) destination;
1046                 copyFolderStructure(userId, folderId, parent.getId(), getLastElement(dest));
1047         }
1048
1049         @Override
1050         public void copyFolderStructure(Long userId, Long folderId, Long destId, String destName) throws ObjectNotFoundException, DuplicateNameException, InsufficientPermissionsException, GSSIOException, QuotaExceededException {
1051                 if (userId == null)
1052                         throw new ObjectNotFoundException("No user specified");
1053                 if (folderId == null)
1054                         throw new ObjectNotFoundException("No folder specified");
1055                 if (destId == null)
1056                         throw new ObjectNotFoundException("No destination specified");
1057                 if (StringUtils.isEmpty(destName))
1058                         throw new ObjectNotFoundException("No destination folder name specified");
1059
1060                 Folder folder = dao.getEntityById(Folder.class, folderId);
1061                 Folder destination = dao.getEntityById(Folder.class, destId);
1062                 final User user = dao.getEntityById(User.class, userId);
1063                 // XXX: quick fix need to copy only visible items to user (Source
1064                 // for bugs)
1065                 if (!folder.getOwner().getId().equals(userId) && !folder.hasReadPermission(user))
1066                         return;
1067                 if(folder.isDeleted())//do not copy trashed folder and contents
1068                         return;
1069                 if (!destination.hasWritePermission(user) || !folder.hasReadPermission(user))
1070                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
1071                 createFolder(user.getId(), destination.getId(), destName);
1072                 Folder createdFolder = dao.getFolder(destination.getId(), destName);
1073                 List<FileHeader> files = folder.getFiles();
1074                 if (files != null)
1075                         for (FileHeader file : files)
1076                                 if(!file.isDeleted())
1077                                         copyFile(userId, file.getId(), createdFolder.getId(), file.getName());
1078                 List<Folder> subFolders = folder.getSubfolders();
1079                 if (subFolders != null)
1080                         for (Folder sub : subFolders)
1081                                 if(!sub.getId().equals(createdFolder.getId()))
1082                                         copyFolderStructure(userId, sub.getId(), createdFolder.getId(), sub.getName());
1083
1084         }
1085
1086         /**
1087          * For a provided path, remove the last element and return the rest, that is
1088          * the path of the parent folder.
1089          *
1090          * @param path the specified path
1091          * @return the path of the parent folder
1092          * @throws ObjectNotFoundException if the provided string contains no path
1093          *             delimiters
1094          */
1095         private String getParentPath(String path) throws ObjectNotFoundException {
1096                 int lastDelimiter = path.lastIndexOf('/');
1097                 if (lastDelimiter == 0)
1098                         return "/";
1099                 if (lastDelimiter == -1)
1100                         // No path found.
1101                         throw new ObjectNotFoundException("There is no parent in the path: " + path);
1102                 else if (lastDelimiter < path.length() - 1)
1103                         // Return the part before the delimiter.
1104                         return path.substring(0, lastDelimiter);
1105                 else {
1106                         // Remove the trailing delimiter and then recurse.
1107                         String strippedTrail = path.substring(0, lastDelimiter);
1108                         return getParentPath(strippedTrail);
1109                 }
1110         }
1111
1112         /**
1113          * Get the last element in a path that denotes the file or folder name.
1114          *
1115          * @param path the provided path
1116          * @return the last element in the path
1117          */
1118         private String getLastElement(String path) {
1119                 int lastDelimiter = path.lastIndexOf('/');
1120                 if (lastDelimiter == -1)
1121                         // No path found.
1122                         return path;
1123                 else if (lastDelimiter < path.length() - 1)
1124                         // Return the part after the delimiter.
1125                         return path.substring(lastDelimiter + 1);
1126                 else {
1127                         // Remove the trailing delimiter and then recurse.
1128                         String strippedTrail = path.substring(0, lastDelimiter);
1129                         return getLastElement(strippedTrail);
1130                 }
1131         }
1132
1133         @Override
1134         public void moveFileToTrash(Long userId, Long fileId) throws ObjectNotFoundException, InsufficientPermissionsException {
1135                 if (userId == null)
1136                         throw new ObjectNotFoundException("No user specified");
1137                 if (fileId == null)
1138                         throw new ObjectNotFoundException("No file specified");
1139
1140                 // Do the actual work.
1141                 FileHeader file = dao.getEntityById(FileHeader.class, fileId);
1142                 Folder parent = file.getFolder();
1143                 if (parent == null)
1144                         throw new ObjectNotFoundException("The specified file has no parent folder");
1145                 User user = dao.getEntityById(User.class, userId);
1146                 if (!file.hasDeletePermission(user))
1147                         throw new InsufficientPermissionsException("User " + user.getId() + " cannot delete file " + file.getName() + "(" + file.getId() + ")");
1148
1149                 file.setDeleted(true);
1150                 dao.update(file);
1151                 touchParentFolders(parent, user, new Date());
1152         }
1153
1154         @Override
1155         public void moveFileToPath(Long userId, Long ownerId, Long fileId, String dest) throws ObjectNotFoundException, InsufficientPermissionsException, QuotaExceededException {
1156                 if (userId == null)
1157                         throw new ObjectNotFoundException("No user specified");
1158                 if (ownerId == null)
1159                         throw new ObjectNotFoundException("No owner specified");
1160                 if (fileId == null)
1161                         throw new ObjectNotFoundException("No file specified");
1162                 if (StringUtils.isEmpty(dest))
1163                         throw new ObjectNotFoundException("No destination specified");
1164
1165                 Object destination = getResourceAtPath(ownerId, getParentPath(dest), true);
1166                 if (!(destination instanceof FolderDTO))
1167                         throw new ObjectNotFoundException("Destination parent folder not found");
1168                 FolderDTO parent = (FolderDTO) destination;
1169                 moveFile(userId, fileId, parent.getId(), getLastElement(dest));
1170         }
1171
1172         @Override
1173         public void moveFile(Long userId, Long fileId, Long destId, String destName) throws InsufficientPermissionsException, ObjectNotFoundException, QuotaExceededException {
1174                 if (userId == null)
1175                         throw new ObjectNotFoundException("No user specified");
1176                 if (fileId == null)
1177                         throw new ObjectNotFoundException("No file specified");
1178                 if (destId == null)
1179                         throw new ObjectNotFoundException("No destination specified");
1180                 if (StringUtils.isEmpty(destName))
1181                         throw new ObjectNotFoundException("No destination file name specified");
1182
1183                 FileHeader file = dao.getEntityById(FileHeader.class, fileId);
1184                 Folder source = file.getFolder();
1185                 Folder destination = dao.getEntityById(Folder.class, destId);
1186
1187                 User owner = dao.getEntityById(User.class, userId);
1188                 if (!file.hasDeletePermission(owner) || !destination.hasWritePermission(owner))
1189                         throw new InsufficientPermissionsException("User " + owner.getId() + " cannot move file " + file.getName() + "(" + file.getId() + ")");
1190
1191                 // if the destination folder belongs to another user:
1192                 if (!file.getOwner().equals(destination.getOwner())) {
1193                         // (a) check if the destination quota allows the move
1194                         if(getQuotaLeft(destination.getOwner().getId()) < file.getTotalSize())
1195                                 throw new QuotaExceededException("Not enough free space available");
1196                         User newOwner = destination.getOwner();
1197                         // (b) if quota OK, change the owner of the file
1198                         file.setOwner(newOwner);
1199                         // if the file has no permission for the new owner, add it
1200                         Permission ownerPermission = null;
1201                         for (final Permission p : file.getPermissions())
1202                                 if (p.getUser() != null)
1203                                         if (p.getUser().equals(newOwner)) {
1204                                                 ownerPermission = p;
1205                                                 break;
1206                                         }
1207                         if (ownerPermission == null) {
1208                                 ownerPermission = new Permission();
1209                                 ownerPermission.setUser(newOwner);
1210                                 file.addPermission(ownerPermission);
1211                         }
1212                         ownerPermission.setRead(true);
1213                         ownerPermission.setWrite(true);
1214                         ownerPermission.setModifyACL(true);
1215                 }
1216                 // move the file to the destination folder
1217                 file.setFolder(destination);
1218                 touchParentFolders(source, owner, new Date());
1219                 touchParentFolders(destination, owner, new Date());
1220         }
1221
1222         @Override
1223         public void moveFolderToPath(Long userId, Long ownerId, Long folderId, String dest) throws ObjectNotFoundException, InsufficientPermissionsException, QuotaExceededException {
1224                 if (userId == null)
1225                         throw new ObjectNotFoundException("No user specified");
1226                 if (ownerId == null)
1227                         throw new ObjectNotFoundException("No owner specified");
1228                 if (folderId == null)
1229                         throw new ObjectNotFoundException("No folder specified");
1230                 if (StringUtils.isEmpty(dest))
1231                         throw new ObjectNotFoundException("No destination specified");
1232
1233                 Object destination = getResourceAtPath(ownerId, getParentPath(dest), true);
1234                 if (!(destination instanceof FolderDTO))
1235                         throw new ObjectNotFoundException("Destination parent folder not found");
1236                 FolderDTO parent = (FolderDTO) destination;
1237                 moveFolder(userId, folderId, parent.getId(), getLastElement(dest));
1238         }
1239
1240         @Override
1241         public void moveFolder(Long userId, Long folderId, Long destId, String destName)
1242                         throws ObjectNotFoundException, InsufficientPermissionsException,
1243                         QuotaExceededException {
1244                 Folder source = dao.getEntityById(Folder.class, folderId);
1245                 Folder destination = dao.getEntityById(Folder.class, destId);
1246                 User user = dao.getEntityById(User.class, userId);
1247                 User sourceOwner = source.getOwner();
1248                 User destinationOwner = destination.getOwner();
1249                 // Do not move trashed folders and contents.
1250                 if (source.isDeleted())
1251                         return;
1252                 // Check permissions.
1253                 if (!destination.hasWritePermission(user)
1254                                 || !source.hasReadPermission(user)
1255                                 || !source.hasWritePermission(user))
1256                         throw new InsufficientPermissionsException("You don't have the " +
1257                                         "necessary permissions");
1258                 // Use the same timestamp for all subsequent modifications to make
1259                 // changes appear simultaneous.
1260                 Date now = new Date();
1261                 // If source and destination are not in the same user's namespace,
1262                 // change owners and check quota.
1263                 if (!sourceOwner.equals(destinationOwner)) {
1264                         changeOwner(source, destinationOwner, user, now);
1265                         if (getQuotaLeft(destinationOwner.getId()) < 0)
1266                                 throw new QuotaExceededException("Not enough free space " +
1267                                                 "available in destination folder");
1268                 }
1269                 // Perform the move.
1270                 Folder oldParent = source.getParent();
1271                 oldParent.removeSubfolder(source);
1272                 destination.addSubfolder(source);
1273                 // Mark the former parent and destination trees upwards as modified.
1274                 touchParentFolders(oldParent, user, now);
1275                 touchParentFolders(source, user, now);
1276         }
1277
1278         /**
1279          * Recursively change the owner of the specified folder and all of its
1280          * contents to the specified owner. Also mark them all as modified with the
1281          * specified modifier and modificationDate.
1282          */
1283         private void changeOwner(Folder folder, User owner, User modifier, Date modificationDate) {
1284                 for (FileHeader file: folder.getFiles()) {
1285                         file.setOwner(owner);
1286                         file.getAuditInfo().setModificationDate(modificationDate);
1287                         file.getAuditInfo().setModifiedBy(modifier);
1288                 }
1289                 for (Folder sub: folder.getSubfolders())
1290                         changeOwner(sub, owner, modifier, modificationDate);
1291                 folder.setOwner(owner);
1292                 folder.getAuditInfo().setModificationDate(modificationDate);
1293                 folder.getAuditInfo().setModifiedBy(modifier);
1294         }
1295
1296         @Override
1297         public List<FileHeaderDTO> getDeletedFiles(Long userId) throws ObjectNotFoundException {
1298                 // Validate.
1299                 if (userId == null)
1300                         throw new ObjectNotFoundException("No user specified");
1301
1302                 // Do the actual work.
1303                 final List<FileHeaderDTO> result = new ArrayList<FileHeaderDTO>();
1304                 final List<FileHeader> files = dao.getDeletedFiles(userId);
1305                 for (final FileHeader f : files)
1306                         result.add(f.getDTO());
1307                 return result;
1308         }
1309
1310         @Override
1311         public void removeFileFromTrash(Long userId, Long fileId)
1312                         throws ObjectNotFoundException, InsufficientPermissionsException {
1313                 if (userId == null)
1314                         throw new ObjectNotFoundException("No user specified");
1315                 if (fileId == null)
1316                         throw new ObjectNotFoundException("No file specified");
1317
1318                 // Do the actual work.
1319                 FileHeader file = dao.getEntityById(FileHeader.class, fileId);
1320                 Folder parent = file.getFolder();
1321                 if (parent == null)
1322                         throw new ObjectNotFoundException("The specified file has no parent folder");
1323                 User user = dao.getEntityById(User.class, userId);
1324                 if (!file.hasDeletePermission(user))
1325                         throw new InsufficientPermissionsException("User " + user.getUsername() +
1326                                                 " cannot restore file " + file.getName());
1327
1328                 file.setDeleted(false);
1329                 dao.update(file);
1330                 touchParentFolders(parent, user, new Date());
1331         }
1332
1333         @Override
1334         public void moveFolderToTrash(Long userId, Long folderId) throws ObjectNotFoundException, InsufficientPermissionsException {
1335                 if (userId == null)
1336                         throw new ObjectNotFoundException("No user specified");
1337                 if (folderId == null)
1338                         throw new ObjectNotFoundException("No folder specified");
1339                 Folder folder = dao.getEntityById(Folder.class, folderId);
1340                 User user = dao.getEntityById(User.class, userId);
1341                 if (!folder.hasDeletePermission(user))
1342                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
1343                 folder.setDeleted(true);
1344                 dao.update(folder);
1345                 touchParentFolders(folder, user, new Date());
1346                 for (FileHeader file : folder.getFiles())
1347                         moveFileToTrash(userId, file.getId());
1348                 for (Folder subFolder : folder.getSubfolders())
1349                         moveFolderToTrash(userId, subFolder.getId());
1350
1351         }
1352
1353         @Override
1354         public void removeFolderFromTrash(Long userId, Long folderId)
1355                         throws ObjectNotFoundException, InsufficientPermissionsException {
1356                 if (userId == null)
1357                         throw new ObjectNotFoundException("No user specified");
1358                 if (folderId == null)
1359                         throw new ObjectNotFoundException("No folder specified");
1360                 Folder folder = dao.getEntityById(Folder.class, folderId);
1361                 User user = dao.getEntityById(User.class, userId);
1362                 if (!folder.hasDeletePermission(user))
1363                         throw new InsufficientPermissionsException("User " + user.getUsername() +
1364                                                 " cannot restore folder " + folder.getName());
1365                 folder.setDeleted(false);
1366                 for (FileHeader file : folder.getFiles())
1367                         removeFileFromTrash(userId, file.getId());
1368                 for (Folder subFolder : folder.getSubfolders())
1369                         removeFolderFromTrash(userId, subFolder.getId());
1370                 dao.update(folder);
1371                 touchParentFolders(folder, user, new Date());
1372         }
1373
1374         @Override
1375         public List<FolderDTO> getDeletedRootFolders(Long userId) throws ObjectNotFoundException {
1376                 List<Folder> folders = dao.getDeletedRootFolders(userId);
1377                 List<FolderDTO> result = new ArrayList<FolderDTO>();
1378                 for (Folder folder : folders)
1379                         result.add(folder.getDTO());
1380                 return result;
1381         }
1382
1383         @Override
1384         public void emptyTrash(Long userId) throws ObjectNotFoundException, InsufficientPermissionsException {
1385                 List<FolderDTO> deletedRootFolders = getDeletedRootFolders(userId);
1386                 for (FolderDTO fdto : deletedRootFolders)
1387                         deleteFolder(userId, fdto.getId());
1388                 List<FileHeaderDTO> deletedFiles = getDeletedFiles(userId);
1389                 for (FileHeaderDTO filedto : deletedFiles)
1390                         deleteFile(userId, filedto.getId());
1391         }
1392
1393         @Override
1394         public void restoreTrash(Long userId) throws ObjectNotFoundException, InsufficientPermissionsException {
1395                 List<FolderDTO> deletedRootFolders = getDeletedRootFolders(userId);
1396                 for (FolderDTO fdto : deletedRootFolders)
1397                         removeFolderFromTrash(userId, fdto.getId());
1398                 List<FileHeaderDTO> deletedFiles = getDeletedFiles(userId);
1399                 for (FileHeaderDTO filedto : deletedFiles)
1400                         removeFileFromTrash(userId, filedto.getId());
1401         }
1402
1403         @Override
1404         public User createUser(String username, String name, String mail,
1405                                 String idp, String idpid) throws ObjectNotFoundException {
1406                 if (username == null)
1407                         throw new ObjectNotFoundException("No username specified");
1408                 if (name == null)
1409                         throw new ObjectNotFoundException("No name specified");
1410
1411                 User user = new User();
1412                 user.setUsername(username);
1413                 user.setName(name);
1414                 user.setEmail(mail);
1415                 user.setIdentityProvider(idp);
1416                 user.setIdentityProviderId(idpid);
1417                 Date now = new Date();
1418                 AuditInfo auditInfo = new AuditInfo();
1419                 auditInfo.setCreationDate(now);
1420                 auditInfo.setModificationDate(now);
1421                 user.setAuditInfo(auditInfo);
1422                 user.setActive(true);
1423                 user.generateAuthToken();
1424                 user.generateWebDAVPassword();
1425                 user.setUserClass(getDefaultUserClass());
1426                 dao.create(user);
1427                 // Make sure we get an ID in the user object.
1428                 dao.flush();
1429                 // Create the root folder for the user.
1430                 createFolder(user.getName(), null, user);
1431                 return user;
1432         }
1433
1434         /**
1435          * Get the default user class, which is the one with the lowest quota.
1436          */
1437         private UserClass getDefaultUserClass() {
1438                 return getUserClasses().get(0);
1439         }
1440
1441         @Override
1442         public List<UserClass> getUserClasses() {
1443                 List<UserClass> classes = dao.getUserClasses();
1444                 // Create a default user class for first-time use. Afterwards, the
1445                 // admin should modify or add to the userclass table.
1446                 if (classes.size() == 0) {
1447                         UserClass defaultClass = new UserClass();
1448                         defaultClass.setName("default");
1449                         Long defaultQuota = getConfiguration().getLong("quota", new Long(52428800L));
1450                         defaultClass.setQuota(defaultQuota);
1451                         dao.create(defaultClass);
1452                         classes.add(defaultClass);
1453                 }
1454                 return classes;
1455         }
1456
1457         @Override
1458         public User findUserByEmail(String email) {
1459                 return dao.findUserByEmail(email);
1460         }
1461
1462         @Override
1463         public void updateUser(User user) {
1464                 dao.update(user);
1465         }
1466
1467         @Override
1468         public User findUser(String username) {
1469                 if (username == null)
1470                         return null;
1471                 return dao.findUser(username);
1472         }
1473
1474         @Override
1475         public User updateUserToken(Long userId) throws ObjectNotFoundException {
1476                 if (userId == null)
1477                         throw new ObjectNotFoundException("No user specified");
1478                 User user = dao.getEntityById(User.class, userId);
1479                 user.generateAuthToken();
1480                 return user;
1481         }
1482
1483         @Override
1484         public Set<PermissionDTO> getFolderPermissions(Long userId, Long folderId) throws ObjectNotFoundException, InsufficientPermissionsException {
1485                 if (userId == null)
1486                         throw new ObjectNotFoundException("No user specified");
1487                 if (folderId == null)
1488                         throw new ObjectNotFoundException("No folder specified");
1489                 User user = dao.getEntityById(User.class, userId);
1490                 Folder folder = dao.getEntityById(Folder.class, folderId);
1491                 if(!folder.hasReadPermission(user))
1492                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
1493                 Set<Permission> perms = folder.getPermissions();
1494                 Set<PermissionDTO> result = new LinkedHashSet<PermissionDTO>();
1495                 for (Permission perm : perms)
1496                         if (perm.getUser() != null && perm.getUser().getId().equals(folder.getOwner().getId()))
1497                                 result.add(perm.getDTO());
1498                 for (Permission perm : perms)
1499                         if (perm.getUser() != null && perm.getUser().getId().equals(folder.getOwner().getId())) {
1500                         } else
1501                                 result.add(perm.getDTO());
1502                 return result;
1503
1504         }
1505
1506         /**
1507          * Set the provided permissions as the new permissions of the specified
1508          * folder.
1509          *
1510          * @param user
1511          * @param folder
1512          * @param permissions
1513          * @throws ObjectNotFoundException
1514          * @throws InsufficientPermissionsException
1515          */
1516         private void setFolderPermissions(User user, Folder folder, Set<PermissionDTO> permissions) throws ObjectNotFoundException, InsufficientPermissionsException {
1517                 if (permissions != null && !permissions.isEmpty()) {
1518                         User owner = folder.getOwner();
1519                         PermissionDTO ownerPerm = null;
1520                         for (PermissionDTO dto : permissions)
1521                                 if (dto.getUser() != null && dto.getUser().getId().equals(owner.getId())) {
1522                                         ownerPerm = dto;
1523                                         break;
1524                                 }
1525                         if (ownerPerm == null || !ownerPerm.hasRead() || !ownerPerm.hasWrite() || !ownerPerm.hasModifyACL())
1526                                 throw new InsufficientPermissionsException("Can't remove permissions from owner");
1527                         // Delete previous entries
1528                         for (Permission perm: folder.getPermissions())
1529                                 dao.delete(perm);
1530                         folder.getPermissions().clear();
1531                         for (PermissionDTO dto : permissions) {
1532                                 // Skip 'empty' permission entries.
1533                                 if (!dto.getRead() && !dto.getWrite() && !dto.getModifyACL()) continue;
1534                                 folder.addPermission(getPermission(dto));
1535                         }
1536                         dao.update(folder);
1537                         for (FileHeader file : folder.getFiles()) {
1538                                 setFilePermissions(file, permissions);
1539                                 Date now = new Date();
1540                                 file.getAuditInfo().setModificationDate(now);
1541                                 file.getAuditInfo().setModifiedBy(user);
1542                         }
1543                         for (Folder sub : folder.getSubfolders())
1544                                 setFolderPermissions(user, sub, permissions);
1545                 }
1546         }
1547
1548         private Permission getPermission(PermissionDTO dto) throws ObjectNotFoundException {
1549                 Permission res = new Permission();
1550                 if (dto.getGroup() != null)
1551                         res.setGroup(dao.getEntityById(Group.class, dto.getGroup().getId()));
1552                 else if (dto.getUser() != null)
1553                         if (dto.getUser().getId() == null)
1554                                 res.setUser(dao.getUser(dto.getUser().getUsername()));
1555                         else
1556                                 res.setUser(dao.getEntityById(User.class, dto.getUser().getId()));
1557                 res.setRead(dto.hasRead());
1558                 res.setWrite(dto.hasWrite());
1559                 res.setModifyACL(dto.hasModifyACL());
1560                 return res;
1561         }
1562
1563         /* (non-Javadoc)
1564          * @see gr.ebs.gss.server.ejb.ExternalAPI#getUsersByUserNameLike(java.lang.String)
1565          */
1566         @Override
1567         public List<UserDTO> getUsersByUserNameLike(String username) {
1568                 List<User> users = dao.getUsersByUserNameLike(username);
1569                 List<UserDTO> result = new ArrayList<UserDTO>();
1570                 for (User u : users)
1571                         result.add(u.getDTO());
1572                 return result;
1573
1574         }
1575
1576         @Override
1577         public void addUserToGroup(Long userId, Long groupId, Long userToAddId) throws ObjectNotFoundException, DuplicateNameException, InsufficientPermissionsException {
1578                 if (userId == null)
1579                         throw new ObjectNotFoundException("No user specified");
1580                 if (groupId == null)
1581                         throw new ObjectNotFoundException("No group specified");
1582                 if (userToAddId == null)
1583                         throw new ObjectNotFoundException("No user to add specified");
1584                 User user = dao.getEntityById(User.class, userId);
1585                 Group group = dao.getEntityById(Group.class, groupId);
1586                 if (!group.getOwner().equals(user))
1587                         throw new InsufficientPermissionsException();
1588                 User userToAdd = dao.getEntityById(User.class, userToAddId);
1589                 if (group.contains(userToAdd))
1590                         throw new DuplicateNameException("User already exists in group");
1591                 group.getMembers().add(userToAdd);
1592                 dao.update(group);
1593
1594         }
1595
1596         @Override
1597         public void invalidateUserToken(Long userId) throws ObjectNotFoundException {
1598                 if (userId == null)
1599                         throw new ObjectNotFoundException("No user specified");
1600                 User user = dao.getEntityById(User.class, userId);
1601                 user.invalidateAuthToken();
1602                 return;
1603         }
1604
1605         @Override
1606         public List<FolderDTO> getSharedRootFolders(Long userId) throws ObjectNotFoundException {
1607                 if (userId == null)
1608                         throw new ObjectNotFoundException("No user specified");
1609                 List<Folder> folders = dao.getSharedRootFolders(userId);
1610                 List<FolderDTO> result = new ArrayList<FolderDTO>();
1611                 for (Folder f : folders) {
1612                         FolderDTO dto = f.getDTO();
1613                         dto.setSubfolders(getSharedSubfolders(userId, f.getId()));
1614                         result.add(dto);
1615                 }
1616                 return result;
1617         }
1618
1619         @Override
1620         public void removeMemberFromGroup(Long userId, Long groupId, Long memberId) throws ObjectNotFoundException, InsufficientPermissionsException {
1621                 if (userId == null)
1622                         throw new ObjectNotFoundException("No user specified");
1623                 if (groupId == null)
1624                         throw new ObjectNotFoundException("No group specified");
1625                 if (memberId == null)
1626                         throw new ObjectNotFoundException("No member specified");
1627                 User owner = dao.getEntityById(User.class, userId);
1628                 Group group = dao.getEntityById(Group.class, groupId);
1629                 User member = dao.getEntityById(User.class, memberId);
1630                 if (!group.getOwner().equals(owner))
1631                         throw new InsufficientPermissionsException("User is not the owner of the group");
1632                 group.removeMemberFromGroup(member);
1633                 dao.update(group);
1634
1635         }
1636
1637         @Override
1638         public List<UserDTO> getUsersSharingFoldersForUser(Long userId) throws ObjectNotFoundException {
1639                 List<User> users = dao.getUsersSharingFoldersForUser(userId);
1640                 List<User> usersFiles = dao.getUsersSharingFilesForUser(userId);
1641                 List<UserDTO> res = new ArrayList<UserDTO>();
1642                 for (User u : users)
1643                         res.add(u.getDTO());
1644                 for(User fu : usersFiles)
1645                         if(!users.contains(fu))
1646                                 res.add(fu.getDTO());
1647                 return res;
1648         }
1649
1650         @Override
1651         public Set<PermissionDTO> getFilePermissions(Long userId, Long fileId) throws ObjectNotFoundException, InsufficientPermissionsException {
1652                 if (userId == null)
1653                         throw new ObjectNotFoundException("No user specified");
1654                 if (fileId == null)
1655                         throw new ObjectNotFoundException("No folder specified");
1656                 User user = dao.getEntityById(User.class, userId);
1657                 FileHeader folder = dao.getEntityById(FileHeader.class, fileId);
1658                 if(!folder.hasReadPermission(user))
1659                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
1660                 Set<Permission> perms = folder.getPermissions();
1661                 Set<PermissionDTO> result = new LinkedHashSet<PermissionDTO>();
1662                 for (Permission perm : perms)
1663                         if (perm.getUser() != null && perm.getUser().getId().equals(folder.getOwner().getId()))
1664                                 result.add(perm.getDTO());
1665                 for (Permission perm : perms)
1666                         if (perm.getUser() != null && perm.getUser().getId().equals(folder.getOwner().getId())) {
1667                         } else
1668                                 result.add(perm.getDTO());
1669                 return result;
1670         }
1671
1672         /**
1673          * Set the provided permissions as the new permissions of the specified
1674          * file. This method sets the modification date/user attributes to the
1675          * current values as a side effect.
1676          *
1677          * @param file
1678          * @param permissions
1679          * @throws ObjectNotFoundException
1680          * @throws InsufficientPermissionsException
1681          */
1682         private void setFilePermissions(FileHeader file,
1683                                 Set<PermissionDTO> permissions)
1684                         throws ObjectNotFoundException, InsufficientPermissionsException {
1685                 if (permissions != null && !permissions.isEmpty()) {
1686                         PermissionDTO ownerPerm = null;
1687                         for (PermissionDTO dto : permissions)
1688                                 if (dto.getUser() != null && dto.getUser().getId().equals(file.getOwner().getId())) {
1689                                         ownerPerm = dto;
1690                                         break;
1691                                 }
1692                         if (ownerPerm == null || !ownerPerm.hasRead() || !ownerPerm.hasWrite() || !ownerPerm.hasModifyACL())
1693                                 throw new InsufficientPermissionsException("Can't remove permissions from owner");
1694                         // Delete previous entries.
1695                         for (Permission perm: file.getPermissions())
1696                                 dao.delete(perm);
1697                         file.getPermissions().clear();
1698                         for (PermissionDTO dto : permissions) {
1699                                 // Skip 'empty' permission entries.
1700                                 if (!dto.getRead() && !dto.getWrite() && !dto.getModifyACL()) continue;
1701                                 file.addPermission(getPermission(dto));
1702                         }
1703                         dao.flush();
1704                 }
1705         }
1706
1707         @Override
1708         public List<FileHeaderDTO> getSharedFilesNotInSharedFolders(Long userId) throws ObjectNotFoundException {
1709                 if (userId == null)
1710                         throw new ObjectNotFoundException("No user specified");
1711                 List<FileHeader> files = dao.getSharedFilesNotInSharedFolders(userId);
1712                 List<FileHeaderDTO> result = new ArrayList<FileHeaderDTO>();
1713                 for (FileHeader f : files)
1714                         result.add(f.getDTO());
1715                 return result;
1716         }
1717
1718         @Override
1719         public List<FileHeaderDTO> getSharedFiles(Long userId) throws ObjectNotFoundException {
1720                 if (userId == null)
1721                         throw new ObjectNotFoundException("No user specified");
1722                 List<FileHeader> files = dao.getSharedFiles(userId);
1723                 List<FileHeaderDTO> result = new ArrayList<FileHeaderDTO>();
1724                 for (FileHeader f : files)
1725                         result.add(f.getDTO());
1726                 return result;
1727         }
1728
1729         @Override
1730         public List<FolderDTO> getSharedFolders(Long userId) throws ObjectNotFoundException {
1731                 if (userId == null)
1732                         throw new ObjectNotFoundException("No user specified");
1733                 List<Folder> folders = dao.getSharedFolders(userId);
1734                 List<FolderDTO> result = new ArrayList<FolderDTO>();
1735                 for (Folder f : folders)
1736                         result.add(f.getDTO());
1737                 return result;
1738         }
1739
1740         @Override
1741         public List<FileHeaderDTO> getSharedFiles(Long ownerId, Long callingUserId) throws ObjectNotFoundException {
1742                 if (ownerId == null)
1743                         throw new ObjectNotFoundException("No owner specified");
1744                 if (callingUserId == null)
1745                         throw new ObjectNotFoundException("No calling user specified");
1746                 List<FileHeader> folders = dao.getSharedFiles(ownerId, callingUserId);
1747                 List<FileHeaderDTO> result = new ArrayList<FileHeaderDTO>();
1748                 for (FileHeader f : folders)
1749                         result.add(f.getDTO());
1750                 return result;
1751         }
1752
1753         @Override
1754         public List<FolderDTO> getSharedRootFolders(Long ownerId, Long callingUserId) throws ObjectNotFoundException {
1755                 if (ownerId == null)
1756                         throw new ObjectNotFoundException("No owner specified");
1757                 if (callingUserId == null)
1758                         throw new ObjectNotFoundException("No calling user specified");
1759                 List<Folder> folders = dao.getSharedRootFolders(ownerId, callingUserId);
1760                 List<FolderDTO> result = new ArrayList<FolderDTO>();
1761                 for (Folder f : folders) {
1762                         FolderDTO dto = f.getDTO();
1763                         dto.setSubfolders(getSharedSubfolders(ownerId, callingUserId, f.getId()));
1764                         result.add(dto);
1765                 }
1766                 return result;
1767
1768         }
1769
1770         @Override
1771         public List<FolderDTO> getSharedSubfolders(Long userId, Long folderId) throws ObjectNotFoundException {
1772                 if (userId == null)
1773                         throw new ObjectNotFoundException("No user specified");
1774                 if (folderId == null)
1775                         throw new ObjectNotFoundException("No folder specified");
1776                 User user = dao.getEntityById(User.class, userId);
1777                 Folder folder = dao.getEntityById(Folder.class, folderId);
1778                 List<FolderDTO> result = new ArrayList<FolderDTO>();
1779                 if (folder.isShared(user) || folder.isReadForAll())
1780                         for (Folder f : folder.getSubfolders())
1781                                 if ((f.isShared(user) || f.isReadForAll()) && !f.isDeleted())
1782                                         result.add(f.getDTO());
1783                 return result;
1784         }
1785
1786         @Override
1787         public List<FolderDTO> getSharedSubfolders(Long userId, Long callingUserId, Long folderId) throws ObjectNotFoundException {
1788                 if (userId == null)
1789                         throw new ObjectNotFoundException("No user specified");
1790                 if (callingUserId == null)
1791                         throw new ObjectNotFoundException("No user specified");
1792                 if (folderId == null)
1793                         throw new ObjectNotFoundException("No folder specified");
1794                 User user = dao.getEntityById(User.class, callingUserId);
1795                 Folder folder = dao.getEntityById(Folder.class, folderId);
1796                 List<FolderDTO> result = new ArrayList<FolderDTO>();
1797                 if (folder.isSharedForOtherUser(user))
1798                         for (Folder f : folder.getSubfolders())
1799                                 if (f.isSharedForOtherUser(user) && !f.isDeleted()){
1800                                         FolderDTO dto = f.getDTO();
1801                                         dto.setSubfolders(getSharedSubfolders(userId, callingUserId, dto.getId()));
1802                                         result.add(dto);
1803                                 }
1804                 return result;
1805
1806         }
1807
1808         @Override
1809         public List<FileHeaderDTO> searchFiles(Long userId, String query) throws ObjectNotFoundException {
1810                 if (userId == null)
1811                         throw new ObjectNotFoundException("No user specified");
1812                 User user = getUser(userId);
1813                 if (query == null)
1814                         throw new ObjectNotFoundException("No query specified");
1815                 List<FileHeader> files = search(user.getId(), query);
1816                 List<FileHeaderDTO> res = new ArrayList<FileHeaderDTO>();
1817                 for(FileHeader f : files)
1818                         res.add(f.getDTO());
1819                 return res;
1820         }
1821
1822         /**
1823          * Performs the actuals search on the solr server and returns the results
1824          *
1825          * We have to use the dismax query type (instead of the
1826          * standard) because it allows for search time field boosting. This is because we can't use indexing
1827          * time field boosting due to the patched rich indexing API that does not allow it
1828          *
1829          * @param userId
1830          * @param query
1831          * @return a List of FileHeader objects
1832          */
1833         private List<FileHeader> search(Long userId, String query) {
1834                 try {
1835                         HttpClient httpClient = new HttpClient();
1836
1837                         GetMethod method = new GetMethod(getConfiguration().getString("solrSelectUrl"));
1838                         NameValuePair[] params = {new NameValuePair("qt", "dismax"),
1839                                                                                 new NameValuePair("q", query),
1840                                                                                 new NameValuePair("sort", "score desc"),
1841                                                                                 new NameValuePair("indent", "on")};
1842                         method.setQueryString(params);
1843                         int retryCount = 0;
1844                         int statusCode = 0;
1845                         String response = null;
1846                         do {
1847                                 statusCode = httpClient.executeMethod(method);
1848                                 logger.debug("HTTP status: " + statusCode);
1849                                 response = method.getResponseBodyAsString();
1850                                 logger.debug(response);
1851                                 retryCount++;
1852                                 if (statusCode != 200 && retryCount < 3)
1853                                         try {
1854                                                 Thread.sleep(3000); //Give Solr a little time to be available
1855                                         } catch (InterruptedException e) {
1856                                         }
1857                         } while (statusCode != 200 && retryCount < 3);
1858                         if (statusCode != 200)
1859                                 throw new EJBException("Search query return error:\n" + response);
1860
1861                         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
1862                         DocumentBuilder db = dbf.newDocumentBuilder();
1863                         Document doc = db.parse(method.getResponseBodyAsStream());
1864                         method.releaseConnection();
1865
1866                         Node root = doc.getElementsByTagName("response").item(0);
1867                         Node lst = root.getFirstChild().getNextSibling();
1868                         Node status = lst.getFirstChild().getNextSibling();
1869                         if (status.getAttributes().getNamedItem("name").getNodeValue().equals("status") &&
1870                                 status.getTextContent().equals("0")) {
1871                                 List<FileHeader> fileResult = new ArrayList<FileHeader>();
1872                                 Node result = lst.getNextSibling().getNextSibling();
1873                                 NodeList docs = result.getChildNodes();
1874                                 User user = getUser(userId);
1875                                 for (int i=1; i<docs.getLength(); i=i+2) {
1876                                         Node d = docs.item(i);
1877                                         NodeList docData = d.getChildNodes();
1878                                         for (int j=1; j<docData.getLength(); j=j+2) {
1879                                                 Node dd = docData.item(j);
1880                                                 if (dd.getAttributes().item(0).getNodeName().equals("name") &&
1881                                                         dd.getAttributes().item(0).getNodeValue().equals("id")) {
1882                                                         Long fileId = Long.valueOf(dd.getTextContent());
1883                                                         try {
1884                                                                 FileHeader file = dao.getEntityById(FileHeader.class, fileId);
1885                                                                 if (file.hasReadPermission(user)) {
1886                                                                         fileResult.add(file);
1887                                                                         logger.debug("File added " + fileId);
1888                                                                 }
1889                                                         } catch (ObjectNotFoundException e) {
1890                                                                 logger.warn("Search result not found", e);
1891                                                         }
1892                                                 }
1893                                         }
1894                                 }
1895                                 return fileResult;
1896                         }
1897                         throw new EJBException();
1898                 } catch (HttpException e) {
1899                         throw new EJBException(e);
1900                 } catch (IOException e) {
1901                         throw new EJBException(e);
1902                 } catch (SAXException e) {
1903                         throw new EJBException(e);
1904                 } catch (ParserConfigurationException e) {
1905                         throw new EJBException(e);
1906                 } catch (ObjectNotFoundException e) {
1907                         throw new EJBException(e);
1908                 }
1909         }
1910
1911         @Override
1912         public void copyFiles(Long userId, List<Long> fileIds, Long destId) throws ObjectNotFoundException, DuplicateNameException, GSSIOException, InsufficientPermissionsException, QuotaExceededException {
1913                 for(Long l : fileIds){
1914                         FileHeader file = dao.getEntityById(FileHeader.class, l);
1915                         copyFile(userId, l, destId, file.getName());
1916                 }
1917
1918
1919         }
1920
1921         @Override
1922         public void moveFiles(Long userId, List<Long> fileIds, Long destId) throws InsufficientPermissionsException, ObjectNotFoundException, QuotaExceededException {
1923                 for(Long l : fileIds){
1924                         FileHeader file = dao.getEntityById(FileHeader.class, l);
1925                         moveFile(userId, l, destId, file.getName());
1926                 }
1927
1928         }
1929
1930         @Override
1931         public void deleteFiles(Long userId, List<Long> fileIds) throws ObjectNotFoundException, InsufficientPermissionsException {
1932                 if (userId == null)
1933                         throw new ObjectNotFoundException("No user specified");
1934                 final User user = dao.getEntityById(User.class, userId);
1935                 List<String> filesToRemove = new ArrayList<String>();
1936                 //first delete database objects
1937                 for(Long fileId : fileIds){
1938                         if (fileId == null)
1939                                 throw new ObjectNotFoundException("No file specified");
1940                         final FileHeader file = dao.getEntityById(FileHeader.class, fileId);
1941                         final Folder parent = file.getFolder();
1942                         if (parent == null)
1943                                 throw new ObjectNotFoundException("The specified file has no parent folder");
1944                         if (!file.hasDeletePermission(user))
1945                                 throw new InsufficientPermissionsException("User " + user.getId() + " cannot delete file " + file.getName() + "(" + file.getId() + ")");
1946
1947                         parent.removeFile(file);
1948                         for (final FileBody body : file.getBodies())
1949                                 filesToRemove.add(body.getStoredFilePath());
1950                         dao.delete(file);
1951                         touchParentFolders(parent, user, new Date());
1952                 }
1953                 //then remove physical files if everything is ok
1954                 for(String physicalFileName : filesToRemove)
1955                         deleteActualFile(physicalFileName);
1956                 //then unindex deleted files
1957                 for(Long fileId : fileIds)
1958                         indexFile(fileId, true);
1959
1960         }
1961
1962         @Override
1963         public void moveFilesToTrash(Long userId, List<Long> fileIds) throws ObjectNotFoundException, InsufficientPermissionsException {
1964                 for(Long l : fileIds)
1965                         moveFileToTrash(userId, l);
1966
1967         }
1968
1969         @Override
1970         public void removeFilesFromTrash(Long userId, List<Long> fileIds) throws ObjectNotFoundException, InsufficientPermissionsException {
1971                 for(Long l : fileIds)
1972                         removeFileFromTrash(userId, l);
1973
1974         }
1975
1976         @Override
1977         public Nonce createNonce(Long userId) throws ObjectNotFoundException {
1978                 if (userId == null)
1979                         throw new ObjectNotFoundException("No user specified");
1980                 User user = dao.getEntityById(User.class, userId);
1981                 Nonce nonce = Nonce.createNonce(user.getId());
1982                 dao.create(nonce);
1983                 return nonce;
1984         }
1985
1986         @Override
1987         public Nonce getNonce(String nonce, Long userId) throws ObjectNotFoundException {
1988                 if (userId == null)
1989                         throw new ObjectNotFoundException("No user specified");
1990                 if (nonce == null)
1991                         throw new ObjectNotFoundException("No nonce specified");
1992                 return dao.getNonce(nonce, userId);
1993         }
1994
1995         @Override
1996         public void removeNonce(Long id) throws ObjectNotFoundException {
1997                 if (id == null)
1998                         throw new ObjectNotFoundException("No nonce specified");
1999                 Nonce nonce = dao.getEntityById(Nonce.class, id);
2000                 dao.delete(nonce);
2001         }
2002
2003         @Override
2004         public void activateUserNonce(Long userId, String nonce, Date nonceExpiryDate) throws ObjectNotFoundException {
2005                 if (userId == null)
2006                         throw new ObjectNotFoundException("No user specified");
2007                 User user = dao.getEntityById(User.class, userId);
2008                 user.setNonce(nonce);
2009                 user.setNonceExpiryDate(nonceExpiryDate);
2010         }
2011
2012         @Override
2013         public StatsDTO getUserStatistics(Long userId) throws ObjectNotFoundException {
2014                 if (userId == null)
2015                         throw new ObjectNotFoundException("No user specified");
2016                 StatsDTO stats = new StatsDTO();
2017                 stats.setFileCount(dao.getFileCount(userId));
2018                 Long fileSize = dao.getFileSize(userId);
2019                 stats.setFileSize(fileSize);
2020                 Long quota = getQuota(userId);
2021                 Long quotaLeft = quota - fileSize;
2022                 stats.setQuotaLeftSize(quotaLeft);
2023                 return stats;
2024         }
2025
2026         @Override
2027         public List<FileBodyDTO> getVersions(Long userId, Long fileId) throws ObjectNotFoundException, InsufficientPermissionsException {
2028                 if (userId == null)
2029                         throw new ObjectNotFoundException("No user specified");
2030                 if (fileId == null)
2031                         throw new ObjectNotFoundException("No file specified");
2032                 User user = dao.getEntityById(User.class, userId);
2033                 FileHeader header = dao.getEntityById(FileHeader.class, fileId);
2034                 if(!header.hasReadPermission(user))
2035                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
2036                 List<FileBodyDTO> result = new LinkedList<FileBodyDTO>();
2037                 for(int i = header.getBodies().size()-1 ; i>=0; i--)
2038                         result.add(header.getBodies().get(i).getDTO());
2039                 return result;
2040         }
2041
2042         @Override
2043         public void removeVersion(Long userId, Long fileId, Long bodyId) throws ObjectNotFoundException, InsufficientPermissionsException {
2044                 if (userId == null)
2045                         throw new ObjectNotFoundException("No user specified");
2046                 if (fileId == null)
2047                         throw new ObjectNotFoundException("No file specified");
2048                 if (bodyId == null)
2049                         throw new ObjectNotFoundException("No body specified");
2050                 User user = dao.getEntityById(User.class, userId);
2051                 FileHeader header = dao.getEntityById(FileHeader.class, fileId);
2052                 if(!header.hasWritePermission(user))
2053                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
2054                 FileBody body = dao.getEntityById(FileBody.class, bodyId);
2055                 if(body.equals(header.getCurrentBody())){
2056
2057                         if(header.getBodies().size() == 1)
2058                                 throw new InsufficientPermissionsException("You cant delete this version, Delete file instead!");
2059                         for(FileBody b : header.getBodies())
2060                                 if(b.getVersion() == body.getVersion()-1)
2061                                         header.setCurrentBody(b);
2062                 }
2063                 deleteActualFile(body.getStoredFilePath());
2064                 header.getBodies().remove(body);
2065
2066                 Folder parent = header.getFolder();
2067                 touchParentFolders(parent, user, new Date());
2068
2069         }
2070
2071         @Override
2072         public void restoreVersion(Long userId, Long fileId, int version) throws ObjectNotFoundException, InsufficientPermissionsException,  GSSIOException, QuotaExceededException {
2073                 if (userId == null)
2074                         throw new ObjectNotFoundException("No user specified");
2075                 if (fileId == null)
2076                         throw new ObjectNotFoundException("No file specified");
2077                 User user = dao.getEntityById(User.class, userId);
2078                 FileHeader header = dao.getEntityById(FileHeader.class, fileId);
2079                 if(!header.hasWritePermission(user))
2080                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
2081                 FileBody body = dao.getFileVersion(fileId, version);
2082                 final File fileContents = new File(body.getStoredFilePath());
2083
2084                 try {
2085                         updateFileContents(userId, fileId, body.getMimeType(), new FileInputStream(fileContents) );
2086                 } catch (FileNotFoundException e) {
2087                         throw new GSSIOException(e);
2088                 }
2089
2090         }
2091
2092         /* (non-Javadoc)
2093          * @see gr.ebs.gss.server.ejb.ExternalAPI#removeOldVersions(java.lang.Long, java.lang.Long)
2094          */
2095         @Override
2096         public void removeOldVersions(Long userId, Long fileId) throws ObjectNotFoundException, InsufficientPermissionsException {
2097                 if (userId == null)
2098                         throw new ObjectNotFoundException("No user specified");
2099                 if (fileId == null)
2100                         throw new ObjectNotFoundException("No file specified");
2101                 User user = dao.getEntityById(User.class, userId);
2102                 FileHeader header = dao.getEntityById(FileHeader.class, fileId);
2103                 if(!header.hasWritePermission(user))
2104                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
2105                 Iterator<FileBody> it = header.getBodies().iterator();
2106                 while(it.hasNext()){
2107                         FileBody body = it.next();
2108                         if(!body.equals(header.getCurrentBody())){
2109                                 deleteActualFile(body.getStoredFilePath());
2110                                 it.remove();
2111                                 dao.delete(body);
2112                         }
2113                 }
2114                 header.getCurrentBody().setVersion(1);
2115
2116                 Folder parent = header.getFolder();
2117                 touchParentFolders(parent, user, new Date());
2118         }
2119
2120         /**
2121          * Gets the quota left for specified user ID.
2122          */
2123         private Long getQuotaLeft(Long userId) throws ObjectNotFoundException{
2124                 Long fileSize = dao.getFileSize(userId);
2125                 Long quota = getQuota(userId);
2126                 return quota - fileSize;
2127         }
2128
2129         /**
2130          * Gets the quota for specified user ID.
2131          */
2132         private Long getQuota(Long userId) throws ObjectNotFoundException{
2133                 UserClass uc = getUser(userId).getUserClass();
2134                 if (uc == null)
2135                         uc = getDefaultUserClass();
2136                 return uc.getQuota();
2137         }
2138
2139         @Override
2140         public void rebuildSolrIndex() {
2141                 try {
2142                         CommonsHttpSolrServer solr = new CommonsHttpSolrServer(getConfiguration().getString("solr.url"));
2143                         solr.deleteByQuery("*:*");
2144                         solr.commit();
2145                         
2146                         List<Long> fileIds = dao.getAllFileIds();
2147                         for (Long id : fileIds) {
2148                                 postFileToSolr(id);
2149                         }
2150                         solr.optimize();
2151                         solr.commit();
2152                 } catch (IOException e) {
2153                         throw new EJBException(e);
2154                 } catch (SolrServerException e) {
2155                         throw new EJBException(e);
2156                 }
2157         }
2158
2159         /**
2160          * Sends a optimize message to the solr server
2161          *
2162          * @param httpClient
2163          * @param retryCount If the commit fails, it is retried three times. This parameter is passed in the recursive
2164          *                                      calls to stop the recursion
2165          * @throws UnsupportedEncodingException
2166          * @throws IOException
2167          * @throws HttpException
2168          */
2169         private void sendOptimize(HttpClient httpClient, int retryCount) throws UnsupportedEncodingException, IOException, HttpException {
2170                 PostMethod method = null;
2171                 try {
2172                         logger.debug("Optimize retry: " + retryCount);
2173                         method = new PostMethod(getConfiguration().getString("solrUpdateUrl"));
2174                         method.setRequestEntity(new StringRequestEntity("<optimize/>", "text/xml", "iso8859-1"));
2175                         int statusCode = httpClient.executeMethod(method);
2176                         logger.debug("HTTP status: " + statusCode);
2177                         String response = method.getResponseBodyAsString();
2178                         logger.debug(response);
2179                         if (statusCode != 200 && retryCount < 2) {
2180                                 try {
2181                                         Thread.sleep(10000); //Give Solr a little time to be available
2182                                 } catch (InterruptedException e) {
2183                                 }
2184                                 sendOptimize(httpClient, retryCount + 1);
2185                         }
2186                 }
2187                 finally {
2188                         if (method != null)
2189                                 method.releaseConnection();
2190                 }
2191         }
2192
2193         @Override
2194         public FileHeaderDTO createFile(Long userId, Long folderId, String name, String mimeType, long fileSize, String filePath)
2195                         throws DuplicateNameException, ObjectNotFoundException, GSSIOException,
2196                         InsufficientPermissionsException, QuotaExceededException {
2197                 // Validate.
2198                 if (userId == null)
2199                         throw new ObjectNotFoundException("No user specified");
2200                 if (folderId == null)
2201                         throw new ObjectNotFoundException("No folder specified");
2202                 String contentType = mimeType;
2203                 if (StringUtils.isEmpty(mimeType))
2204                         contentType = DEFAULT_MIME_TYPE;
2205                 if (StringUtils.isEmpty(name))
2206                         throw new ObjectNotFoundException("No file name specified");
2207                 if (dao.existsFolderOrFile(folderId, name))
2208                         throw new DuplicateNameException("A folder or file with the name '" + name +
2209                                                 "' already exists at this level");
2210
2211                 // Do the actual work.
2212                 Folder parent = null;
2213                 try {
2214                         parent = dao.getEntityById(Folder.class, folderId);
2215                 } catch (final ObjectNotFoundException onfe) {
2216                         // Supply a more accurate problem description.
2217                         throw new ObjectNotFoundException("Parent folder not found");
2218                 }
2219                 final User owner = dao.getEntityById(User.class, userId);
2220                 if (!parent.hasWritePermission(owner))
2221                         throw new InsufficientPermissionsException("You don't have the permissions to write to this folder");
2222                 final FileHeader file = new FileHeader();
2223                 file.setName(name);
2224                 parent.addFile(file);
2225                 // set file owner to folder owner
2226                 file.setOwner(parent.getOwner());
2227                 //set file's readForAll value according to parent folder readForAll value
2228                 file.setReadForAll(parent.isReadForAll());
2229
2230                 final Date now = new Date();
2231                 final AuditInfo auditInfo = new AuditInfo();
2232                 auditInfo.setCreatedBy(owner);
2233                 auditInfo.setCreationDate(now);
2234                 auditInfo.setModifiedBy(owner);
2235                 auditInfo.setModificationDate(now);
2236                 file.setAuditInfo(auditInfo);
2237                 // TODO set the proper versioning flag on creation
2238                 file.setVersioned(false);
2239
2240                 for (final Permission p : parent.getPermissions()) {
2241                         final Permission permission = new Permission();
2242                         permission.setGroup(p.getGroup());
2243                         permission.setUser(p.getUser());
2244                         permission.setRead(p.getRead());
2245                         permission.setWrite(p.getWrite());
2246                         permission.setModifyACL(p.getModifyACL());
2247                         file.addPermission(permission);
2248                 }
2249
2250                 // Create the file body.
2251                 try {
2252                         createFileBody(name, contentType, fileSize, filePath, file, auditInfo);
2253                 } catch (FileNotFoundException e) {
2254                         throw new GSSIOException(e);
2255                 }
2256                 touchParentFolders(parent, owner, new Date());
2257                 dao.flush();
2258                 indexFile(file.getId(), false);
2259
2260                 return file.getDTO();
2261         }
2262
2263         @Override
2264         public FileHeaderDTO updateFileContents(Long userId, Long fileId, String mimeType, long fileSize, String filePath) throws ObjectNotFoundException, GSSIOException, InsufficientPermissionsException, QuotaExceededException {
2265                 if (userId == null)
2266                         throw new ObjectNotFoundException("No user specified");
2267                 if (fileId == null)
2268                         throw new ObjectNotFoundException("No file specified");
2269                 String contentType = mimeType;
2270
2271                 FileHeader file = dao.getEntityById(FileHeader.class, fileId);
2272
2273                 // if no mime type or the generic mime type is defined by the client, then try to identify it from the filename extension
2274                 if (StringUtils.isEmpty(mimeType) || "application/octet-stream".equals(mimeType)
2275                                         || "application/download".equals(mimeType) || "application/force-download".equals(mimeType)
2276                                         || "octet/stream".equals(mimeType) || "application/unknown".equals(mimeType))
2277                         contentType = identifyMimeType(file.getName());
2278
2279                 final User owner = dao.getEntityById(User.class, userId);
2280                 if (!file.hasWritePermission(owner))
2281                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
2282                 final Date now = new Date();
2283                 final AuditInfo auditInfo = new AuditInfo();
2284                 auditInfo.setCreatedBy(owner);
2285                 auditInfo.setCreationDate(now);
2286                 auditInfo.setModifiedBy(owner);
2287                 auditInfo.setModificationDate(now);
2288                 try {
2289                         createFileBody(file.getName(), contentType, fileSize, filePath, file, auditInfo);
2290                 } catch (FileNotFoundException e) {
2291                         throw new GSSIOException(e);
2292                 }
2293                 Folder parent = file.getFolder();
2294                 touchParentFolders(parent, owner, new Date());
2295
2296                 indexFile(fileId, false);
2297                 return file.getDTO();
2298         }
2299
2300         /**
2301          * Helper method for identifying mime type by examining the filename extension
2302          *
2303          * @param filename
2304          * @return the mime type
2305          */
2306         private String identifyMimeType(String filename) {
2307                 if (filename.indexOf('.') != -1) {
2308                         String extension = filename.substring(filename.lastIndexOf('.')).toLowerCase(Locale.ENGLISH);
2309                         if (".doc".equals(extension))
2310                                 return "application/msword";
2311                         else if (".xls".equals(extension))
2312                                 return "application/vnd.ms-excel";
2313                         else if (".ppt".equals(extension))
2314                                 return "application/vnd.ms-powerpoint";
2315                         else if (".pdf".equals(extension))
2316                                 return "application/pdf";
2317                         else if (".gif".equals(extension))
2318                                 return "image/gif";
2319                         else if (".jpg".equals(extension) || ".jpeg".equals(extension) || ".jpe".equals(extension))
2320                                 return "image/jpeg";
2321                         else if (".tiff".equals(extension) || ".tif".equals(extension))
2322                                 return "image/tiff";
2323                         else if (".png".equals(extension))
2324                                 return "image/png";
2325                         else if (".bmp".equals(extension))
2326                                 return "image/bmp";
2327                 }
2328                 // when all else fails assign the default mime type
2329                 return DEFAULT_MIME_TYPE;
2330         }
2331
2332         /**
2333          * Helper method to create a new file body and attach it as the current body
2334          * of the provided file header.
2335          *
2336          * @param name the original file name
2337          * @param mimeType the content type
2338          * @param fileSize the uploaded file size
2339          * @param filePath the uploaded file full path
2340          * @param header the file header that will be associated with the new body
2341          * @param auditInfo the audit info
2342          * @param owner the owner of the file
2343          * @throws FileNotFoundException
2344          * @throws QuotaExceededException
2345          * @throws ObjectNotFoundException if the owner was not found
2346          */
2347         private void createFileBody(String name, String mimeType, long fileSize, String filePath,
2348                                 FileHeader header, AuditInfo auditInfo)
2349                         throws FileNotFoundException, QuotaExceededException, ObjectNotFoundException {
2350
2351                 long currentTotalSize = 0;
2352                 if (!header.isVersioned() && header.getCurrentBody() != null && header.getBodies() != null)
2353                         currentTotalSize = header.getTotalSize();
2354                 Long quotaLeft = getQuotaLeft(header.getOwner().getId());
2355                 if(quotaLeft < fileSize-currentTotalSize) {
2356                         // quota exceeded -> delete the file
2357                         deleteActualFile(filePath);
2358                         throw new QuotaExceededException("Not enough free space available");
2359                 }
2360
2361                 FileBody body = new FileBody();
2362
2363                 // if no mime type or the generic mime type is defined by the client, then try to identify it from the filename extension
2364                 if (StringUtils.isEmpty(mimeType) || "application/octet-stream".equals(mimeType)
2365                                         || "application/download".equals(mimeType) || "application/force-download".equals(mimeType)
2366                                         || "octet/stream".equals(mimeType) || "application/unknown".equals(mimeType))
2367                         body.setMimeType(identifyMimeType(name));
2368                 else
2369                         body.setMimeType(mimeType);
2370                 body.setAuditInfo(auditInfo);
2371                 body.setFileSize(fileSize);
2372                 body.setOriginalFilename(name);
2373                 body.setStoredFilePath(filePath);
2374                 //CLEAR OLD VERSION IF FILE IS NOT VERSIONED AND GETS UPDATED
2375                 if(!header.isVersioned() && header.getCurrentBody() != null){
2376                         header.setCurrentBody(null);
2377                         if (header.getBodies() != null) {
2378                                 Iterator<FileBody> it = header.getBodies().iterator();
2379                                 while(it.hasNext()){
2380                                         FileBody bo = it.next();
2381                                         deleteActualFile(bo.getStoredFilePath());
2382                                         it.remove();
2383                                         dao.delete(bo);
2384                                 }
2385                         }
2386                 }
2387
2388                 dao.flush();
2389                 header.addBody(body);
2390                 header.setAuditInfo(auditInfo);
2391
2392                 dao.create(body);
2393         }
2394
2395
2396         @Override
2397         @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
2398         public File uploadFile(InputStream stream, Long userId) throws IOException, ObjectNotFoundException {
2399                 if (userId == null)
2400                         throw new ObjectNotFoundException("No user specified");
2401                 User owner = dao.getEntityById(User.class, userId);
2402                 if(owner == null)
2403                         throw new ObjectNotFoundException("No user specified");
2404                 long start = 0, end = 0;
2405                 if (logger.isDebugEnabled())
2406                         start = System.currentTimeMillis();
2407                 File result = new File(generateRepositoryFilePath());
2408                 try {
2409                         final FileOutputStream output = new FileOutputStream(result);
2410                         final byte[] buffer = new byte[UPLOAD_BUFFER_SIZE];
2411                         int n = 0;
2412
2413                         while (-1 != (n = stream.read(buffer)))
2414                                 output.write(buffer, 0, n);
2415                         output.close();
2416                         stream.close();
2417                 } catch (IOException e) {
2418                         if (!result.delete())
2419                                 logger.warn("Could not delete " + result.getPath());
2420                         throw e;
2421                 }
2422                 if (logger.isDebugEnabled()) {
2423                         end = System.currentTimeMillis();
2424                         logger.debug("Time to upload: " + (end - start) + " (msec)");
2425                 }
2426                 return result;
2427         }
2428
2429
2430         @Override
2431         public void createFileUploadProgress(Long userId, String filename, Long bytesTransfered, Long fileSize) throws ObjectNotFoundException{
2432
2433                 if (userId == null)
2434                         throw new ObjectNotFoundException("No user specified");
2435                 User user = dao.getEntityById(User.class, userId);
2436                 FileUploadStatus status = dao.getFileUploadStatus(userId, filename);
2437                 if(status == null){
2438                         status = new FileUploadStatus();
2439                         status.setOwner(user);
2440                         status.setFilename(filename);
2441                         status.setBytesUploaded(bytesTransfered);
2442                         status.setFileSize(fileSize);
2443                         dao.create(status);
2444                 }
2445                 else{
2446                         status.setBytesUploaded(bytesTransfered);
2447                         status.setFileSize(fileSize);
2448                         dao.update(status);
2449                 }
2450
2451         }
2452
2453         @Override
2454         public void removeFileUploadProgress(Long userId, String filename) throws ObjectNotFoundException{
2455                 if (userId == null)
2456                         throw new ObjectNotFoundException("No user specified");
2457                 FileUploadStatus status = dao.getFileUploadStatus(userId, filename);
2458                 if(status != null)
2459                         dao.delete(status);
2460         }
2461
2462         @Override
2463         public FileUploadStatus getFileUploadStatus(Long userId, String fileName) {
2464                 return dao.getFileUploadStatus(userId, fileName);
2465         }
2466
2467         @Override
2468         public FolderDTO getFolderWithSubfolders(Long userId, Long folderId) throws ObjectNotFoundException, InsufficientPermissionsException {
2469                 if (userId == null)
2470                         throw new ObjectNotFoundException("No user specified");
2471                 if (folderId == null)
2472                         throw new ObjectNotFoundException("No folder specified");
2473                 final User user = dao.getEntityById(User.class, userId);
2474                 final Folder folder = dao.getEntityById(Folder.class, folderId);
2475                 // Check permissions
2476                 if (!folder.hasReadPermission(user))
2477                         throw new InsufficientPermissionsException("You don't have the permissions to read this folder");
2478                 List<FolderDTO> subfolders = new ArrayList<FolderDTO>();
2479                 if (folder.hasReadPermission(user))
2480                         for (Folder f : folder.getSubfolders())
2481                                 if (f.hasReadPermission(user) && !f.isDeleted())
2482                                         subfolders.add(f.getDTO());
2483                 FolderDTO result = folder.getDTO();
2484                 result.setSubfolders(subfolders);
2485                 return folder.getDTO();
2486         }
2487
2488         @Override
2489         public FolderDTO getFolderWithSubfolders(Long userId, Long callingUserId, Long folderId) throws ObjectNotFoundException, InsufficientPermissionsException {
2490                 if (userId == null)
2491                         throw new ObjectNotFoundException("No user specified");
2492                 if (folderId == null)
2493                         throw new ObjectNotFoundException("No folder specified");
2494                 User user = dao.getEntityById(User.class, callingUserId);
2495                 Folder folder = dao.getEntityById(Folder.class, folderId);
2496                 // Check permissions
2497                 if (!folder.hasReadPermission(user))
2498                         throw new InsufficientPermissionsException("You don't have the permissions to read this folder");
2499
2500                 FolderDTO result = folder.getDTO();
2501                 result.setSubfolders(getSharedSubfolders(userId, callingUserId, folder.getId()));
2502                 return result;
2503         }
2504
2505         @Override
2506         public FileBodyDTO getFileVersion(Long userId, Long fileId, int version)
2507                         throws ObjectNotFoundException, InsufficientPermissionsException {
2508                 if (userId == null)
2509                         throw new ObjectNotFoundException("No user specified");
2510                 if (fileId == null)
2511                         throw new ObjectNotFoundException("No file specified");
2512                 if (version < 1)
2513                         throw new ObjectNotFoundException("No valid version specified");
2514                 User user = dao.getEntityById(User.class, userId);
2515                 FileHeader file = dao.getEntityById(FileHeader.class, fileId);
2516                 if (!file.hasReadPermission(user) && !file.getFolder().hasReadPermission(user))
2517                         throw new InsufficientPermissionsException("You don't have the necessary permissions");
2518                 FileBody body = dao.getFileVersion(fileId, version);
2519                 return body.getDTO();
2520         }
2521
2522         @Override
2523         public User updateUserPolicyAcceptance(Long userId, boolean isAccepted) throws ObjectNotFoundException {
2524                 if (userId == null)
2525                         throw new ObjectNotFoundException("No user specified");
2526                 User user = dao.getEntityById(User.class, userId);
2527                 user.setAcceptedPolicy(isAccepted);
2528                 return user;
2529         }
2530
2531         @Override
2532         public void updateAccounting(User user, Date date, long bandwidthDiff) {
2533                 dao.updateAccounting(user, date, bandwidthDiff);
2534         }
2535
2536         @Override
2537         public boolean canReadFolder(Long userId, Long folderId) throws ObjectNotFoundException {
2538                 if (userId == null)
2539                         throw new ObjectNotFoundException("No user specified");
2540                 if (folderId == null)
2541                         throw new ObjectNotFoundException("No folder specified");
2542                 User user = dao.getEntityById(User.class, userId);
2543                 Folder folder = dao.getEntityById(Folder.class, folderId);
2544                 // Check permissions
2545                 if (!folder.hasReadPermission(user))
2546                         return false;
2547                 return true;
2548         }
2549
2550         @Override
2551         public String resetWebDAVPassword(Long userId) throws ObjectNotFoundException {
2552                 if (userId == null)
2553                         throw new ObjectNotFoundException("No user specified");
2554                 User user = dao.getEntityById(User.class, userId);
2555                 user.generateWebDAVPassword();
2556                 return user.getWebDAVPassword();
2557         }
2558
2559         @Override
2560         public Invitation findInvite(String code) {
2561                 if (code == null)
2562                         return null;
2563                 return dao.findInvite(code);
2564         }
2565
2566         @Override
2567         public void createLdapUser(String username, String firstname, String lastname, String email, String password) {
2568                 LDAPConnection lc = new LDAPConnection();
2569         LDAPAttributeSet attributeSet = new LDAPAttributeSet();
2570         attributeSet.add(new LDAPAttribute("objectClass", getConfiguration().getStringArray("objectClass")));
2571         attributeSet.add(new LDAPAttribute("uid", username));
2572         attributeSet.add(new LDAPAttribute("cn", new String[]{firstname + " " + lastname}));
2573         attributeSet.add(new LDAPAttribute("sn", lastname));
2574         attributeSet.add(new LDAPAttribute("givenName", firstname));
2575         attributeSet.add(new LDAPAttribute("mail", email));
2576         attributeSet.add(new LDAPAttribute("userPassword", password));
2577         String dn = "uid=" + username + "," + getConfiguration().getString("baseDn");
2578         LDAPEntry newEntry = new LDAPEntry(dn, attributeSet);
2579         try {
2580                 lc.connect(getConfiguration().getString("ldapHost"), LDAPConnection.DEFAULT_PORT);
2581                 lc.bind(LDAPConnection.LDAP_V3, getConfiguration().getString("bindDn"),
2582                                 getConfiguration().getString("bindPassword").getBytes("UTF8"));
2583                 lc.add(newEntry);
2584                 logger.info("Successfully added LDAP account: " + dn);
2585                 lc.disconnect();
2586         } catch(LDAPException e) {
2587                 throw new RuntimeException(e);
2588         } catch(UnsupportedEncodingException e) {
2589                 throw new RuntimeException(e);
2590         }
2591
2592         }
2593
2594         @Override
2595         public UserClass upgradeUserClass(String username, String code) throws ObjectNotFoundException, InvitationUsedException {
2596                 User user = findUser(username);
2597                 if (user == null)
2598                         throw new ObjectNotFoundException("The user was not found");
2599                 Invitation invite = findInvite(code);
2600                 if (invite.getUser() != null)
2601                         throw new InvitationUsedException("This code has already been used");
2602                 invite.setUser(user);
2603                 UserClass couponClass = getCouponUserClass();
2604                 user.setUserClass(couponClass);
2605                 return couponClass;
2606         }
2607
2608         @Override
2609         public UserClass getCouponUserClass() {
2610                 return dao.findCouponUserClass();
2611         }
2612
2613         /**
2614          * Mark the folder as modified from the specified user and change it's modification date.
2615          */
2616         private void touchFolder(Folder f, User _user, Date now){
2617                 final AuditInfo auditInfo = f.getAuditInfo();
2618                 auditInfo.setModificationDate(now);
2619                 auditInfo.setModifiedBy(_user);
2620                 f.setAuditInfo(auditInfo);
2621         }
2622
2623         /**
2624          * Mark the file as modified from the specified user and change it's modification date.
2625          */
2626         private void touchFile(FileHeader f, User _user, Date now){
2627                 final AuditInfo auditInfo = f.getAuditInfo();
2628                 auditInfo.setModificationDate(now);
2629                 auditInfo.setModifiedBy(_user);
2630                 f.setAuditInfo(auditInfo);
2631         }
2632
2633         /**
2634          * Set the provided readForAll as the new readforAll value of the specified
2635          * folder and sub-folders.
2636          *
2637          * @param user
2638          * @param folder
2639          * @param readForAll
2640          * @throws ObjectNotFoundException
2641          *
2642          */
2643         private void setFolderReadForAll(User user, Folder folder, Boolean readForAll){
2644                 if (readForAll != null && user.equals(folder.getOwner())){
2645                         folder.setReadForAll(readForAll);
2646                         dao.update(folder);
2647                         for (FileHeader file : folder.getFiles())
2648                                 file.setReadForAll(readForAll);
2649                         if(readForAll)
2650                                 //only update subfolders when readforall is true. otherwise all sub-folders stay untouched
2651                                 for (Folder sub : folder.getSubfolders())
2652                                         setFolderReadForAll(user, sub, readForAll);
2653
2654                 }
2655
2656         }
2657
2658         /**
2659          * @param id
2660          * @param config
2661          * @throws ObjectNotFoundException
2662          * @throws SolrServerException
2663          * @throws IOException
2664          * @throws MalformedURLException
2665          */
2666         public void postFileToSolr(Long id) {
2667                 try {
2668                         FileHeader file = dao.getEntityById(FileHeader.class, id);
2669                         FileBody body = file.getCurrentBody();
2670                         String mime = body.getMimeType();
2671                         boolean multipart = true;
2672                         if (!mime.equals("application/pdf") 
2673                                                 && !mime.equals("text/plain")
2674                                                 && !mime.equals("text/html")
2675                                                 && !mime.endsWith("msword")
2676                                                 && !mime.endsWith("ms-excel")
2677                                                 && !mime.endsWith("powerpoint")
2678                                                 || (body.getFileSize() > getConfiguration().getLong("solrDocumentUploadLimitInKB") * 1024))
2679                                 multipart = false;
2680
2681                         if (!multipart)
2682                                 sendMetaDataOnly(getConfiguration().getString("solr.url"), file);
2683                         else {
2684                                 CommonsHttpSolrServer solr = new CommonsHttpSolrServer(getConfiguration().getString("solr.url"));
2685                                 ContentStreamUpdateRequest solrRequest = new ContentStreamUpdateRequest(getConfiguration().getString("solr.rich.update.path"));
2686                                 solrRequest.setParam("literal.id", file.getId().toString());
2687                                 solrRequest.setParam("literal.name", file.getName());
2688                                 for (FileTag t : file.getFileTags()) {
2689                                         solrRequest.getParams().add("literal.tag", t.getTag());
2690                                 }
2691                                 solrRequest.addFile(new File(body.getStoredFilePath()));
2692                                 solrRequest.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
2693                                 try {
2694                                         solr.request(solrRequest);
2695                                 }
2696                                 catch (SolrException e) {
2697                                         logger.warn("File " + id + " failed with " + e.getLocalizedMessage() + ". Retrying without the file");
2698                                         //Let 's try without the file
2699                                         sendMetaDataOnly(getConfiguration().getString("solr.url"), file);
2700                                 }
2701                                 catch (NullPointerException e) {
2702                                         logger.warn("File " + id + " failed with " + e.getLocalizedMessage() + ". Retrying without the file");
2703                                         //Let 's try without the file
2704                                         sendMetaDataOnly(getConfiguration().getString("solr.url"), file);
2705                                 }
2706                                 catch (SolrServerException e) {
2707                                         logger.warn("File " + id + " failed with " + e.getLocalizedMessage() + ". Retrying without the file");
2708                                         //Let 's try without the file
2709                                         sendMetaDataOnly(getConfiguration().getString("solr.url"), file);
2710                                 }
2711                         }
2712                 } catch (MalformedURLException e) {
2713                         throw new EJBException(e);
2714                 } catch (ObjectNotFoundException e) {
2715                         logger.error("Indexing of file id " + id + " failed.", e);
2716                 } catch (SolrServerException e) {
2717                         throw new EJBException(e);
2718                 } catch (IOException e) {
2719                         throw new EJBException(e);
2720                 }
2721         }
2722
2723         private void sendMetaDataOnly(String solrUrl, FileHeader file) throws SolrServerException, IOException {
2724                 CommonsHttpSolrServer solr = new CommonsHttpSolrServer(solrUrl);
2725                 SolrInputDocument solrDoc = new SolrInputDocument();
2726                 solrDoc.addField("id", file.getId().toString());
2727                 solrDoc.addField("name", file.getName());
2728                 for (FileTag t : file.getFileTags()) {
2729                         solrDoc.addField("tag", t.getTag());
2730                 }
2731                 solr.add(solrDoc);
2732                 solr.commit();
2733         }
2734
2735         private String tokenizeFilename(String filename){
2736                 StringBuffer result = new StringBuffer();
2737                 StringTokenizer tokenizer = new StringTokenizer(filename,"._");
2738                 while(tokenizer.hasMoreTokens()){
2739                         result.append(tokenizer.nextToken());
2740                         result.append(" ");
2741                 }
2742                 result.append(filename);
2743                 return result.toString();
2744         }
2745 }