source: ComponentRegistry/branches/jeaferversion/ComponentRegistry/src/main/java/clarin/cmdi/componentregistry/impl/database/ComponentRegistryDbImpl.java @ 1640

Last change on this file since 1640 was 1640, checked in by jeafer, 12 years ago

Jean-Charles branch ComponentRegistry commit5 (functionnal),
Changes regarding comment on the ComponentRegistry.

Modification of classes CommentsDao?
Response class has been split up. A general class ComponentRegistryResponse?
a abstractDescription response: RegisterResponse?
a comment response : CommentResponse?
Improvement of validation process for comments
Improvement of ComponentRegistryRestService?
New classes test CommentResponseTest? and CommentValidatorTest?

Documentation added to most classes
Clean up code in other classes

File size: 22.6 KB
Line 
1package clarin.cmdi.componentregistry.impl.database;
2
3import java.io.ByteArrayInputStream;
4import java.io.ByteArrayOutputStream;
5import java.io.IOException;
6import java.io.InputStream;
7import java.io.OutputStream;
8import java.io.UnsupportedEncodingException;
9import java.security.Principal;
10import java.text.ParseException;
11import java.util.Calendar;
12import java.util.Date;
13import java.util.List;
14
15import javax.xml.bind.JAXBException;
16
17import org.slf4j.Logger;
18import org.slf4j.LoggerFactory;
19import org.springframework.beans.factory.annotation.Autowired;
20import org.springframework.beans.factory.annotation.Qualifier;
21import org.springframework.dao.DataAccessException;
22
23import clarin.cmdi.componentregistry.ComponentRegistry;
24import clarin.cmdi.componentregistry.ComponentRegistryException;
25import clarin.cmdi.componentregistry.Configuration;
26import clarin.cmdi.componentregistry.DeleteFailedException;
27import clarin.cmdi.componentregistry.MDMarshaller;
28import clarin.cmdi.componentregistry.UserUnauthorizedException;
29import clarin.cmdi.componentregistry.components.CMDComponentSpec;
30import clarin.cmdi.componentregistry.impl.ComponentRegistryImplBase;
31import clarin.cmdi.componentregistry.model.AbstractDescription;
32import clarin.cmdi.componentregistry.model.Comment;
33import clarin.cmdi.componentregistry.model.ComponentDescription;
34import clarin.cmdi.componentregistry.model.ProfileDescription;
35import clarin.cmdi.componentregistry.model.UserMapping.User;
36
37/**
38 * Implementation of ComponentRegistry that uses Database Acces Objects for
39 * accessing the registry (ergo: a database implementation)
40 *
41 * @author Twan Goosen <twan.goosen@mpi.nl>
42 */
43public class ComponentRegistryDbImpl extends ComponentRegistryImplBase implements ComponentRegistry {
44
45    private final static Logger LOG = LoggerFactory.getLogger(ComponentRegistryDbImpl.class);
46    private Number userId;
47    @Autowired
48    private Configuration configuration;
49    @Autowired
50    private ProfileDescriptionDao profileDescriptionDao;
51    @Autowired
52    private ComponentDescriptionDao componentDescriptionDao;
53    @Autowired
54    private UserDao userDao;
55    @Autowired
56    @Qualifier("componentsCache")
57    private CMDComponentSpecCache componentsCache;
58    @Autowired
59    @Qualifier("profilesCache")
60    private CMDComponentSpecCache profilesCache;
61    @Autowired
62    private CommentsDao commentsDao;
63
64    /**
65     * Default constructor, makes this a (spring) bean. No user is set, so
66     * public registry by default. Use setUser() to make it a user registry.
67     *
68     * @see setUser
69     */
70    public ComponentRegistryDbImpl() {
71    }
72
73    /**
74     * Creates a new ComponentRegistry (either public or not) for the provided
75     * user
76     *
77     * @param userId
78     *            User id of the user to create registry for. Pass null for
79     *            public
80     */
81    public ComponentRegistryDbImpl(Number userId) {
82        this.userId = userId;
83    }
84
85    @Override
86    public List<ProfileDescription> getProfileDescriptions() throws ComponentRegistryException {
87        try {
88            if (isPublic()) {
89                return profileDescriptionDao.getPublicProfileDescriptions();
90            } else {
91                return profileDescriptionDao.getUserspaceDescriptions(getUserId());
92            }
93        } catch (DataAccessException ex) {
94            throw new ComponentRegistryException("Database access error while trying to get profile descriptions", ex);
95        }
96    }
97
98    @Override
99    public ProfileDescription getProfileDescription(String id) throws ComponentRegistryException {
100        try {
101            return profileDescriptionDao.getByCmdId(id, getUserId());
102        } catch (DataAccessException ex) {
103            throw new ComponentRegistryException("Database access error while trying to get profile description", ex);
104        }
105    }
106
107    @Override
108    public List<ComponentDescription> getComponentDescriptions() throws ComponentRegistryException {
109        try {
110            if (isPublic()) {
111                return componentDescriptionDao.getPublicComponentDescriptions();
112            } else {
113                return componentDescriptionDao.getUserspaceDescriptions(getUserId());
114            }
115        } catch (DataAccessException ex) {
116            throw new ComponentRegistryException("Database access error while trying to get component descriptions", ex);
117        }
118    }
119
120    @Override
121    public ComponentDescription getComponentDescription(String id) throws ComponentRegistryException {
122        try {
123            return componentDescriptionDao.getByCmdId(id, getUserId());
124        } catch (DataAccessException ex) {
125            throw new ComponentRegistryException("Database access error while trying to get component description", ex);
126        }
127    }
128
129    @Override
130    public List<Comment> getCommentsInProfile(String profileId) throws ComponentRegistryException {
131        try {
132            return commentsDao.getCommentsFromProfile(profileId);
133        } catch (DataAccessException ex) {
134            throw new ComponentRegistryException("Database access error while trying to get list of comments from profile", ex);
135        }
136    }
137
138    @Override
139    public Comment getSpecifiedCommentInProfile(String commentId) throws ComponentRegistryException {
140        try {
141            return commentsDao.getSpecifiedCommentFromProfile(commentId);
142        } catch (DataAccessException ex) {
143            throw new ComponentRegistryException("Database access error while trying to get comment from profile", ex);
144        }
145    }
146
147    @Override
148    public List<Comment> getCommentsInComponent(String componentId) throws ComponentRegistryException {
149        try {
150            return commentsDao.getCommentsFromComponent(componentId);
151        } catch (DataAccessException ex) {
152            throw new ComponentRegistryException("Database access error while trying to get list of comments from component", ex);
153        }
154    }
155
156    @Override
157    public Comment getSpecifiedCommentInComponent(String commentId) throws ComponentRegistryException {
158        try {
159            return commentsDao.getSpecifiedCommentFromComponent(commentId);
160        } catch (DataAccessException ex) {
161            throw new ComponentRegistryException("Database access error while trying to get comment from component", ex);
162        }
163    }
164
165    @Override
166    public CMDComponentSpec getMDProfile(String id) throws ComponentRegistryException {
167        if (inWorkspace(profileDescriptionDao, id)) {
168            CMDComponentSpec result = profilesCache.get(id);
169            if (result == null && !profilesCache.containsKey(id)) {
170                result = getUncachedMDProfile(id);
171                profilesCache.put(id, result);
172            }
173            return result;
174        } else {
175            // May exist, but not in this workspace
176            return null;
177        }
178    }
179
180    public CMDComponentSpec getUncachedMDProfile(String id) throws ComponentRegistryException {
181        try {
182            return getUncachedMDComponent(id, profileDescriptionDao);
183        } catch (DataAccessException ex) {
184            throw new ComponentRegistryException("Database access error while trying to get profile", ex);
185        }
186    }
187
188    @Override
189    public CMDComponentSpec getMDComponent(String id) throws ComponentRegistryException {
190        if (inWorkspace(componentDescriptionDao, id)) {
191            CMDComponentSpec result = componentsCache.get(id);
192            if (result == null && !componentsCache.containsKey(id)) {
193                result = getUncachedMDComponent(id);
194                componentsCache.put(id, result);
195            }
196            return result;
197        } else {
198            return null;
199        }
200    }
201
202    public CMDComponentSpec getUncachedMDComponent(String id) throws ComponentRegistryException {
203        try {
204            return getUncachedMDComponent(id, componentDescriptionDao);
205        } catch (DataAccessException ex) {
206            throw new ComponentRegistryException("Database access error while trying to get component", ex);
207        }
208    }
209
210    @Override
211    public int register(AbstractDescription description, CMDComponentSpec spec) {
212        enrichSpecHeader(spec, description);
213        try {
214            String xml = componentSpecToString(spec);
215            // Convert principal name to user record id
216            Number uid = convertUserIdInDescription(description);
217            getDaoForDescription(description).insertDescription(description, xml, isPublic(), uid);
218            invalidateCache(description);
219            return 0;
220        } catch (DataAccessException ex) {
221            LOG.error("Database error while registering component", ex);
222            return -1;
223        } catch (JAXBException ex) {
224            LOG.error("Error while registering component", ex);
225            return -2;
226        } catch (UnsupportedEncodingException ex) {
227            LOG.error("Error while registering component", ex);
228            return -3;
229        }
230    }
231
232    @Override
233    public int registerComment(Comment comment, String userId) {
234        try {
235            // Convert principal name to user record id
236            Number uid = convertUserIdInComment(comment, userId);
237            commentsDao.insertComment(comment, uid);
238            return 0;
239        } catch (DataAccessException ex) {
240            LOG.error("Database error while registering component", ex);
241            return -1;
242        }
243    }
244
245    /**
246     * Calling service sets user id to principle. Our task is to convert this to
247     * an id for later reference. If none is set and this is a user's workspace,
248     * set from that user's id.
249     *
250     * @param description
251     *            Description containing principle name as userId
252     * @return Id (from database)
253     * @throws DataAccessException
254     */
255    private Number convertUserIdInDescription(AbstractDescription description) throws DataAccessException {
256        Number uid = null;
257        if (description.getUserId() != null) {
258            User user = userDao.getByPrincipalName(description.getUserId());
259            if (user != null) {
260                uid = user.getId();
261            }
262        } else {
263            uid = userId;
264        }
265        if (uid != null) {
266            description.setUserId(uid.toString());
267        }
268        return uid;
269    }
270
271    /**
272     * Calling service sets user id to principle. Our task is to convert this to
273     * an id for later reference. If none is set and this is a user's workspace,
274     * set from that user's id.
275     *
276     * @param comment
277     *            Comment containing principle name as userId
278     * @return Id (from database)
279     * @throws DataAccessException
280     */
281    private Number convertUserIdInComment(Comment comment, String uId) throws DataAccessException {
282        Number uid = null;
283        if (comment.getUserId() != null) {
284            User user = userDao.getByPrincipalName(uId);
285            if (user != null) {
286                uid = user.getId();
287            }
288        } else {
289            uid = userId;
290        }
291        if (uid != null) {
292            comment.setUserId(uid.toString());
293        }
294        return uid;
295    }
296
297    @Override
298    public int update(AbstractDescription description, CMDComponentSpec spec, Principal principal, boolean forceUpdate) {
299        try {
300            checkAuthorisation(description, principal);
301            checkAge(description, principal);
302            // For public components, check if used in other components or profiles (unless forced)
303            if (!forceUpdate && this.isPublic() && !description.isProfile()) {
304                checkStillUsed(description.getId());
305            }
306            AbstractDescriptionDao<?> dao = getDaoForDescription(description);
307            dao.updateDescription(getIdForDescription(description), description, componentSpecToString(spec));
308            invalidateCache(description);
309            return 0;
310        } catch (JAXBException ex) {
311            LOG.error("Error while updating component", ex);
312            return -1;
313        } catch (UnsupportedEncodingException ex) {
314            LOG.error("Error while updating component", ex);
315            return -1;
316        } catch (IllegalArgumentException ex) {
317            LOG.error("Error while updating component", ex);
318            return -1;
319        } catch (UserUnauthorizedException e) {
320            LOG.error("Error while updating component", e);
321            return -1;
322        } catch (DeleteFailedException e) {
323            LOG.error("Error while updating component", e);
324            return -1;
325        } catch (ComponentRegistryException e) {
326            LOG.error("Error while updating component", e);
327            return -1;
328        }
329    }
330
331    @Override
332    public int publish(AbstractDescription desc, CMDComponentSpec spec, Principal principal) {
333        int result = 0;
334        AbstractDescriptionDao<?> dao = getDaoForDescription(desc);
335        if (!isPublic()) { // if already in public workspace there is nothing todo
336            desc.setHref(AbstractDescription.createPublicHref(desc.getHref()));
337            Number id = getIdForDescription(desc);
338            try {
339                // Update description & content
340                dao.updateDescription(id, desc, componentSpecToString(spec));
341                // Set to public
342                dao.setPublished(id, true);
343            } catch (DataAccessException ex) {
344                LOG.error("Database error while updating component", ex);
345                return -1;
346            } catch (JAXBException ex) {
347                LOG.error("Error while updating component", ex);
348                return -2;
349            } catch (UnsupportedEncodingException ex) {
350                LOG.error("Error while updating component", ex);
351                return -3;
352            }
353        }
354        return result;
355    }
356
357    @Override
358    public void getMDProfileAsXml(String profileId, OutputStream output) throws ComponentRegistryException {
359        CMDComponentSpec expandedSpec = CMDComponentSpecExpanderDbImpl.expandProfile(profileId, this);
360        writeXml(expandedSpec, output);
361    }
362
363    @Override
364    public void getMDProfileAsXsd(String profileId, OutputStream outputStream) throws ComponentRegistryException {
365        CMDComponentSpec expandedSpec = CMDComponentSpecExpanderDbImpl.expandProfile(profileId, this);
366        writeXsd(expandedSpec, outputStream);
367    }
368
369    @Override
370    public void getMDComponentAsXml(String componentId, OutputStream output) throws ComponentRegistryException {
371        CMDComponentSpec expandedSpec = CMDComponentSpecExpanderDbImpl.expandComponent(componentId, this);
372        writeXml(expandedSpec, output);
373    }
374
375    @Override
376    public void getMDComponentAsXsd(String componentId, OutputStream outputStream) throws ComponentRegistryException {
377        CMDComponentSpec expandedSpec = CMDComponentSpecExpanderDbImpl.expandComponent(componentId, this);
378        writeXsd(expandedSpec, outputStream);
379    }
380
381    @Override
382    public void deleteMDProfile(String profileId, Principal principal) throws UserUnauthorizedException, DeleteFailedException,
383            ComponentRegistryException {
384        ProfileDescription desc = getProfileDescription(profileId);
385        if (desc != null) {
386            try {
387                checkAuthorisation(desc, principal);
388                checkAge(desc, principal);
389                profileDescriptionDao.setDeleted(desc, true);
390                invalidateCache(desc);
391            } catch (DataAccessException ex) {
392                throw new DeleteFailedException("Database access error while trying to delete profile", ex);
393            }
394        }
395    }
396
397    @Override
398    public void deleteMDComponent(String componentId, Principal principal, boolean forceDelete) throws UserUnauthorizedException,
399            DeleteFailedException, ComponentRegistryException {
400        ComponentDescription desc = componentDescriptionDao.getByCmdId(componentId);
401        if (desc != null) {
402            try {
403                checkAuthorisation(desc, principal);
404                checkAge(desc, principal);
405
406                if (!forceDelete) {
407                    checkStillUsed(componentId);
408                }
409                componentDescriptionDao.setDeleted(desc, true);
410                invalidateCache(desc);
411            } catch (DataAccessException ex) {
412                throw new DeleteFailedException("Database access error while trying to delete component", ex);
413            }
414        }
415    }
416
417    @Override
418    public boolean isPublic() {
419        return null == userId;
420    }
421
422    public void setPublic() {
423        this.userId = null;
424    }
425
426    /**
427     * @return The user, or null if this is the public registry.
428     */
429    public Number getUserId() {
430        return userId;
431    }
432
433    /**
434     * @param User
435     *            for which this should be the registry. Pass null for the
436     *            public registry
437     */
438    public void setUserId(Number user) {
439        this.userId = user;
440    }
441
442    private void invalidateCache(AbstractDescription description) {
443        if (description.isProfile()) {
444            profilesCache.remove(description.getId());
445        } else {
446            componentsCache.remove(description.getId());
447        }
448    }
449
450    private AbstractDescriptionDao<?> getDaoForDescription(AbstractDescription description) {
451        return description.isProfile() ? profileDescriptionDao : componentDescriptionDao;
452    }
453
454    /**
455     * Looks up description on basis of CMD Id. This will also check if such a
456     * record even exists.
457     *
458     * @param description
459     *            Description to look up
460     * @return Database id for description
461     * @throws IllegalArgumentException
462     *             If description with non-existing id is passed
463     */
464    private Number getIdForDescription(AbstractDescription description) throws IllegalArgumentException {
465        Number dbId = null;
466        AbstractDescriptionDao<?> dao = getDaoForDescription(description);
467        try {
468            dbId = dao.getDbId(description.getId());
469        } catch (DataAccessException ex) {
470            LOG.error("Error getting dbId for component with id " + description.getId(), ex);
471        }
472        if (dbId == null) {
473            throw new IllegalArgumentException("Could not get database Id for description");
474        } else {
475            return dbId;
476        }
477    }
478
479    private String componentSpecToString(CMDComponentSpec spec) throws UnsupportedEncodingException, JAXBException {
480        ByteArrayOutputStream os = new ByteArrayOutputStream();
481        MDMarshaller.marshal(spec, os);
482        String xml = os.toString("UTF-8");
483        return xml;
484    }
485
486    private CMDComponentSpec getUncachedMDComponent(String id, AbstractDescriptionDao dao) {
487        String xml = dao.getContent(false, id);
488        if (xml != null) {
489            try {
490                InputStream is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
491                return MDMarshaller.unmarshal(CMDComponentSpec.class, is, null);
492
493            } catch (JAXBException ex) {
494                LOG.error(null, ex);
495            } catch (UnsupportedEncodingException ex) {
496                LOG.error(null, ex);
497            }
498        }
499        return null;
500    }
501
502    private void checkAuthorisation(AbstractDescription desc, Principal principal) throws UserUnauthorizedException {
503        if (!isOwnerOfDescription(desc, principal.getName()) && !configuration.isAdminUser(principal)) {
504            throw new UserUnauthorizedException("Unauthorized operation user '" + principal.getName()
505                    + "' is not the creator (nor an administrator) of the " + (desc.isProfile() ? "profile" : "component") + "(" + desc
506                    + ").");
507        }
508    }
509
510    private void checkAuthorisationComment(Comment desc, Principal principal) throws UserUnauthorizedException {
511        if (!isOwnerOfComment(desc, principal.getName()) && !configuration.isAdminUser(principal)) {
512            throw new UserUnauthorizedException("Unauthorized operation user '" + principal.getName()
513                    + "' is not the creator (nor an administrator) of the " + (desc.getId()) + "(" + desc
514                    + ").");
515        }
516    }
517
518    private boolean isOwnerOfDescription(AbstractDescription desc, String principalName) {
519        String owner = getDaoForDescription(desc).getOwnerPrincipalName(getIdForDescription(desc));
520        return owner != null // If owner is null, no one can be owner
521                && principalName.equals(owner);
522    }
523
524    private boolean isOwnerOfComment(Comment com, String principalName) {
525        String owner = commentsDao.getOwnerPrincipalName(Integer.parseInt(com.getId()));
526        return owner != null // If owner is null, no one can be owner
527                && principalName.equals(owner);
528    }
529
530    private void checkAge(AbstractDescription desc, Principal principal) throws DeleteFailedException {
531        if (isPublic() && !configuration.isAdminUser(principal)) {
532            try {
533                Date regDate = AbstractDescription.getDate(desc.getRegistrationDate());
534                Calendar calendar = Calendar.getInstance();
535                calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH) - 1);
536                if (regDate.before(calendar.getTime())) { // More then month old
537                    throw new DeleteFailedException(
538                            "The "
539                            + (desc.isProfile() ? "Profile" : "Component")
540                            + " is more then a month old and cannot be deleted anymore. It might have been used to create metadata, deleting it would invalidate that metadata.");
541                }
542            } catch (ParseException e) {
543                LOG.error("Cannot parse date of " + desc + " Error:" + e);
544            }
545        }
546    }
547
548    private boolean inWorkspace(AbstractDescriptionDao<?> dao, String cmdId) {
549        if (isPublic()) {
550            return dao.isPublic(cmdId);
551        } else {
552            return dao.isInUserSpace(cmdId, getUserId());
553        }
554    }
555
556    @Override
557    public String getName() {
558        if (isPublic()) {
559            return ComponentRegistry.PUBLIC_NAME;
560        } else {
561            return "User " + getUserId() + " Registry";
562        }
563    }
564
565    @Override
566    public List<ProfileDescription> getDeletedProfileDescriptions() {
567        return profileDescriptionDao.getDeletedDescriptions(getUserId());
568    }
569
570    @Override
571    public List<ComponentDescription> getDeletedComponentDescriptions() {
572        return componentDescriptionDao.getDeletedDescriptions(getUserId());
573    }
574
575    @Override
576    public void deleteComment(String commentId, Principal principal) throws IOException,
577            ComponentRegistryException, UserUnauthorizedException, DeleteFailedException {
578        Comment com = commentsDao.getById(commentId);
579        if (com != null) {
580            try {
581                checkAuthorisationComment(com, principal);
582                commentsDao.deleteComment(com);
583            } catch (DataAccessException ex) {
584                throw new DeleteFailedException("Database access error while trying to delete component", ex);
585            }
586        }
587    }
588}
Note: See TracBrowser for help on using the repository browser.