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

Last change on this file since 1604 was 1604, checked in by twagoo, 13 years ago

Added parameter 'mdEditor' to REST call /registry/profiles which causes only selected profiles are shown. Implemented programmatic filtering on this, could be done on database which would be more efficient.

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