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