source: src/main/java/geniusweb/partiesserver/AvailablePartiesUpdater.java@ 44

Last change on this file since 44 was 44, checked in by bart, 2 years ago

Domaineditor released.

File size: 7.5 KB
Line 
1package geniusweb.partiesserver;
2
3import java.io.File;
4import java.io.IOException;
5import java.net.MalformedURLException;
6import java.net.URISyntaxException;
7import java.net.URL;
8import java.nio.file.Path;
9import java.nio.file.Paths;
10import java.util.Collection;
11import java.util.LinkedList;
12import java.util.logging.Level;
13
14import geniusweb.partiesserver.repository.AvailableJavaParty;
15import geniusweb.partiesserver.repository.AvailablePartiesRepo;
16import geniusweb.partiesserver.repository.AvailableParty;
17import geniusweb.party.Party;
18import tudelft.utilities.files.FileInfo;
19import tudelft.utilities.files.FileWatcher;
20import tudelft.utilities.immutablelist.Tuple;
21import tudelft.utilities.listener.Listenable;
22import tudelft.utilities.logging.ReportToLogger;
23import tudelft.utilities.logging.Reporter;
24import tudelft.utilities.repository.Repository;
25
26/**
27 *
28 * This object keeps the {@link AvailablePartiesRepo} in sync with the file
29 * system. To work, this must be started in a global background thread,
30 * independent of current sessions etc. This thread is created when the
31 * constructor is called.
32 * <p>
33 * The {@link #PARTIESREPO} points to a directory that must exist. All the jar
34 * files in there are collected and tested if they can be loaded and are then
35 * added to the list of available parties.
36 * <p>
37 * Please make only 1 instance of this class. Double updates on the
38 * {@link AvailablePartiesRepo} is not useful
39 */
40public class AvailablePartiesUpdater {
41 private static final String PARTIESREPO = "partiesrepo";
42 private final Path rootdir;
43 private final Reporter log = new ReportToLogger("partiesserver");
44 private final Listenable<Tuple<FileInfo, FileInfo>> watcher;
45 private final Repository<String, AvailableParty> repository;
46
47 /**
48 *
49 * @param repo the repo to be automatically updated.
50 */
51 public AvailablePartiesUpdater(Repository<String, AvailableParty> repo) {
52 this.repository = repo;
53 this.rootdir = getRootDir();
54 log.log(Level.INFO, "Repository located at " + this.rootdir);
55 watcher = getFileWatcher(rootdir.toFile());
56 watcher.addListener(info -> update(info));
57 // manually trigger the first event.
58 update(new Tuple<FileInfo, FileInfo>(null,
59 new FileInfo(rootdir.toFile(), 2)));
60 }
61
62 /**
63 * Called when some file that may be relevant has changed. Synchronized to
64 * avoid weird states if filesystem changes rapidly. We ignore everything
65 * but jar files.
66 */
67 private synchronized void update(Tuple<FileInfo, FileInfo> info) {
68 log.log(Level.INFO, "change: " + info);
69 File oldfile = info.get1() == null ? null : info.get1().getFile();
70 File newfile = info.get2() == null ? null : info.get2().getFile();
71
72 File file = oldfile != null ? oldfile : newfile;
73
74 Path relpath = file.toPath().relativize(rootdir);
75 // workaround: root gives "" with depth 1 instead of 0.
76 if (relpath.toString().isEmpty()) {
77 reset();
78 return;
79 }
80
81 // it's file or dir inside the root directory
82 if (newfile == null) {
83 // file removed
84 repository.remove(getShortName(file));
85 } else {
86 tryLoad(file);
87 }
88
89 }
90
91 /**
92 * @param jarfile a jarfile
93 * @return the filename only, without extension
94 *
95 */
96 private String getShortName(File jarfile) {
97 String partyname = jarfile.getName();
98 return partyname.substring(0, partyname.length() - 4);
99
100 }
101
102 /**
103 * Try to load a file. Report to logger if it fails to load.
104 *
105 * @param file the file to load. Supposedly a jar file but we will report if
106 * not.
107 */
108 private void tryLoad(File file) {
109 try {
110 repository.put(
111 new AvailableJavaParty(getShortName(file), load(file)));
112 } catch (Throwable e) {
113 log.log(Level.WARNING, "Can not load class from file " + file, e);
114 }
115 }
116
117 /**
118 * Try to load jarfile, check that it contains required info and that the
119 * class can be instantiated.
120 *
121 * @param jarfile the jarfile to load the class from
122 * @return the loaded Class file from the jarfile (the class that is
123 * assigned as 'main class' in the manifest).
124 * @throws IOException
125 * @throws IllegalAccessException
126 * @throws InstantiationException
127 * @throws ClassNotFoundException
128 * @throws IllegalArgumentException if the given file is not a jar file or
129 * can't be read. Or if the file has no
130 * manifest containing a main class.
131 * @throws RuntimeException if party throws something during
132 * construction or while handling calls to
133 * it.
134 */
135 @SuppressWarnings("unchecked")
136 private Class<? extends Party> load(File jarfile)
137 throws IOException, InstantiationException, IllegalAccessException,
138 ClassNotFoundException {
139 /**
140 * We can not call loader.close() as instances of this class may need to
141 * load more later on when they start running. Only when all instances
142 * are dead and gone we could close but it's not clear how to track the
143 * number of active instances nor how to get a callback on that here.
144 */
145 JarClassLoader1 loader = new JarClassLoader1(toURL(jarfile),
146 getClass().getClassLoader());
147 String mainclass = loader.getMainClassName();
148 if (mainclass == null) {
149 throw new IllegalArgumentException(
150 "jar file does not contain manifest with main class");
151 }
152 Class<? extends Party> partyclass = (Class<? extends Party>) loader
153 .loadClass(mainclass);
154 partyclass.newInstance();
155 return partyclass;
156 }
157
158 private URL toURL(File file) throws MalformedURLException {
159 return new URL("jar:" + file.toURI().toURL() + "!/");
160 }
161
162 /**
163 * Remove all parties and reload them all from scratch
164 */
165 private void reset() {
166 Collection<AvailableParty> toremove = new LinkedList<>(
167 repository.list());
168 for (AvailableParty party : toremove) {
169 repository.remove(party.getID());
170 }
171 File rootfile = rootdir.toFile();
172 if (rootfile.exists() && rootfile.canRead() && rootfile.isDirectory()) {
173 for (File file : rootfile.listFiles()) {
174 tryLoad(file);
175 }
176 } else {
177 log.log(Level.SEVERE, "Can not read repository root directory "
178 + rootdir
179 + ": it does not exist, can not be read or is not a directory.");
180 }
181 }
182
183 /**
184 * Search from location of this class upwards till we find a directory
185 * called "domainsrepo".
186 *
187 * @return the root dir of the repo. This is the 'database' where all known
188 * profiles are stored.
189 */
190 protected Path getRootDir() {
191 // If you run this normal from tomcat we are somewhere inside
192 // projectroot/WEB_INF/classes/....
193 // Search back upwards to projectroot.
194 Path dir;
195 try {
196 dir = Paths.get(getClass().getProtectionDomain().getCodeSource()
197 .getLocation().toURI()).getParent();
198 } catch (URISyntaxException e) {
199 throw new RuntimeException("problem with path uri", e);
200 }
201 log.log(Level.INFO,
202 "searching for " + PARTIESREPO + " upwards from " + dir);
203 while (dir.getNameCount() > 1) {
204 Path repo = dir.resolve(PARTIESREPO);
205 if (repo.toFile().exists())
206 return repo;
207 log.log(Level.INFO, "Directory did not contain repo:" + dir);
208 dir = dir.getParent();
209 }
210 return Paths.get(PARTIESREPO); // bit silly, it's not there, but we need
211 // to do something.
212 }
213
214 /**
215 * Factory method that gives a watcher for file changes. See
216 * {@link FileWatcher}.
217 *
218 * @param directory the directory to watch
219 * @return a class that watches for file changes in the given directory.
220 * Should ignore changes in sub-directories
221 */
222 protected Listenable<Tuple<FileInfo, FileInfo>> getFileWatcher(
223 File directory) {
224 return new FileWatcher(directory, 3000, 1);
225 }
226}
Note: See TracBrowser for help on using the repository browser.