1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.opencastproject.oaipmh.server;
22
23 import static org.opencastproject.oaipmh.util.OsgiUtil.checkDictionary;
24 import static org.opencastproject.oaipmh.util.OsgiUtil.getCfg;
25 import static org.opencastproject.oaipmh.util.OsgiUtil.getContextProperty;
26 import static org.opencastproject.util.data.Collections.map;
27 import static org.opencastproject.util.data.Monadics.mlist;
28 import static org.opencastproject.util.data.Option.none;
29 import static org.opencastproject.util.data.Option.some;
30 import static org.opencastproject.util.data.functions.Strings.trimToNil;
31
32 import org.opencastproject.oaipmh.util.XmlGen;
33 import org.opencastproject.security.api.SecurityService;
34 import org.opencastproject.util.OsgiUtil;
35 import org.opencastproject.util.UrlSupport;
36 import org.opencastproject.util.data.Option;
37
38 import org.apache.commons.lang3.StringUtils;
39 import org.osgi.framework.ServiceRegistration;
40 import org.osgi.service.cm.ConfigurationException;
41 import org.osgi.service.component.ComponentContext;
42 import org.osgi.service.component.annotations.Activate;
43 import org.osgi.service.component.annotations.Component;
44 import org.osgi.service.component.annotations.Deactivate;
45 import org.osgi.service.component.annotations.Modified;
46 import org.osgi.service.component.annotations.Reference;
47 import org.osgi.service.component.annotations.ReferenceCardinality;
48 import org.osgi.service.component.annotations.ReferencePolicy;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 import java.io.IOException;
53 import java.util.Dictionary;
54 import java.util.Map;
55
56 import javax.servlet.ServletException;
57 import javax.servlet.http.HttpServlet;
58 import javax.servlet.http.HttpServletRequest;
59 import javax.servlet.http.HttpServletResponse;
60
61
62 @Component(
63 immediate = true,
64 service = { OaiPmhServerInfo.class, OaiPmhServer.class },
65 property = {
66 "service.description=OAI-PMH server"
67 }
68 )
69 public final class OaiPmhServer extends HttpServlet implements OaiPmhServerInfo {
70
71 private static final long serialVersionUID = -7536526468920288612L;
72
73 private static final Logger logger = LoggerFactory.getLogger(OaiPmhServer.class);
74
75 private static final String CFG_DEFAULT_REPOSITORY = "default-repository";
76 private static final String CFG_OAIPMH_MOUNTPOINT = "org.opencastproject.oaipmh.mountpoint";
77 private static final String CFG_DEFAULT_OAIPMH_MOUNTPOINT = "/oaipmh";
78
79 private SecurityService securityService;
80
81 private final Map<String, OaiPmhRepository> repositories = map();
82
83 private ComponentContext componentContext;
84
85 private String defaultRepo;
86
87
88
89
90 private String mountPoint;
91
92 private ServiceRegistration<?> serviceRegistration;
93
94
95 @Reference(
96 cardinality = ReferenceCardinality.MULTIPLE,
97 policy = ReferencePolicy.DYNAMIC,
98 unbind = "unsetRepository"
99 )
100 public void setRepository(final OaiPmhRepository r) {
101 synchronized (repositories) {
102 final String rId = r.getRepositoryId();
103 if (repositories.containsKey(rId)) {
104 logger.error("A repository with id {} has already been registered", rId);
105 } else {
106
107 repositories.put(rId, r);
108 logger.info("Registered repository " + rId);
109 }
110 }
111 }
112
113
114 public void unsetRepository(OaiPmhRepository r) {
115 synchronized (repositories) {
116 repositories.remove(r.getRepositoryId());
117 logger.info("Unregistered repository " + r.getRepositoryId());
118 }
119 }
120
121
122 @Reference
123 public void setSecurityService(SecurityService securityService) {
124 this.securityService = securityService;
125 }
126
127
128 @Activate
129 public void activate(ComponentContext cc) throws ConfigurationException {
130 logger.info("Activate");
131 this.componentContext = cc;
132
133 try {
134 mountPoint = UrlSupport.concat("/", StringUtils.trimToNull(getContextProperty(componentContext, CFG_OAIPMH_MOUNTPOINT)));
135 } catch (RuntimeException e) {
136 mountPoint = CFG_DEFAULT_OAIPMH_MOUNTPOINT;
137 }
138 updated(cc.getProperties());
139 }
140
141 @Modified
142 public void modified(ComponentContext cc) throws ConfigurationException {
143 logger.info("Updated");
144 updated(cc.getProperties());
145 }
146
147 @Deactivate
148 public void deactivate() {
149 tryUnregisterServlet();
150 }
151
152
153 public synchronized void updated(Dictionary<String, ?> properties) throws ConfigurationException {
154
155
156
157 checkDictionary(properties, componentContext);
158 defaultRepo = getCfg(properties, CFG_DEFAULT_REPOSITORY);
159
160 try {
161
162 tryUnregisterServlet();
163 logger.info("Registering OAI-PMH server under " + mountPoint);
164 logger.info("Default repository is " + defaultRepo);
165
166 serviceRegistration = OsgiUtil.registerServlet(componentContext.getBundleContext(), this, mountPoint);
167 } catch (Exception e) {
168 logger.error("Error registering OAI-PMH servlet", e);
169 throw new RuntimeException("Error registering OAI-PMH servlet", e);
170 }
171 logger.info("There are {} repositories registered yet. Watch out for later registration messages.",
172 repositories.values().size());
173 }
174
175 @Override
176 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
177 dispatch(req, res);
178 }
179
180 @Override
181 protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
182 dispatch(req, res);
183 }
184
185 private void dispatch(final HttpServletRequest req, final HttpServletResponse res) throws IOException {
186 try {
187 for (String serverUrl : OaiPmhServerInfoUtil.oaiPmhServerUrlOfCurrentOrganization(securityService)) {
188 for (String repoId : repositoryId(req, mountPoint)) {
189 if (runRepo(repoId, serverUrl, req, res)) {
190 return;
191 } else {
192 res.sendError(HttpServletResponse.SC_NOT_FOUND);
193 return;
194 }
195 }
196
197 if (runRepo(defaultRepo, serverUrl, req, res)) {
198 return;
199 }
200 }
201 res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
202 } catch (Exception e) {
203 logger.error("Error handling OAI-PMH request", e);
204 res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
205 }
206 }
207
208
209
210
211
212
213 private boolean runRepo(String repoId, String serverUrl, HttpServletRequest req, HttpServletResponse res)
214 throws Exception {
215 for (OaiPmhRepository repo : getRepoById(repoId)) {
216 final String repoUrl = UrlSupport.concat(serverUrl, mountPoint, repoId);
217 runRepo(repo, repoUrl, req, res);
218 return true;
219 }
220 return false;
221 }
222
223 private void runRepo(OaiPmhRepository repo, final String repoUrl, final HttpServletRequest req,
224 HttpServletResponse res) throws Exception {
225 final Params p = new Params() {
226 @Override
227 String getParameter(String key) {
228 return req.getParameter(key);
229 }
230
231 @Override
232 String getRepositoryUrl() {
233 return repoUrl;
234 }
235 };
236 final XmlGen oai = repo.selectVerb(p);
237 res.setCharacterEncoding("UTF-8");
238 res.setContentType("text/xml;charset=UTF-8");
239 oai.generate(res.getOutputStream());
240 }
241
242 private synchronized void tryUnregisterServlet() {
243 if (serviceRegistration != null) {
244 serviceRegistration.unregister();
245 serviceRegistration = null;
246 }
247 }
248
249
250
251
252
253
254
255
256
257 public static Option<String> repositoryId(HttpServletRequest req, String mountPoint) {
258 return mlist(StringUtils.removeStart(UrlSupport.removeDoubleSeparator(req.getRequestURI()), mountPoint).split("/"))
259 .bind(trimToNil).headOpt();
260 }
261
262
263 private Option<OaiPmhRepository> getRepoById(String id) {
264 synchronized (repositories) {
265 if (hasRepo(id)) {
266 return some(repositories.get(id));
267 } else {
268 logger.warn("No OAI-PMH repository has been registered with id " + id);
269 return none();
270 }
271 }
272 }
273
274 @Override
275 public boolean hasRepo(String id) {
276 synchronized (repositories) {
277 return repositories.containsKey(id);
278 }
279 }
280
281 @Override
282 public String getMountPoint() {
283 return mountPoint;
284 }
285 }