001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.request.resource;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Locale;
023
024import org.apache.wicket.Application;
025import org.apache.wicket.core.util.lang.WicketObjects;
026import org.apache.wicket.markup.head.HeaderItem;
027import org.apache.wicket.util.io.IClusterable;
028import org.apache.wicket.util.lang.Args;
029import org.apache.wicket.util.lang.Objects;
030import org.danekja.java.util.function.serializable.SerializableSupplier;
031
032/**
033 * Reference to a resource. Can be used to reference global resources.
034 * <p>
035 * Even though resource reference is just a factory for resources, it still needs to be identified
036 * by a globally unique identifier, combination of <code>scope</code> and <code>name</code>. Those
037 * are used to generate URLs for resource references. <code>locale</code>, <code>style</code> and
038 * <code>variation</code> are optional fields to allow having specific references for individual
039 * locales, styles and variations.
040 * 
041 * @author Matej Knopp
042 * @author Juergen Donnerstag
043 */
044public abstract class ResourceReference implements IClusterable
045{
046        private static final long serialVersionUID = 1L;
047
048        private final Key data;
049
050        /**
051         * Creates new {@link ResourceReference} instance.
052         * 
053         * @param key
054         *            The data making up the resource reference
055         */
056        public ResourceReference(final Key key)
057        {
058                Args.notNull(key, "key");
059
060                data = key;
061        }
062
063        /**
064         * Creates new {@link ResourceReference} instance.
065         * 
066         * @param scope
067         *            mandatory parameter
068         * @param name
069         *            mandatory parameter
070         * @param locale
071         *            resource locale
072         * @param style
073         *            resource style
074         * @param variation
075         *            resource variation
076         */
077        public ResourceReference(Class<?> scope, String name, Locale locale, String style,
078                String variation)
079        {
080                Args.notNull(scope, "scope");
081                Args.notNull(name, "name");
082
083                data = new Key(scope.getName(), name, locale, style, variation);
084        }
085
086        /**
087         * Creates new {@link ResourceReference} instance.
088         * 
089         * @param scope
090         *            mandatory parameter
091         * @param name
092         *            mandatory parameter
093         */
094        public ResourceReference(Class<?> scope, String name)
095        {
096                this(scope, name, null, null, null);
097        }
098
099        /**
100         * Construct.
101         * 
102         * @param name
103         *            resource name
104         */
105        public ResourceReference(String name)
106        {
107                this(Application.class, name, null, null, null);
108        }
109
110        /**
111         * @return Gets the data making up the resource reference. They'll be use by
112         *         ResourceReferenceRegistry to make up the key under which the resource reference gets
113         *         stored.
114         */
115        public final Key getKey()
116        {
117                return data;
118        }
119
120        /**
121         * @return name
122         */
123        public String getName()
124        {
125                return data.getName();
126        }
127
128        /**
129         * returns extension of the resource reference
130         * 
131         * @return extension of the resource's name in lower-case or <code>null</code> if there is no
132         *         extension
133         */
134        public final String getExtension()
135        {
136                String name = getName();
137
138                final int queryAt = name.indexOf('?');
139
140                // remove query string part
141                if (queryAt != -1)
142                {
143                        name = name.substring(0, queryAt);
144                }
145
146                // get start of extension
147                final int extPos = name.lastIndexOf('.');
148
149                if (extPos == -1)
150                {
151                        return null;
152                }
153
154                // return extension
155                return name.substring(extPos + 1).toLowerCase(Locale.ROOT);
156        }
157
158        /**
159         * @return scope
160         */
161        public Class<?> getScope()
162        {
163                return WicketObjects.resolveClass(data.getScope());
164        }
165
166        /**
167         * @return locale
168         */
169        public Locale getLocale()
170        {
171                return data.getLocale();
172        }
173
174        /**
175         * @return style
176         */
177        public String getStyle()
178        {
179                return data.getStyle();
180        }
181
182        /**
183         * @return variation
184         */
185        public String getVariation()
186        {
187                return data.getVariation();
188        }
189
190        /**
191         * Can be used to disable registering certain resource references in
192         * {@link ResourceReferenceRegistry}.
193         * 
194         * @return <code>true</code> if this reference can be registered, <code>false</code> otherwise.
195         */
196        public boolean canBeRegistered()
197        {
198                return true;
199        }
200
201        /**
202         * @see java.lang.Object#equals(java.lang.Object)
203         */
204        @Override
205        public boolean equals(Object obj)
206        {
207                if (this == obj)
208                {
209                        return true;
210                }
211                if (obj instanceof ResourceReference == false)
212                {
213                        return false;
214                }
215                ResourceReference that = (ResourceReference)obj;
216                return Objects.equal(data, that.data);
217        }
218
219        /**
220         * @see java.lang.Object#hashCode()
221         */
222        @Override
223        public int hashCode()
224        {
225                return data.hashCode();
226        }
227
228        /**
229         * Returns the resource.
230         * 
231         * @return resource instance
232         */
233        public abstract IResource getResource();
234
235        /**
236         * Allows to specify which locale, style and variation values will the generated URL for this
237         * resource reference have.
238         * 
239         * @return url attributes
240         */
241        public UrlAttributes getUrlAttributes()
242        {
243                return new UrlAttributes(getLocale(), getStyle(), getVariation());
244        }
245        
246        /**
247         * Factory method to build a resource reference that uses the provided supplier to return
248         * the resource.
249         * 
250         * @param name
251         *                              The name to use with the resource
252         * @param resourceSupplier
253         *                              Lambda supplier to build the resource
254         * @return the new resource reference
255         */
256        public static final ResourceReference of(String name, SerializableSupplier<IResource> resourceSupplier)
257        {
258                return new LambdaResourceReference(name, resourceSupplier);
259        }
260
261        /**
262         * Factory method to build a resource reference that uses the provided supplier to return
263         * the resource.
264         * 
265         * @param key
266         *                              The {@link Key} to use with the resource
267         * @param resourceSupplier
268         *                              Lambda supplier to build the resource
269         * @return  the new resource reference
270         */
271        public static final ResourceReference of(Key key, SerializableSupplier<IResource> resourceSupplier)
272        {
273                return new LambdaResourceReference(key, resourceSupplier);
274        }
275
276        public static final class LambdaResourceReference extends ResourceReference
277        {
278                private static final long serialVersionUID = 1826862147241009289L;
279                
280                final SerializableSupplier<IResource> resourceBuilder;
281
282                public LambdaResourceReference(String name, SerializableSupplier<IResource> resourceBuilder) 
283                {
284                        super(name);
285                        this.resourceBuilder = Args.notNull(resourceBuilder, "resourceBuilder");
286                }
287
288                public LambdaResourceReference(Key key, SerializableSupplier<IResource> resourceBuilder) 
289                {
290                        super(key);
291                        this.resourceBuilder = Args.notNull(resourceBuilder, "resourceBuilder");
292                }
293
294                @Override
295                public IResource getResource() 
296                {
297                        return resourceBuilder.get();
298                }
299        }
300        
301        /**
302         * @see ResourceReference#getUrlAttributes()
303         * 
304         * @author Matej Knopp
305         */
306        public static class UrlAttributes
307        {
308                private final Locale locale;
309                private final String style;
310                private final String variation;
311
312                /**
313                 * Construct.
314                 * 
315                 * @param locale
316                 *            resource locale
317                 * @param style
318                 *            resource style
319                 * @param variation
320                 *            resource variation
321                 */
322                public UrlAttributes(Locale locale, String style, String variation)
323                {
324                        this.locale = locale;
325                        this.style = style;
326                        this.variation = variation;
327                }
328
329                /**
330                 * @return locale
331                 */
332                public Locale getLocale()
333                {
334                        return locale;
335                }
336
337                /**
338                 * @return style
339                 */
340                public String getStyle()
341                {
342                        return style;
343                }
344
345                /**
346                 * @return variation
347                 */
348                public String getVariation()
349                {
350                        return variation;
351                }
352
353                /**
354                 * @see java.lang.Object#equals(java.lang.Object)
355                 */
356                @Override
357                public boolean equals(Object obj)
358                {
359                        if (this == obj)
360                        {
361                                return true;
362                        }
363                        if (obj instanceof UrlAttributes == false)
364                        {
365                                return false;
366                        }
367                        UrlAttributes that = (UrlAttributes)obj;
368                        return Objects.equal(getLocale(), that.getLocale()) &&
369                                Objects.equal(getStyle(), that.getStyle()) &&
370                                Objects.equal(getVariation(), that.getVariation());
371                }
372
373                /**
374                 * @see java.lang.Object#hashCode()
375                 */
376                @Override
377                public int hashCode()
378                {
379                        return Objects.hashCode(getLocale(), getStyle(), getVariation());
380                }
381
382                /**
383                 * @see java.lang.Object#toString()
384                 */
385                @Override
386                public String toString()
387                {
388                        return "locale: " + locale + "; style: " + style + "; variation: " + variation;
389                }
390        }
391
392        /**
393         * A (re-usable) data store for all relevant ResourceReference data
394         */
395        public static class Key implements Serializable
396        {
397                private static final long serialVersionUID = 1L;
398
399                private final String scope;
400                private final String name;
401                private final Locale locale;
402                private final String style;
403                private final String variation;
404
405                /**
406                 * Construct.
407                 * 
408                 * @param reference
409                 *            resource reference
410                 */
411                public Key(final ResourceReference reference)
412                {
413                        this(reference.getScope().getName(), reference.getName(), reference.getLocale(),
414                                reference.getStyle(), reference.getVariation());
415                }
416
417                /**
418                 * Construct.
419                 * 
420                 * @param scope
421                 *            resource scope
422                 * @param name
423                 *            resource name
424                 * @param locale
425                 *            resource locale
426                 * @param style
427                 *            resource style
428                 * @param variation
429                 *            resource variation
430                 */
431                public Key(final String scope, final String name, final Locale locale, final String style,
432                        final String variation)
433                {
434                        Args.notNull(scope, "scope");
435                        Args.notNull(name, "name");
436
437                        this.scope = scope.intern();
438                        this.name = name.intern();
439                        this.locale = locale;
440                        this.style = style != null ? style.intern() : null;
441                        this.variation = variation != null ? variation.intern() : null;
442                }
443
444                /**
445                 * @see java.lang.Object#equals(java.lang.Object)
446                 */
447                @Override
448                public boolean equals(final Object obj)
449                {
450                        if (this == obj)
451                        {
452                                return true;
453                        }
454                        if (obj instanceof Key == false)
455                        {
456                                return false;
457                        }
458                        Key that = (Key)obj;
459                        return Objects.equal(scope, that.scope) && //
460                                Objects.equal(name, that.name) && //
461                                Objects.equal(locale, that.locale) && //
462                                Objects.equal(style, that.style) && //
463                                Objects.equal(variation, that.variation);
464                }
465
466                /**
467                 * @see java.lang.Object#hashCode()
468                 */
469                @Override
470                public int hashCode()
471                {
472                        return Objects.hashCode(scope, name, locale, style, variation);
473                }
474
475                /**
476                 * Gets scope.
477                 * 
478                 * @return scope
479                 */
480                public final String getScope()
481                {
482                        return scope;
483                }
484
485                /**
486                 * @return Assuming scope ist a fully qualified class name, than get the associated class
487                 */
488                public final Class<?> getScopeClass()
489                {
490                        return WicketObjects.resolveClass(scope);
491                }
492
493                /**
494                 * Gets name.
495                 * 
496                 * @return name
497                 */
498                public final String getName()
499                {
500                        return name;
501                }
502
503                /**
504                 * Gets locale.
505                 * 
506                 * @return locale
507                 */
508                public final Locale getLocale()
509                {
510                        return locale;
511                }
512
513                /**
514                 * Gets style.
515                 * 
516                 * @return style
517                 */
518                public final String getStyle()
519                {
520                        return style;
521                }
522
523                /**
524                 * Gets variation.
525                 * 
526                 * @return variation
527                 */
528                public final String getVariation()
529                {
530                        return variation;
531                }
532
533                /**
534                 * @see java.lang.Object#toString()
535                 */
536                @Override
537                public String toString()
538                {
539                        return "scope: " + scope + "; name: " + name + "; locale: " + locale + "; style: " +
540                                style + "; variation: " + variation;
541                }
542        }
543
544        @Override
545        public String toString()
546        {
547                return data.toString();
548        }
549
550        /**
551         * @return the resources this ResourceReference depends on.
552         */
553        public List<HeaderItem> getDependencies()
554        {
555                return new ArrayList<>();
556        }
557}