/* * Copyright 2012 James Moger * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.moxie.proxy; import java.io.File; import java.io.FilenameFilter; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.moxie.IMavenCache; import org.moxie.Pom; import org.moxie.PomReader; import org.moxie.RemoteRepository; import org.moxie.proxy.connection.ProxyConnectionServer; import org.moxie.proxy.resources.ArtifactsResource; import org.moxie.proxy.resources.AtomResource; import org.moxie.proxy.resources.RecentResource; import org.moxie.proxy.resources.RootResource; import org.moxie.proxy.resources.SearchResource; import org.moxie.utils.StringUtils; import org.restlet.Application; import org.restlet.Context; import org.restlet.Restlet; import org.restlet.ext.freemarker.ContextTemplateLoader; import org.restlet.resource.Directory; import org.restlet.routing.Router; import org.restlet.routing.TemplateRoute; import org.restlet.routing.Variable; import freemarker.template.Configuration; public class MoxieProxy extends Application { private final ProxyConfig config; private final LuceneExecutor lucene; private final ProxyConnectionServer proxy; private final ScheduledExecutorService executorService; private Configuration configuration; public MoxieProxy(ProxyConfig config) { this.config = config; this.lucene = new LuceneExecutor(config); this.proxy = new ProxyConnectionServer(config, lucene); this.executorService = Executors.newSingleThreadScheduledExecutor(); } @Override public Restlet createInboundRoot() { Context context = getContext(); // initialize Freemarker templates configuration = new Configuration(); configuration.setTemplateLoader(new ContextTemplateLoader(context, "clap://class/templates")); configuration.setDateFormat(config.getDateFormat()); // map the routes Router router = new Router(context); // map the local repository folders for (String folder : config.getLocalRepositories()) { attachMaven2(context, router, folder); attachBrowsing(context, router, folder); } // map the remote repository folders for (RemoteRepository repository : config.getRemoteRepositories()) { attachMaven2(context, router, repository.id); attachBrowsing(context, router, repository.id); } // Search artifacts router.attach("/search", SearchResource.class); // Recent artifacts router.attach("/recent/{repository}", RecentResource.class); router.attach("/recent", RecentResource.class); // Atom artifacts feed router.attach("/atom/{repository}", AtomResource.class); router.attach("/atom", AtomResource.class); // Root router.attach("/", RootResource.class); router.attach("", RootResource.class); // static resources Directory dir = new Directory(context, "clap://class"); dir.setListingAllowed(true); dir.setDeeplyAccessible(true); router.attach("/", dir); if (config.getProxyPort() > 0) { getLogger().info(MessageFormat.format(Constants.MESSAGE_STARTUP, "PROXY", config.getProxyPort())); } getLogger().info(MessageFormat.format("Moxie root is {0}", config.getMoxieRoot())); getLogger().info(MessageFormat.format("{0} is ready.", Constants.getName())); return router; } /** * Attaches a folder for Maven 2 serving * @param context * @param router * @param folder */ void attachMaven2(Context context, Router router, String folder) { // Maven 2 resource Directory m2 = new Directory(context, config.getArtifactRoot(folder).toURI().toString()); m2.setListingAllowed(true); m2.setDeeplyAccessible(true); router.attach("/m2/" + folder, m2); config.getArtifactRoot(folder).mkdirs(); } /** * Attaches a folder for web browsing * @param context * @param router * @param folder */ void attachBrowsing(Context context, Router router, String folder) { TemplateRoute route = router.attach("/" + folder + "/{path}", ArtifactsResource.class); Map<String, Variable> variables = route.getTemplate().getVariables(); variables.put("path", new Variable(Variable.TYPE_URI_PATH)); router.attach("/" + folder, ArtifactsResource.class); } /** * Start the Moxie Proxy app. * * @throws Exception */ @Override public void start() throws Exception { super.start(); // reindex all artifacts lucene.reindex(); // setup asynchronous incremental updates // // We could sychronously update indexes on pom retrieval BUT we run // into complications with parsing parent poms which we might not have // at index time. So instead we queue poms to index and process // them after a modest delay most likely sufficient to have realized the // retrieval of the parent poms. executorService.scheduleAtFixedRate(lucene, 2, 2, TimeUnit.MINUTES); // start the proxy server if (config.isProxyEnabled()) { proxy.start(); } } /** * Stop the Moxie Proxy app. * * @throws Exception */ public void stop() throws Exception { super.stop(); executorService.shutdown(); proxy.shutdown(); } public Configuration getFreemarkerConfiguration() { return configuration; } public ProxyConfig getProxyConfig() { return config; } public Pom readPom(File folder) { if (folder.isDirectory()) { File [] poms = folder.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(org.moxie.Constants.POM); } }); if (poms == null || poms.length == 0) { return null; } File pomFile = poms[0]; IMavenCache cache = config.getMoxieCache(); return PomReader.readPom(cache, pomFile); } return null; } /** * Searches the specified repositories for the given text or query * * @param text * if the text is null or empty, null is returned * @param page * the page number to retrieve. page is 1-indexed. * @param pageSize * the number of elements to return for this page * @return a list of SearchResults in order from highest to the lowest score * */ public List<SearchResult> search(String query, int page, int pageSize) { return lucene.search(query, page, pageSize, getAccessibleRepositories()); } public List<SearchResult> getRecentArtifacts(String repository, int page, int pageSize) { SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd"); Calendar c = Calendar.getInstance(); c.add(Calendar.DATE, 1); String to = df.format(c.getTime()); c.add(Calendar.DATE, -180); String from = df.format(c.getTime()); String query = MessageFormat.format("date:[{0} TO {1}] AND NOT packaging:pom", from, to); List<SearchResult> list; if (StringUtils.isEmpty(repository)) { // all accessible repositories list = lucene.search(query, page, pageSize, getAccessibleRepositories()); } else { // specified repository list = lucene.search(query, page, pageSize, repository); } Collections.sort(list); return list; } public int getArtifactCount(String repository) { List<SearchResult> list = lucene.search("*", 1, 0, repository); return list.size(); } public String getRepositorySize(String repository) { return config.getRepositorySize(repository); } List<String> getAccessibleRepositories() { // TODO filter repositories by login List<String> list = new ArrayList<String>(); list.addAll(config.getLocalRepositories()); for (RemoteRepository repository : config.getRemoteRepositories()) { list.add(repository.id); } return list; } boolean authenticate(String username, String password) { // TODO authenticate here return true; } }