source: ComponentRegistry/trunk/ComponentRegistry/src/main/java/clarin/cmdi/componentregistry/impl/database/ComponentRegistryDbImpl.java @ 1790

Last change on this file since 1790 was 1790, checked in by twagoo, 12 years ago

When registering a component, set creatorName in component description from display name in database

Related to #156

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